]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
private-addresses.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Thu, 4 Sep 2008 12:25:15 +0000 (12:25 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Thu, 4 Sep 2008 12:25:15 +0000 (12:25 +0000)
git-svn-id: file:///svn/unbound/trunk@1224 be551aaa-1e26-0410-a405-d3ace91eadb9

doc/Changelog
doc/unbound.conf.5.in
iterator/iter_priv.c
iterator/iter_priv.h
iterator/iter_scrub.c
iterator/iter_scrub.h
iterator/iterator.c
testdata/iter_privaddr.rpl [new file with mode: 0644]

index 44375a106487b5efa1eb1e031d9527a327a9899d..d2674ee777d431aaf392e368746b75e29aacd4b1 100644 (file)
@@ -1,3 +1,7 @@
+4 September 2008: Wouter
+       - scrubber scrubs away private addresses.
+       - test for private addresses. man page entry.
+
 3 September 2008: Wouter
        - options for 'DNS Rebinding' protection: private-address and
          private-domain.
index 11a3ec82d263c9db8ca1a0b4c2d2ed2f84526e84..49f23c189338963e414967c4a1a743ed331b55da 100644 (file)
@@ -412,6 +412,29 @@ support this. It is known that some authority servers do not support 0x20,
 and resolution will fail for them. A solution is on the TODO list.
 This feature is an experimental implementation of draft dns\-0x20.
 .TP
+.B private\-address: \fI<IP address or subnet>
+Give IPv4 of IPv6 addresses or classless subnets. These are addresses
+on your private network, and are not allowed to be returned for public
+internet names.  Any occurence of such addresses are removed from
+DNS answers. Additionally, the DNSSEC validator may mark the answers
+bogus. This protects against so-called DNS Rebinding, where a user browser
+is turned into a network proxy, allowing remote access through the browser
+to other parts of your private network.  Some names can be allowed to
+contain your private addresses, by default all the \fBlocal\-data\fR
+that you configured is allowed to, and you can specify additional
+names using \fBprivate\-domain\fR.  No private addresses are enabled
+by default.  We consider to enable this for the RFC1918 private IP
+address space by default in later releases. That would enable private 
+addresses for 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 192.254.0.0/16 
+fd00::/8 and fe80::/10, since the RFC standards say these addresses 
+should not be visible on the public internet.  Turning on 127.0.0.0/8 
+would hinder many spamblocklists as they use that.
+.TP
+.B private\-domain: \fI<domain name>
+Allow this domain, and all its subdomains to contain private addresses.
+Give multiple times to allow multiple domain names to contain private 
+addresses. Default is none.
+.TP
 .B do\-not\-query\-address: \fI<IP address>
 Do not query the given IP address. Can be IP4 or IP6. Append /num to 
 indicate a classless delegation netblock, for example like
index cb7cc186fa750b18ad7b4f18f4fefd200794af61..ccd530b874acca95972a719d60cc78b9ed6a39d2 100644 (file)
@@ -46,6 +46,7 @@
 #include "util/log.h"
 #include "util/config_file.h"
 #include "util/data/dname.h"
+#include "util/data/msgparse.h"
 #include "util/net_help.h"
 #include "util/storage/dnstree.h"
 
@@ -113,7 +114,7 @@ static int read_names(struct iter_priv* priv, struct config_file* cfg)
        int nm_labs;
        ldns_rdf* rdf;
 
-       for(p = cfg->private_address; p; p = p->next) {
+       for(p = cfg->private_domain; p; p = p->next) {
                log_assert(p->str);
                rdf = ldns_dname_new_frm_str(p->str);
                if(!rdf) {
@@ -162,13 +163,29 @@ int priv_apply_cfg(struct iter_priv* priv, struct config_file* cfg)
        return 1;
 }
 
-int priv_lookup_addr(struct iter_priv* priv, struct sockaddr_storage* addr,
+/**
+ * See if an address is blocked.
+ * @param priv: structure for address storage.
+ * @param addr: address to check
+ * @param addrlen: length of addr.
+ * @return: true if the address must not be queried. false if unlisted.
+ */
+static int 
+priv_lookup_addr(struct iter_priv* priv, struct sockaddr_storage* addr,
        socklen_t addrlen)
 {
        return addr_tree_lookup(&priv->a, addr, addrlen) != NULL;
 }
 
-int priv_lookup_name(struct iter_priv* priv, uint8_t* name, uint16_t dclass)
+/**
+ * See if a name is whitelisted.
+ * @param priv: structure for address storage.
+ * @param name: name to check.
+ * @param dclass: class to check.
+ * @return: true if the name is OK. false if unlisted.
+ */
+static int 
+priv_lookup_name(struct iter_priv* priv, uint8_t* name, uint16_t dclass)
 {
        size_t len;
        int labs = dname_count_size_labels(name, &len);
@@ -180,3 +197,48 @@ size_t priv_get_mem(struct iter_priv* priv)
        if(!priv) return 0;
        return sizeof(*priv) + regional_get_mem(priv->region);
 }
+
+int priv_rrset_bad(struct iter_priv* priv, struct rrset_parse* rrset)
+{
+       /* see if it is a private name, that is allowed to have any */
+       if(priv_lookup_name(priv, rrset->dname, ntohs(rrset->rrset_class))) {
+               return 0;
+       } else {
+               /* so its a public name, check the address */
+               struct sockaddr_storage addr;
+               socklen_t len;
+               struct rr_parse* rr;
+               if(rrset->type == LDNS_RR_TYPE_A) {
+                       struct sockaddr_in* sa = (struct sockaddr_in*)&addr;
+                       len = (socklen_t)sizeof(*sa);
+                       memset(sa, 0, len);
+                       sa->sin_family = AF_INET;
+                       sa->sin_port = (in_port_t)htons(UNBOUND_DNS_PORT);
+                       for(rr = rrset->rr_first; rr; rr = rr->next) {
+                               if(ldns_read_uint16(rr->ttl_data+4) 
+                                       != INET_SIZE)
+                                       continue;
+                               memmove(&sa->sin_addr, rr->ttl_data+4+2, 
+                                       INET_SIZE);
+                               if(priv_lookup_addr(priv, &addr, len))
+                                       return 1;
+                       }
+               } else if(rrset->type == LDNS_RR_TYPE_AAAA) {
+                       struct sockaddr_in6* sa = (struct sockaddr_in6*)&addr;
+                       len = (socklen_t)sizeof(*sa);
+                       memset(sa, 0, len);
+                       sa->sin6_family = AF_INET6;
+                       sa->sin6_port = (in_port_t)htons(UNBOUND_DNS_PORT);
+                       for(rr = rrset->rr_first; rr; rr = rr->next) {
+                               if(ldns_read_uint16(rr->ttl_data+4) 
+                                       != INET6_SIZE)
+                                       continue;
+                               memmove(&sa->sin6_addr, rr->ttl_data+4+2, 
+                                       INET6_SIZE);
+                               if(priv_lookup_addr(priv, &addr, len))
+                                       return 1;
+                       }
+               } 
+       }
+       return 0;
+}
index 9f2689e1200daec8fba43f52e6967403205f8a2b..b7177ccadc46cc5f5343a58d46f2ef1c95daa933 100644 (file)
@@ -46,6 +46,7 @@
 struct iter_env;
 struct config_file;
 struct regional;
+struct rrset_parse;
 
 /**
  * Iterator priv structure
@@ -89,23 +90,12 @@ void priv_delete(struct iter_priv* priv);
 int priv_apply_cfg(struct iter_priv* priv, struct config_file* cfg);
 
 /**
- * See if an address is blocked.
- * @param priv: structure for address storage.
- * @param addr: address to check
- * @param addrlen: length of addr.
- * @return: true if the address must not be queried. false if unlisted.
- */
-int priv_lookup_addr(struct iter_priv* priv, struct sockaddr_storage* addr,
-       socklen_t addrlen);
-
-/**
- * See if a name is whitelisted.
- * @param priv: structure for address storage.
- * @param name: name to check.
- * @param dclass: class to check.
- * @return: true if the name is OK. false if unlisted.
+ * See if rrset is bad.
+ * @param priv: structure for private address storage.
+ * @param rrset: the rrset to examine, A or AAAA.
+ * @return true if the rrset is bad and should be removed.
  */
-int priv_lookup_name(struct iter_priv* priv, uint8_t* name, uint16_t dclass);
+int priv_rrset_bad(struct iter_priv* priv, struct rrset_parse* rrset);
 
 /**
  * Get memory used by priv structure.
index 27972fd978be37e529e6a13a75875a123f3ed4c2..09abb219adf6b25f0d1e2a6cd71056a413b61138 100644 (file)
@@ -41,6 +41,8 @@
  */
 #include "config.h"
 #include "iterator/iter_scrub.h"
+#include "iterator/iterator.h"
+#include "iterator/iter_priv.h"
 #include "services/cache/rrset.h"
 #include "util/log.h"
 #include "util/net_help.h"
@@ -558,11 +560,13 @@ static int sanitize_nsec_is_overreach(struct rrset_parse* rrset,
  * @param qinfo: the question originally asked.
  * @param zonename: name of server zone.
  * @param env: module environment with config and cache.
+ * @param ie: iterator environment with private address data.
  * @return 0 on error.
  */
 static int
 scrub_sanitize(ldns_buffer* pkt, struct msg_parse* msg, 
-       struct query_info* qinfo, uint8_t* zonename, struct module_env* env)
+       struct query_info* qinfo, uint8_t* zonename, struct module_env* env,
+       struct iter_env* ie)
 {
        struct rrset_parse* rrset, *prev;
        prev = NULL;
@@ -606,6 +610,18 @@ scrub_sanitize(ldns_buffer* pkt, struct msg_parse* msg,
        prev = NULL;
        rrset = msg->rrset_first;
        while(rrset) {
+
+               /* remove private addresses */
+               if( (rrset->type == LDNS_RR_TYPE_A || 
+                       rrset->type == LDNS_RR_TYPE_AAAA) &&
+                       priv_rrset_bad(ie->priv, rrset)) {
+                       /* set servfail, so the classification becomes
+                        * THROWAWAY, instead of LAME or other unwanted */
+                       FLAGS_SET_RCODE(msg->flags, LDNS_RCODE_SERVFAIL);
+                       remove_rrset("sanitize: removing public name with "
+                               "private address", pkt, msg, prev, &rrset);
+                       continue;
+               }
                
                /* skip DNAME records -- they will always be followed by a 
                 * synthesized CNAME, which will be relevant.
@@ -653,7 +669,7 @@ scrub_sanitize(ldns_buffer* pkt, struct msg_parse* msg,
 int 
 scrub_message(ldns_buffer* pkt, struct msg_parse* msg, 
        struct query_info* qinfo, uint8_t* zonename, struct regional* region,
-       struct module_env* env)
+       struct module_env* env, struct iter_env* ie)
 {
        /* basic sanity checks */
        log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS, 
@@ -684,7 +700,7 @@ scrub_message(ldns_buffer* pkt, struct msg_parse* msg,
        if(!scrub_normalize(pkt, msg, qinfo, region))
                return 0;
        /* delete all out-of-zone information */
-       if(!scrub_sanitize(pkt, msg, qinfo, zonename, env))
+       if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie))
                return 0;
        return 1;
 }
index 7ec9b76e1feaecc769f202441e457e24a9da1cd9..8bceb0059479bb6ecadb459b8800cb0af1abe2ea 100644 (file)
@@ -46,6 +46,7 @@ struct msg_parse;
 struct query_info;
 struct regional;
 struct module_env;
+struct iter_env;
 
 /**
  * Cleanup the passed dns message.
@@ -57,10 +58,11 @@ struct module_env;
  *     Used to determine out of bailiwick information.
  * @param regional: where to allocate (new) parts of the message.
  * @param env: module environment with config settings and cache. 
+ * @param ie: iterator module environment data.
  * @return: false if the message is total waste. true if scrubbed with success.
  */
 int scrub_message(ldns_buffer* pkt, struct msg_parse* msg, 
        struct query_info* qinfo, uint8_t* zonename, struct regional* regional,
-       struct module_env* env);
+       struct module_env* env, struct iter_env* ie);
 
 #endif /* ITERATOR_ITER_SCRUB_H */
index de440c72114726e43c9a4c5f993b2362ee972121..d63412261fab46118973f8999c0f2968cdc22113 100644 (file)
@@ -1773,7 +1773,7 @@ process_response(struct module_qstate* qstate, struct iter_qstate* iq,
 
        /* normalize and sanitize: easy to delete items from linked lists */
        if(!scrub_message(pkt, prs, &iq->qchase, iq->dp->name, 
-               qstate->env->scratch, qstate->env))
+               qstate->env->scratch, qstate->env, ie))
                goto handle_it;
 
        /* allocate response dns_msg in region */
diff --git a/testdata/iter_privaddr.rpl b/testdata/iter_privaddr.rpl
new file mode 100644 (file)
index 0000000..603f5b1
--- /dev/null
@@ -0,0 +1,223 @@
+; config options
+server:
+       private-address: 10.0.0.0/8
+       private-address: 172.16.0.0/12
+       private-address: 192.168.0.0/16
+       private-address: 192.254.0.0/16
+       private-address: fd00::/8
+       private-address: fe80::/10
+
+       private-domain: "example.net"
+
+stub-zone:
+       name: "."
+       stub-addr: 193.0.14.129         # K.ROOT-SERVERS.NET.
+
+CONFIG_END
+
+SCENARIO_BEGIN Test iterator scrubber with private addresses.
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+       ADDRESS 193.0.14.129 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS        K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET.    IN      A       193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+com.   IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    IN      A       192.5.6.30
+ENTRY_END
+
+; root server authoritative for example.net too.
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+mail.example.net. IN A
+SECTION ANSWER
+mail.example.net. IN A 10.20.30.40
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+       ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com.   IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    IN      A       192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 100
+       ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A  192.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+mail.example.com. IN AAAA
+SECTION ANSWER
+mail.example.com. IN AAAA fe80::15
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+foo.example.com. IN A
+SECTION ANSWER
+foo.example.com. IN A  10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+ENTRY_END
+RANGE_END
+
+; public address is not scrubbed
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; recursion happens here.
+STEP 2 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A  192.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+ENTRY_END
+
+; IPv4 address is scrubbed
+STEP 3 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+foo.example.com. IN A
+ENTRY_END
+
+; recursion happens here.
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA SERVFAIL
+SECTION QUESTION
+foo.example.com. IN A
+SECTION ANSWER
+; scrubbed away
+;foo.example.com. IN A 10.20.30.40
+ENTRY_END
+
+; IPv6 address is scrubbed
+STEP 20 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+mail.example.com. IN AAAA
+ENTRY_END
+
+STEP 30 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA SERVFAIL
+SECTION QUESTION
+mail.example.com. IN AAAA
+SECTION ANSWER
+ENTRY_END
+
+; allowed domain is not scrubbed.
+STEP 40 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+mail.example.net. IN A
+ENTRY_END
+
+STEP 50 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+mail.example.net. IN A
+SECTION ANSWER
+mail.example.net. IN A 10.20.30.40
+ENTRY_END
+
+SCENARIO_END