+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.
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
#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"
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) {
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);
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;
+}
struct iter_env;
struct config_file;
struct regional;
+struct rrset_parse;
/**
* Iterator priv structure
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.
*/
#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"
* @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;
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.
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,
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;
}
struct query_info;
struct regional;
struct module_env;
+struct iter_env;
/**
* Cleanup the passed dns message.
* 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 */
/* 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 */
--- /dev/null
+; 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