From: Wouter Wijngaards Date: Mon, 7 May 2007 13:17:27 +0000 (+0000) Subject: EDNS for the client. X-Git-Tag: release-0.3~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f3c0cd34d83559a24c64f9dda7b6846d76bb994b;p=thirdparty%2Funbound.git EDNS for the client. git-svn-id: file:///svn/unbound/trunk@288 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/daemon/worker.c b/daemon/worker.c index 8a0591613..4dc964240 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -51,6 +51,7 @@ #include "util/storage/slabhash.h" #include "services/listen_dnsport.h" #include "services/outside_network.h" +#include "util/data/msgparse.h" #ifdef HAVE_SYS_TYPES_H # include @@ -62,6 +63,11 @@ #define DNS_ID_AND_FLAGS 4 /** timeout in seconds for UDP queries to auth servers. TODO: proper rtt */ #define UDP_QUERY_TIMEOUT 4 +/** Advertised version of EDNS capabilities */ +#define EDNS_ADVERTISED_VERSION 0 +/** Advertised size of EDNS capabilities */ +#define EDNS_ADVERTISED_SIZE 4096 + void worker_send_cmd(struct worker* worker, ldns_buffer* buffer, @@ -206,6 +212,8 @@ worker_handle_reply(struct comm_point* c, void* arg, int error, struct query_info qinf; struct reply_info* rep; struct msgreply_entry* e; + struct edns_data svr_edns; /* unused server edns advertisement */ + uint16_t us; int r; verbose(VERB_DETAIL, "reply to query with stored ID %d", @@ -223,7 +231,7 @@ worker_handle_reply(struct comm_point* c, void* arg, int error, return 0; /* too much in the query section */ /* woohoo a reply! */ if((r=reply_info_parse(c->buffer, &w->worker->alloc, &qinf, &rep, - w->worker->scratchpad))!=0) { + w->worker->scratchpad, &svr_edns))!=0) { if(r == LDNS_RCODE_SERVFAIL) log_err("reply_info_parse: out of memory"); /* formerr on my parse gives servfail to my client */ @@ -231,8 +239,14 @@ worker_handle_reply(struct comm_point* c, void* arg, int error, region_free_all(w->worker->scratchpad); return 0; } + us = w->edns.udp_size; + w->edns.edns_version = EDNS_ADVERTISED_VERSION; + w->edns.udp_size = EDNS_ADVERTISED_SIZE; + w->edns.ext_rcode = 0; + w->edns.bits &= EDNS_DO; if(!reply_info_answer_encode(&qinf, rep, w->query_id, w->query_flags, - w->query_reply.c->buffer, 0, 0, w->worker->scratchpad)) { + w->query_reply.c->buffer, 0, 0, w->worker->scratchpad, us, + &w->edns)) { replyerror(LDNS_RCODE_SERVFAIL, w); query_info_clear(&qinf); reply_info_parsedelete(rep, &w->worker->alloc); @@ -308,7 +322,7 @@ worker_check_request(ldns_buffer* pkt) LDNS_NSCOUNT(ldns_buffer_begin(pkt))); return LDNS_RCODE_FORMERR; } - if(LDNS_ARCOUNT(ldns_buffer_begin(pkt)) != 0) { + if(LDNS_ARCOUNT(ldns_buffer_begin(pkt)) > 1) { verbose(VERB_DETAIL, "request wrong nr ar=%d", LDNS_ARCOUNT(ldns_buffer_begin(pkt))); return LDNS_RCODE_FORMERR; @@ -349,11 +363,12 @@ worker_handle_control_cmd(struct comm_point* c, void* arg, int error, /** answer query from the cache */ static int answer_from_cache(struct worker* worker, struct lruhash_entry* e, uint16_t id, - uint16_t flags, struct comm_reply* repinfo) + uint16_t flags, struct comm_reply* repinfo, struct edns_data* edns) { struct msgreply_entry* mrentry = (struct msgreply_entry*)e->key; struct reply_info* rep = (struct reply_info*)e->data; uint32_t timenow = time(0); + uint16_t udpsize = edns->udp_size; size_t i; /* see if it is possible */ if(rep->ttl <= timenow) { @@ -361,6 +376,10 @@ answer_from_cache(struct worker* worker, struct lruhash_entry* e, uint16_t id, /* but this ignores it */ return 0; } + edns->edns_version = EDNS_ADVERTISED_VERSION; + edns->udp_size = EDNS_ADVERTISED_SIZE; + edns->ext_rcode = 0; + edns->bits &= EDNS_DO; /* check rrsets */ for(i=0; irrset_count; i++) { if(i>0 && rep->ref[i].key == rep->ref[i-1].key) @@ -377,7 +396,8 @@ answer_from_cache(struct worker* worker, struct lruhash_entry* e, uint16_t id, } /* locked and ids and ttls are OK. */ if(!reply_info_answer_encode(&mrentry->key, rep, id, flags, - repinfo->c->buffer, timenow, 1, worker->scratchpad)) { + repinfo->c->buffer, timenow, 1, worker->scratchpad, + udpsize, edns)) { replyerror_fillbuf(LDNS_RCODE_SERVFAIL, repinfo, id, flags, &mrentry->key); } @@ -403,6 +423,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, struct lruhash_entry* e; struct query_info qinfo; struct work_query* w; + struct edns_data edns; verbose(VERB_DETAIL, "worker handle request"); if(error != NETEVENT_NOERROR) { @@ -427,12 +448,27 @@ worker_handle_request(struct comm_point* c, void* arg, int error, return 1; } h = query_info_hash(&qinfo); + if((ret=parse_edns_from_pkt(c->buffer, &edns)) != 0) { + LDNS_QR_SET(ldns_buffer_begin(c->buffer)); + LDNS_RCODE_SET(ldns_buffer_begin(c->buffer), ret); + return 1; + } + if(edns.edns_present && edns.edns_version != 0) { + /* TODO BADVERS errcode */ + LDNS_QR_SET(ldns_buffer_begin(c->buffer)); + LDNS_RCODE_SET(ldns_buffer_begin(c->buffer), + LDNS_RCODE_NOTIMPL); + return 1; + } + if(c->type != comm_udp) + edns.udp_size = 65535; /* max size for TCP replies */ if((e=slabhash_lookup(worker->daemon->msg_cache, h, &qinfo, 0))) { /* answer from cache - we have acquired a readlock on it */ log_info("answer from the cache"); if(answer_from_cache(worker, e, *(uint16_t*)ldns_buffer_begin(c->buffer), - ldns_buffer_read_u16_at(c->buffer, 2), repinfo)) { + ldns_buffer_read_u16_at(c->buffer, 2), repinfo, + &edns)) { lock_rw_unlock(&e->lock); return 1; } @@ -458,6 +494,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, query_info_clear(&qinfo); return 0; } + w->edns = edns; worker->free_queries = w->next; worker->num_requests ++; log_assert(worker->num_requests <= worker->request_size); diff --git a/daemon/worker.h b/daemon/worker.h index 50e9722d4..d278aec6a 100644 --- a/daemon/worker.h +++ b/daemon/worker.h @@ -48,6 +48,7 @@ #include "util/locks.h" #include "util/alloc.h" #include "util/data/msgreply.h" +#include "util/data/msgparse.h" #include "daemon/stats.h" struct listen_dnsport; struct outside_network; @@ -84,6 +85,8 @@ struct work_query { uint16_t query_id; /** flags uint16 from query */ uint16_t query_flags; + /** edns data from the query */ + struct edns_data edns; }; /** diff --git a/doc/Changelog b/doc/Changelog index 78b90f872..b81dc0bea 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +7 May 2007: Wouter + - EDNS read from query, used to make reply smaller. + - advertised edns value constants. + 4 May 2007: Wouter - msgreply sizefunc is more accurate. - config settings for rrset cache size and slabs. diff --git a/testcode/unitmsgparse.c b/testcode/unitmsgparse.c index fe5221204..5e42bdf80 100644 --- a/testcode/unitmsgparse.c +++ b/testcode/unitmsgparse.c @@ -251,6 +251,7 @@ testpkt(ldns_buffer* pkt, struct alloc_cache* alloc, ldns_buffer* out, uint16_t flags; uint32_t timenow = 0; region_type *region = region_create(malloc, free); + struct edns_data edns; hex_to_buf(pkt, hex); memmove(&id, ldns_buffer_begin(pkt), sizeof(id)); @@ -258,7 +259,7 @@ testpkt(ldns_buffer* pkt, struct alloc_cache* alloc, ldns_buffer* out, flags = 0; else memmove(&flags, ldns_buffer_at(pkt, 2), sizeof(flags)); flags = ntohs(flags); - ret = reply_info_parse(pkt, alloc, &qi, &rep, region); + ret = reply_info_parse(pkt, alloc, &qi, &rep, region, &edns); if(ret != 0) { if(vbmp) printf("parse code %d: %s\n", ret, ldns_lookup_by_id(ldns_rcodes, ret)->name); @@ -267,7 +268,7 @@ testpkt(ldns_buffer* pkt, struct alloc_cache* alloc, ldns_buffer* out, unit_assert(ret != LDNS_RCODE_SERVFAIL); } else { ret = reply_info_encode(&qi, rep, id, flags, out, timenow, - region); + region, 65535); unit_assert(ret != 0); /* udp packets should fit */ if(vbmp) printf("inlen %u outlen %u\n", (unsigned)ldns_buffer_limit(pkt), diff --git a/util/data/msgparse.c b/util/data/msgparse.c index 6a0176c51..71b9246d1 100644 --- a/util/data/msgparse.c +++ b/util/data/msgparse.c @@ -904,3 +904,34 @@ parse_extract_edns(struct msg_parse* msg, struct edns_data* edns) /* ignore rdata and rrsigs */ return 0; } + +int +parse_edns_from_pkt(ldns_buffer* pkt, struct edns_data* edns) +{ + log_assert(LDNS_QDCOUNT(ldns_buffer_begin(pkt)) == 1); + log_assert(LDNS_ANCOUNT(ldns_buffer_begin(pkt)) == 0); + log_assert(LDNS_NSCOUNT(ldns_buffer_begin(pkt)) == 0); + /* check edns section is present */ + if(LDNS_ARCOUNT(ldns_buffer_begin(pkt)) > 1) { + return LDNS_RCODE_FORMERR; + } + if(LDNS_ARCOUNT(ldns_buffer_begin(pkt)) == 0) { + memset(edns, 0, sizeof(*edns)); + edns->udp_size = 512; + return 0; + } + /* domain name must be the root of length 1. */ + if(pkt_dname_len(pkt) != 1) + return LDNS_RCODE_FORMERR; + if(ldns_buffer_remaining(pkt) < 10) /* type, class, ttl, rdatalen */ + return LDNS_RCODE_FORMERR; + if(ldns_buffer_read_u16(pkt) != LDNS_RR_TYPE_OPT) + return LDNS_RCODE_FORMERR; + edns->edns_present = 1; + edns->udp_size = ldns_buffer_read_u16(pkt); /* class is udp size */ + edns->ext_rcode = ldns_buffer_read_u8(pkt); /* ttl used for bits */ + edns->edns_version = ldns_buffer_read_u8(pkt); + edns->bits = ldns_buffer_read_u16(pkt); + /* ignore rdata and rrsigs */ + return 0; +} diff --git a/util/data/msgparse.h b/util/data/msgparse.h index 0b2023a05..fece48e8f 100644 --- a/util/data/msgparse.h +++ b/util/data/msgparse.h @@ -189,6 +189,8 @@ struct rr_parse { #define EDNS_RCODE_BADVERS 16 /** bad EDNS version */ /** largest valid compression offset */ #define PTR_MAX_OFFSET 0x3fff +/** bits for EDNS bitfield */ +#define EDNS_DO 0x8000 /* Dnssec Ok */ /** * EDNS data storage @@ -244,4 +246,15 @@ int parse_packet(ldns_buffer* pkt, struct msg_parse* msg, */ int parse_extract_edns(struct msg_parse* msg, struct edns_data* edns); +/** + * If EDNS data follows a query section, extract it and initialize edns struct. + * @param pkt: the packet. position at start must be right after the query + * section. At end, right after EDNS data or no movement if failed. + * @param edns: the edns data allocated by the caller. Does not have to be + * initialised. + * @return: 0 on success, or an RCODE on error. + * RCODE formerr if OPT is badly formatted and so on. + */ +int parse_edns_from_pkt(ldns_buffer* pkt, struct edns_data* edns); + #endif /* UTIL_DATA_MSGPARSE_H */ diff --git a/util/data/msgreply.c b/util/data/msgreply.c index 727bdd653..4095e7567 100644 --- a/util/data/msgreply.c +++ b/util/data/msgreply.c @@ -343,7 +343,8 @@ parse_create_msg(ldns_buffer* pkt, struct msg_parse* msg, } int reply_info_parse(ldns_buffer* pkt, struct alloc_cache* alloc, - struct query_info* qinf, struct reply_info** rep, struct region* region) + struct query_info* qinf, struct reply_info** rep, struct region* region, + struct edns_data* edns) { /* use scratch pad region-allocator during parsing. */ struct msg_parse* msg; @@ -360,6 +361,8 @@ int reply_info_parse(ldns_buffer* pkt, struct alloc_cache* alloc, if((ret = parse_packet(pkt, msg, region)) != 0) { return ret; } + if((ret = parse_extract_edns(msg, edns)) != 0) + return ret; /* parse OK, allocate return structures */ /* this also performs dname decompression */ @@ -986,13 +989,15 @@ insert_section(struct reply_info* rep, size_t num_rrsets, uint16_t* num_rrs, int reply_info_encode(struct query_info* qinfo, struct reply_info* rep, uint16_t id, uint16_t flags, ldns_buffer* buffer, uint32_t timenow, - region_type* region) + region_type* region, uint16_t udpsize) { uint16_t ancount=0, nscount=0, arcount=0; struct compress_tree_node* tree = 0; int r; ldns_buffer_clear(buffer); + if(udpsize < ldns_buffer_limit(buffer)) + ldns_buffer_set_limit(buffer, udpsize); if(ldns_buffer_remaining(buffer) < LDNS_HEADER_SIZE) return 0; @@ -1061,10 +1066,45 @@ int reply_info_encode(struct query_info* qinfo, struct reply_info* rep, return 1; } +/** estimate size of EDNS field in packet */ +static uint16_t +calc_edns_field_size(struct edns_data* edns) +{ + if(!edns || !edns->edns_present) + return 0; + /* domain root '.' + type + class + ttl + rdatalen(=0) */ + return 1 + 2 + 2 + 4 + 2; +} + +/** append EDNS field as last record in packet */ +static void +attach_edns_field(ldns_buffer* pkt, struct edns_data* edns) +{ + size_t len; + if(!edns || !edns->edns_present) + return; + /* inc additional count */ + ldns_buffer_write_u16_at(pkt, 10, + ldns_buffer_read_u16_at(pkt, 10) + 1); + len = ldns_buffer_limit(pkt); + ldns_buffer_clear(pkt); + ldns_buffer_set_position(pkt, len); + /* write EDNS record */ + ldns_buffer_write_u8(pkt, 0); /* '.' label */ + ldns_buffer_write_u16(pkt, LDNS_RR_TYPE_OPT); /* type */ + ldns_buffer_write_u16(pkt, edns->udp_size); /* class */ + ldns_buffer_write_u8(pkt, edns->ext_rcode); /* ttl */ + ldns_buffer_write_u8(pkt, edns->edns_version); + ldns_buffer_write_u16(pkt, edns->bits); + ldns_buffer_write_u16(pkt, 0); /* rdatalen */ + ldns_buffer_flip(pkt); +} + int reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, uint16_t id, uint16_t qflags, ldns_buffer* pkt, uint32_t timenow, - int cached, struct region* region) + int cached, struct region* region, uint16_t udpsize, + struct edns_data* edns) { uint16_t flags; @@ -1076,11 +1116,15 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, flags = (rep->flags & ~BIT_AA) | (qflags & (BIT_RD|BIT_CD)); } log_assert(flags & BIT_QR); /* QR bit must be on in our replies */ - - if(!reply_info_encode(qinf, rep, id, flags, pkt, timenow, region)) { + if(udpsize < LDNS_HEADER_SIZE + calc_edns_field_size(edns)) + return 0; /* packet too small to contain edns... */ + udpsize -= calc_edns_field_size(edns); + if(!reply_info_encode(qinf, rep, id, flags, pkt, timenow, region, + udpsize)) { log_err("reply encode: out of memory"); return 0; } + attach_edns_field(pkt, edns); return 1; } diff --git a/util/data/msgreply.h b/util/data/msgreply.h index 857733ffe..467cc96ac 100644 --- a/util/data/msgreply.h +++ b/util/data/msgreply.h @@ -47,6 +47,7 @@ struct comm_reply; struct alloc_cache; struct iovec; struct region; +struct edns_data; /** * Structure to store query information that makes answers to queries @@ -185,13 +186,14 @@ int query_info_parse(struct query_info* m, ldns_buffer* query); * @param rep: allocated reply_info is returned (only on no error). * @param qinf: query_info is returned (only on no error). * @param region: where to store temporary data (for parsing). + * @param edns: where to store edns information, does not need to be inited. * @return: zero is OK, or DNS error code in case of error * o FORMERR for parse errors. * o SERVFAIL for memory allocation errors. */ int reply_info_parse(ldns_buffer* pkt, struct alloc_cache* alloc, struct query_info* qinf, struct reply_info** rep, - struct region* region); + struct region* region, struct edns_data* edns); /** * Sorts the ref array. @@ -256,11 +258,15 @@ hashvalue_t query_info_hash(struct query_info *q); * @param cached: set true if a cached reply (so no AA bit). * set false for the first reply. * @param region: where to allocate temp variables (for compression). + * @param udpsize: size of the answer, 512, from EDNS, or 64k for TCP. + * @param edns: EDNS data included in the answer, NULL for none. + * or if edns_present = 0, it is not included. * @return: 0 on error (server failure). */ int reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, uint16_t id, uint16_t qflags, ldns_buffer* dest, uint32_t timenow, - int cached, struct region* region); + int cached, struct region* region, uint16_t udpsize, + struct edns_data* edns); /** * Regenerate the wireformat from the stored msg reply. @@ -274,12 +280,13 @@ int reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, * @param buffer: buffer to store the packet into. * @param timenow: time now, to adjust ttl values. * @param region: to store temporary data in. + * @param udpsize: size of the answer, 512, from EDNS, or 64k for TCP. * @return: nonzero is success, or * 0 on error: malloc failure (no log_err has been done). */ int reply_info_encode(struct query_info* qinfo, struct reply_info* rep, uint16_t id, uint16_t flags, ldns_buffer* buffer, uint32_t timenow, - struct region* region); + struct region* region, uint16_t udpsize); /** * Setup query info entry