[master] ECS authoritative support

3936.	[func]		Added authoritative support for the EDNS Client
			Subnet (ECS) option.

			ACLs can now include "ecs" elements which specify
			an address or network prefix; if an ECS option is
			included in a DNS query, then the address encoded
			in the option will be matched against "ecs" ACL
			elements.

			Also, if an ECS address is included in a query,
			then it will be used instead of the client source
			address when matching "geoip" ACL elements.  This
			behavior can be overridden with "geoip-use-ecs no;".

			When "ecs" or "geoip" ACL elements are used to
			select a view for a query, the response will include
			an ECS option to indicate which client network the
			answer is valid for.

			(Thanks to Vincent Bernat.) [RT #36781]
This commit is contained in:
Evan Hunt
2014-08-28 22:05:57 -07:00
parent 180319f572
commit d46855caed
35 changed files with 1155 additions and 357 deletions

View File

@@ -15,8 +15,6 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: acl.c,v 1.55 2011/06/17 23:47:49 tbox Exp $ */
/*! \file */
#include <config.h>
@@ -194,10 +192,25 @@ dns_acl_match(const isc_netaddr_t *reqaddr,
int *match,
const dns_aclelement_t **matchelt)
{
isc_uint16_t bitlen, family;
return (dns_acl_match2(reqaddr, reqsigner, NULL, 0, NULL, acl, env,
match, matchelt));
}
isc_result_t
dns_acl_match2(const isc_netaddr_t *reqaddr,
const dns_name_t *reqsigner,
const isc_netaddr_t *ecs,
isc_uint8_t ecslen,
isc_uint8_t *scope,
const dns_acl_t *acl,
const dns_aclenv_t *env,
int *match,
const dns_aclelement_t **matchelt)
{
isc_uint16_t bitlen;
isc_prefix_t pfx;
isc_radix_node_t *node = NULL;
const isc_netaddr_t *addr;
const isc_netaddr_t *addr = reqaddr;
isc_netaddr_t v4addr;
isc_result_t result;
int match_num = -1;
@@ -205,20 +218,19 @@ dns_acl_match(const isc_netaddr_t *reqaddr,
REQUIRE(reqaddr != NULL);
REQUIRE(matchelt == NULL || *matchelt == NULL);
REQUIRE(ecs != NULL || scope == NULL);
if (env == NULL || env->match_mapped == ISC_FALSE ||
reqaddr->family != AF_INET6 ||
!IN6_IS_ADDR_V4MAPPED(&reqaddr->type.in6))
addr = reqaddr;
else {
isc_netaddr_fromv4mapped(&v4addr, reqaddr);
if (env != NULL && env->match_mapped &&
addr->family == AF_INET6 &&
IN6_IS_ADDR_V4MAPPED(&addr->type.in6))
{
isc_netaddr_fromv4mapped(&v4addr, addr);
addr = &v4addr;
}
/* Always match with host addresses. */
family = addr->family;
bitlen = family == AF_INET6 ? 128 : 32;
NETADDR_TO_PREFIX_T(addr, pfx, bitlen);
bitlen = (addr->family == AF_INET6) ? 128 : 32;
NETADDR_TO_PREFIX_T(addr, pfx, bitlen, ISC_FALSE);
/* Assume no match. */
*match = 0;
@@ -228,37 +240,75 @@ dns_acl_match(const isc_netaddr_t *reqaddr,
/* Found a match. */
if (result == ISC_R_SUCCESS && node != NULL) {
match_num = node->node_num[ISC_IS6(family)];
if (*(isc_boolean_t *) node->data[ISC_IS6(family)] == ISC_TRUE)
int off = ISC_RADIX_OFF(&pfx);
match_num = node->node_num[off];
if (*(isc_boolean_t *) node->data[off])
*match = match_num;
else
*match = -match_num;
}
isc_refcount_destroy(&pfx.refcount);
/*
* If ecs is not NULL, we search the radix tree again to
* see if we find a better match on an ECS node
*/
if (ecs != NULL) {
node = NULL;
addr = ecs;
if (env != NULL && env->match_mapped &&
addr->family == AF_INET6 &&
IN6_IS_ADDR_V4MAPPED(&addr->type.in6))
{
isc_netaddr_fromv4mapped(&v4addr, addr);
addr = &v4addr;
}
NETADDR_TO_PREFIX_T(addr, pfx, ecslen, ISC_TRUE);
result = isc_radix_search(acl->iptable->radix, &node, &pfx);
if (result == ISC_R_SUCCESS && node != NULL) {
int off = ISC_RADIX_OFF(&pfx);
if (match_num == -1 ||
node->node_num[off] < match_num)
{
match_num = node->node_num[off];
if (scope != NULL)
*scope = node->bit;
if (*(isc_boolean_t *) node->data[off])
*match = match_num;
else
*match = -match_num;
}
}
isc_refcount_destroy(&pfx.refcount);
}
/* Now search non-radix elements for a match with a lower node_num. */
for (i = 0; i < acl->length; i++) {
dns_aclelement_t *e = &acl->elements[i];
/* Already found a better match? */
if (match_num != -1 && match_num < e->node_num) {
isc_refcount_destroy(&pfx.refcount);
return (ISC_R_SUCCESS);
break;
}
if (dns_aclelement_match(reqaddr, reqsigner,
e, env, matchelt)) {
if (dns_aclelement_match2(reqaddr, reqsigner, ecs, ecslen,
scope, e, env, matchelt))
{
if (match_num == -1 || e->node_num < match_num) {
if (e->negative == ISC_TRUE)
if (e->negative)
*match = -e->node_num;
else
*match = e->node_num;
}
isc_refcount_destroy(&pfx.refcount);
return (ISC_R_SUCCESS);
break;
}
}
isc_refcount_destroy(&pfx.refcount);
return (ISC_R_SUCCESS);
}
@@ -349,7 +399,7 @@ dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, isc_boolean_t pos)
#endif
/* reverse sense of positives if this is a negative acl */
if (!pos && source->elements[i].negative == ISC_FALSE) {
if (!pos && !source->elements[i].negative) {
dest->elements[nelem + i].negative = ISC_TRUE;
} else {
dest->elements[nelem + i].negative =
@@ -386,10 +436,29 @@ dns_aclelement_match(const isc_netaddr_t *reqaddr,
const dns_aclelement_t *e,
const dns_aclenv_t *env,
const dns_aclelement_t **matchelt)
{
return (dns_aclelement_match2(reqaddr, reqsigner, NULL, 0, NULL,
e, env, matchelt));
}
isc_boolean_t
dns_aclelement_match2(const isc_netaddr_t *reqaddr,
const dns_name_t *reqsigner,
const isc_netaddr_t *ecs,
isc_uint8_t ecslen,
isc_uint8_t *scope,
const dns_aclelement_t *e,
const dns_aclenv_t *env,
const dns_aclelement_t **matchelt)
{
dns_acl_t *inner = NULL;
int indirectmatch;
isc_result_t result;
#ifdef HAVE_GEOIP
const isc_netaddr_t *addr = NULL;
#endif
REQUIRE(ecs != NULL || scope == NULL);
switch (e->type) {
case dns_aclelementtype_keyname:
@@ -421,15 +490,17 @@ dns_aclelement_match(const isc_netaddr_t *reqaddr,
case dns_aclelementtype_geoip:
if (env == NULL || env->geoip == NULL)
return (ISC_FALSE);
return (dns_geoip_match(reqaddr, env->geoip, &e->geoip_elem));
addr = (env->geoip_use_ecs && ecs != NULL) ? ecs : reqaddr;
return (dns_geoip_match(addr, scope, env->geoip,
&e->geoip_elem));
#endif
default:
/* Should be impossible. */
INSIST(0);
}
result = dns_acl_match(reqaddr, reqsigner, inner, env,
&indirectmatch, matchelt);
result = dns_acl_match2(reqaddr, reqsigner, ecs, ecslen, scope,
inner, env, &indirectmatch, matchelt);
INSIST(result == ISC_R_SUCCESS);
/*
@@ -438,7 +509,6 @@ dns_aclelement_match(const isc_netaddr_t *reqaddr,
* surprise positive match through double negation.
* XXXDCL this should be documented.
*/
if (indirectmatch > 0) {
if (matchelt != NULL)
*matchelt = e;
@@ -449,7 +519,6 @@ dns_aclelement_match(const isc_netaddr_t *reqaddr,
* A negative indirect match may have set *matchelt, but we don't
* want it set when we return.
*/
if (matchelt != NULL)
*matchelt = NULL;
@@ -519,17 +588,15 @@ initialize_action(void) {
*/
static void
is_insecure(isc_prefix_t *prefix, void **data) {
isc_boolean_t secure;
int bitlen, family;
int bitlen, family, off;
bitlen = prefix->bitlen;
family = prefix->family;
/* Negated entries are always secure. */
secure = * (isc_boolean_t *)data[ISC_IS6(family)];
if (!secure) {
off = ISC_RADIX_OFF(prefix);
if (data[off] != NULL && * (isc_boolean_t *) data[off])
return;
}
/* If loopback prefix found, return */
switch (family) {
@@ -628,6 +695,7 @@ dns_aclenv_init(isc_mem_t *mctx, dns_aclenv_t *env) {
env->match_mapped = ISC_FALSE;
#ifdef HAVE_GEOIP
env->geoip = NULL;
env->geoip_use_ecs = ISC_FALSE;
#endif
return (ISC_R_SUCCESS);
@@ -644,6 +712,9 @@ dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s) {
dns_acl_detach(&t->localnets);
dns_acl_attach(s->localnets, &t->localnets);
t->match_mapped = s->match_mapped;
#ifdef HAVE_GEOIP
t->geoip_use_ecs = s->geoip_use_ecs;
#endif
}
void