]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
EDNS for the client.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Mon, 7 May 2007 13:17:27 +0000 (13:17 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Mon, 7 May 2007 13:17:27 +0000 (13:17 +0000)
git-svn-id: file:///svn/unbound/trunk@288 be551aaa-1e26-0410-a405-d3ace91eadb9

daemon/worker.c
daemon/worker.h
doc/Changelog
testcode/unitmsgparse.c
util/data/msgparse.c
util/data/msgparse.h
util/data/msgreply.c
util/data/msgreply.h

index 8a05916132cc31035f263b4f17904159c063cdb4..4dc964240285c5ab7e00d21a7f391c5639da501b 100644 (file)
@@ -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 <sys/types.h>
 #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; i<rep->rrset_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);
index 50e9722d461287380ddb29375823f32243bc6059..d278aec6aa212cf443088935138fe3bbbb560543 100644 (file)
@@ -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;
 };
 
 /**
index 78b90f8725ab297590b51d0ba51384de3be55449..b81dc0bea6ec46ee50711596b42376f22f397642 100644 (file)
@@ -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.
index fe5221204040df30542e42782a31c281bf106aec..5e42bdf80e71738598dc2c730d5f669d738db00a 100644 (file)
@@ -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),
index 6a0176c5113f93efed0f51dd585b2d20472dee09..71b9246d1aa04287b44f9b31d863906990db68b6 100644 (file)
@@ -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;
+}
index 0b2023a05f8a4010c8b4f8ff32f1f6db922d5133..fece48e8f69d265a46db38fbfbf67ee13be335ab 100644 (file)
@@ -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 */
index 727bdd65344fd34448c19fa64a76344d707905be..4095e75670e33af075504e32e28e9f17fe2d9ece 100644 (file)
@@ -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;
 }
 
index 857733ffe05c3efe275f93084890eb16e86d10e3..467cc96ace2745c07c8504cd2e2b827ef6f2c4fb 100644 (file)
@@ -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