From e7829aefd8a20a91d642d36a1da7df37c32581cb Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 22 Jan 2014 22:21:51 +0000 Subject: [PATCH] Cache RRSIGS. --- src/cache.c | 49 +++++++++++---- src/dnsmasq.h | 1 + src/dnssec.c | 162 ++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 153 insertions(+), 59 deletions(-) diff --git a/src/cache.c b/src/cache.c index 126d259..4921ea6 100644 --- a/src/cache.c +++ b/src/cache.c @@ -330,8 +330,9 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name)) { - - if ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || ((crecp->flags | flags) & F_CNAME)) + /* Don't delete DNSSEC in favour of a CNAME, they can co-exist */ + if ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || + ((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))) { if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) return 0; @@ -344,7 +345,7 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign #ifdef HAVE_DNSSEC /* Deletion has to be class-sensitive for DS, DNSKEY, RRSIG, also type-covered sensitive for RRSIG */ - if (flags & crecp->flags & (F_DNSKEY | F_DS)) + if ((flags & (F_DNSKEY | F_DS)) == (crecp->flags & (F_DNSKEY | F_DS))) { int del = 0; switch (flags & (F_DS | F_DNSKEY)) @@ -1227,6 +1228,7 @@ void cache_add_dhcp_entry(char *host_name, int prot, void dump_cache(time_t now) { struct server *serv, *serv1; + char *t = ""; my_syslog(LOG_INFO, _("time %lu"), (unsigned long)now); my_syslog(LOG_INFO, _("cache size %d, %d/%d cache insertions re-used unexpired cache entries."), @@ -1267,7 +1269,7 @@ void dump_cache(time_t now) { struct crec *cache ; int i; - my_syslog(LOG_INFO, "Host Address Flags Expires"); + my_syslog(LOG_INFO, "Host Address Flags Expires"); for (i=0; ihash_next) @@ -1282,9 +1284,21 @@ void dump_cache(time_t now) #ifdef HAVE_DNSSEC else if (cache->flags & F_DS) { - a = daemon->addrbuff; - sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag, - cache->addr.ds.algo, cache->addr.ds.digest); + if (cache->flags & F_DNSKEY) + { + char tp[20]; + /* RRSIG */ + querystr("", tp, cache->addr.sig.type_covered); + a = daemon->addrbuff; + sprintf(a, "%5u %3u %s", cache->addr.sig.keytag, + cache->addr.sig.algo, tp); + } + else + { + a = daemon->addrbuff; + sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag, + cache->addr.ds.algo, cache->addr.ds.digest); + } } else if (cache->flags & F_DNSKEY) { @@ -1304,12 +1318,21 @@ void dump_cache(time_t now) #endif } - p += sprintf(p, "%-30.30s %s%s%s%s%s%s%s%s%s%s%s%s%s ", a, - cache->flags & F_IPV4 ? "4" : "", - cache->flags & F_IPV6 ? "6" : "", - cache->flags & F_DNSKEY ? "K" : "", - cache->flags & F_DS ? "S" : "", - cache->flags & F_CNAME ? "C" : "", + if (cache->flags & F_IPV4) + t = "4"; + else if (cache->flags & F_IPV6) + t = "6"; + else if (cache->flags & F_CNAME) + t = "C"; +#ifdef HAVE_DNSSEC + else if ((cache->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) + t = "G"; /* DNSKEY and DS set -> RRISG */ + else if (cache->flags & F_DS) + t = "S"; + else if (cache->flags & F_DNSKEY) + t = "K"; +#endif + p += sprintf(p, "%-30.30s %s%s%s%s%s%s%s%s%s ", a, t, cache->flags & F_FORWARD ? "F" : " ", cache->flags & F_REVERSE ? "R" : " ", cache->flags & F_IMMORTAL ? "I" : " ", diff --git a/src/dnsmasq.h b/src/dnsmasq.h index b60e639..48610a3 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -380,6 +380,7 @@ struct crec { struct { struct blockdata *keydata; unsigned short class, type_covered, keytag; + char algo; } sig; } addr; time_t ttd; /* time to die */ diff --git a/src/dnssec.c b/src/dnssec.c index a80df99..eff01b8 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -484,7 +484,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int otherwise find the key in the cache. */ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, - int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in) + int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in) { static unsigned char **rrset = NULL, **sigs = NULL; static int rrset_sz = 0, sig_sz = 0; @@ -500,12 +500,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in name_labels = count_labels(name); /* For 4035 5.3.2 check */ + cache_start_insert(); /* RRSIGS */ + /* look for RRSIGs for this RRset and get pointers to each RR in the set. */ for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); j != 0; j--) { unsigned char *pstart, *pdata; - int stype, sclass; + int stype, sclass, ttl; pstart = p; @@ -514,12 +516,15 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in GETSHORT(stype, p); GETSHORT(sclass, p); - p += 4; /* TTL */ + GETLONG(ttl, p); pdata = p; GETSHORT(rdlen, p); + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_INSECURE; + if (res == 1 && sclass == class) { if (stype == type) @@ -550,17 +555,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in return STAT_INSECURE; /* bad packet */ GETSHORT(type_covered, p); - algo = *p++; - labels = *p++; - p += 4; /* orig_ttl */ - GETLONG(sig_expiration, p); - GETLONG(sig_inception, p); - p = pdata + 2; /* restore for ADD_RDLEN */ - if (type_covered == type && - check_date_range(sig_inception, sig_expiration) && - hash_find(algo_digest_name(algo)) && - labels <= name_labels) + if (type_covered == type) { if (sigidx == sig_sz) { @@ -581,7 +577,34 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in } sigs[sigidx++] = pdata; - } + + /* If it's a type we're going to cache, cache the RRISG too */ + if (type_covered == T_A || type_covered == T_AAAA || + type_covered == T_CNAME || type_covered == T_DS || + type_covered == T_DNSKEY) + { + struct all_addr a; + struct blockdata *block; + a.addr.dnssec.class = class; + a.addr.dnssec.type = type_covered; + + algo = *p++; + p += 13; /* labels, orig_ttl, expiration, inception */ + GETSHORT(key_tag, p); + if ((block = blockdata_alloc((char*)pdata + 2, rdlen)) && + (crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS))) + { + crecp->uid = rdlen; + crecp->addr.sig.keydata = block; + crecp->addr.sig.class = class; + crecp->addr.sig.keytag = key_tag; + crecp->addr.sig.type_covered = type_covered; + crecp->addr.sig.algo = algo; + } + } + } + + p = pdata + 2; /* restore for ADD_RDLEN */ } } @@ -589,6 +612,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in return STAT_INSECURE; } + cache_end_insert(); /* RRSIGS */ + /* RRset empty, no RRSIGs */ if (rrsetidx == 0 || sigidx == 0) return STAT_INSECURE; @@ -616,23 +641,26 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in algo = *p++; labels = *p++; GETLONG(orig_ttl, p); - p += 8; /* sig_expiration and sig_inception */ + GETLONG(sig_expiration, p); + GETLONG(sig_inception, p); GETSHORT(key_tag, p); if (!extract_name(header, plen, &p, keyname, 1, 0)) return STAT_INSECURE; + if (!check_date_range(sig_inception, sig_expiration) || + labels > name_labels || + !(hash = hash_find(algo_digest_name(algo))) || + !hash_init(hash, &ctx, &digest)) + continue; + /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */ if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY))) return STAT_NEED_KEY; sig = p; sig_len = rdlen - (p - psav); - - if (!(hash = hash_find(algo_digest_name(algo))) || - !hash_init(hash, &ctx, &digest)) - continue; - + nsigttl = htonl(orig_ttl); hash->update(ctx, 18, psav); @@ -756,10 +784,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch return STAT_NEED_DS; } - cache_start_insert(); - /* NOTE, we need to find ONE DNSKEY which matches the DS */ - for (valid = 0, j = ntohs(header->ancount); j != 0; j--) + for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) { /* Ensure we have type, class TTL and length */ if (!(rc = extract_name(header, plen, &p, name, 0, 10))) @@ -788,35 +814,19 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch return STAT_INSECURE; algo = *p++; keytag = dnskey_keytag(algo, flags, p, rdlen - 4); + key = NULL; - /* Cache needs to known class for DNSSEC stuff */ - a.addr.dnssec.class = class; - - /* Put the key into the cache. Note that if the validation fails, we won't - call cache_end_insert() and this will never be committed. */ - if ((key = blockdata_alloc((char*)p, rdlen - 4)) && - (recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))) - { - struct all_addr a; - - a.addr.keytag = keytag; - log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u"); - - recp1->uid = rdlen - 4; - recp1->addr.key.keydata = key; - recp1->addr.key.algo = algo; - recp1->addr.key.keytag = keytag; - recp1->addr.key.flags = flags; - recp1->addr.key.class = class; - } + /* key must have zone key flag set */ + if (flags & 0x100) + key = blockdata_alloc((char*)p, rdlen - 4); p = psave; + if (!ADD_RDLEN(header, p, plen, rdlen)) return STAT_INSECURE; /* bad packet */ - /* Already determined that message is OK or failed to store or ineligable - (ie no zone key flag) key. Don't attempt to validate, just loop stuffing cache */ - if (valid || !key || !(flags & 0x100)) + /* No zone key flag or malloc failure */ + if (!key) continue; for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS)) @@ -852,10 +862,70 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch } } } + blockdata_free(key); } if (valid) { + /* DNSKEY RRset determined to be OK, now cache it. */ + cache_start_insert(); + + p = skip_questions(header, plen); + + for (j = ntohs(header->ancount); j != 0; j--) + { + /* Ensure we have type, class TTL and length */ + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + if (qclass != class || qtype != T_DNSKEY || rc == 2) + { + if (ADD_RDLEN(header, p, plen, rdlen)) + continue; + + return STAT_INSECURE; /* bad packet */ + } + + if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4) + return STAT_INSECURE; /* bad packet */ + + psave = p; + + GETSHORT(flags, p); + if (*p++ != 3) + return STAT_INSECURE; + algo = *p++; + keytag = dnskey_keytag(algo, flags, p, rdlen - 4); + + /* Cache needs to known class for DNSSEC stuff */ + a.addr.dnssec.class = class; + + if ((key = blockdata_alloc((char*)p, rdlen - 4)) && + (recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))) + { + struct all_addr a; + + a.addr.keytag = keytag; + log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u"); + + recp1->uid = rdlen - 4; + recp1->addr.key.keydata = key; + recp1->addr.key.algo = algo; + recp1->addr.key.keytag = keytag; + recp1->addr.key.flags = flags; + recp1->addr.key.class = class; + } + + p = psave; + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_INSECURE; /* bad packet */ + } + /* commit cache insert. */ cache_end_insert(); return STAT_SECURE;