diff --git a/CHANGES b/CHANGES
index e7c9c89b54..47f40b53ee 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,24 @@
+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]
+
3935. [bug] "geoip asnum" ACL elements would not match unless
the full organization name was specified. They
can now match against the AS number alone (e.g.,
diff --git a/README b/README
index 8298ae1f88..23ef8a8328 100644
--- a/README
+++ b/README
@@ -56,6 +56,12 @@ BIND 9.11.0
BIND 9.11.0 includes a number of changes from BIND 9.10 and earlier
releases. New features include:
+ - The EDNS Client Subnet (ECS) option is now supported for
+ authoritative servers; if a query contains an ECS option
+ then ACLs containing "geoip" or "ecs" elements can match
+ against the the address encoded in the option. This can be
+ used to select a view for a query, so that different answers
+ can be provided depending on the client network.
- The EDNS EXPIRE option has been implemented on the client
side, allowing a slave server to set the expiration timer
correctly when transferring zone data from another slave
diff --git a/bin/named/client.c b/bin/named/client.c
index 68d14a942d..f2fe82fa78 100644
--- a/bin/named/client.c
+++ b/bin/named/client.c
@@ -122,6 +122,7 @@
#endif
#define SIT_SIZE 24U /* 8 + 4 + 4 + 8 */
+#define ECS_SIZE 20U /* 2 + 1 + 1 + [0..16] */
/*% nameserver client manager structure */
struct ns_clientmgr {
@@ -244,7 +245,8 @@ static void ns_client_dumpmessage(ns_client_t *client, const char *reason);
static isc_result_t get_client(ns_clientmgr_t *manager, ns_interface_t *ifp,
dns_dispatch_t *disp, isc_boolean_t tcp);
static inline isc_boolean_t
-allowed(isc_netaddr_t *addr, dns_name_t *signer, dns_acl_t *acl);
+allowed(isc_netaddr_t *addr, dns_name_t *signer, isc_netaddr_t *ecs_addr,
+ isc_uint8_t ecs_addrlen, isc_uint8_t *ecs_scope, dns_acl_t *acl);
#ifdef ISC_PLATFORM_USESIT
static void compute_sit(ns_client_t *client, isc_uint32_t when,
isc_uint32_t nonce, isc_buffer_t *buf);
@@ -1042,7 +1044,8 @@ client_send(ns_client_t *client) {
if (client->message->tsigkey != NULL)
name = &client->message->tsigkey->name;
if (client->view->nocasecompress == NULL ||
- !allowed(&netaddr, name, client->view->nocasecompress))
+ !allowed(&netaddr, name, NULL, 0, NULL,
+ client->view->nocasecompress))
{
dns_compress_setsensitive(&cctx, ISC_TRUE);
}
@@ -1381,6 +1384,7 @@ isc_result_t
ns_client_addopt(ns_client_t *client, dns_message_t *message,
dns_rdataset_t **opt)
{
+ unsigned char ecs[ECS_SIZE];
char nsid[BUFSIZ], *nsidp;
#ifdef ISC_PLATFORM_USESIT
unsigned char sit[SIT_SIZE];
@@ -1459,6 +1463,38 @@ ns_client_addopt(ns_client_t *client, dns_message_t *message,
ednsopts[count].value = expire;
count++;
}
+ if (((client->attributes & NS_CLIENTATTR_HAVEECS) != 0) &&
+ (client->ecs_addr.family == AF_INET ||
+ client->ecs_addr.family == AF_INET6))
+ {
+ int i, addrbytes = (client->ecs_addrlen + 7) / 8;
+ isc_uint8_t *paddr;
+ isc_buffer_t buf;
+
+ /* Add client subnet option. */
+ isc_buffer_init(&buf, ecs, sizeof(ecs));
+ if (client->ecs_addr.family == AF_INET)
+ isc_buffer_putuint16(&buf, 1);
+ else
+ isc_buffer_putuint16(&buf, 2);
+ isc_buffer_putuint8(&buf, client->ecs_addrlen);
+ isc_buffer_putuint8(&buf, client->ecs_scope);
+
+ paddr = (isc_uint8_t *) &client->ecs_addr.type;
+ for (i = 0; i < addrbytes; i++) {
+ unsigned char uc;
+ uc = paddr[i];
+ if (i == addrbytes - 1 &&
+ ((client->ecs_addrlen % 8) != 0))
+ uc &= (1U << (8 - (client->ecs_addrlen % 8)));
+ isc_buffer_putuint8(&buf, uc);
+ }
+
+ ednsopts[count].code = DNS_OPT_CLIENT_SUBNET;
+ ednsopts[count].length = addrbytes + 4;
+ ednsopts[count].value = ecs;
+ count++;
+ }
result = dns_message_buildopt(message, opt, 0, udpsize, flags,
ednsopts, count);
@@ -1466,14 +1502,17 @@ ns_client_addopt(ns_client_t *client, dns_message_t *message,
}
static inline isc_boolean_t
-allowed(isc_netaddr_t *addr, dns_name_t *signer, dns_acl_t *acl) {
+allowed(isc_netaddr_t *addr, dns_name_t *signer,
+ isc_netaddr_t *ecs_addr, isc_uint8_t ecs_addrlen,
+ isc_uint8_t *ecs_scope, dns_acl_t *acl)
+{
int match;
isc_result_t result;
if (acl == NULL)
return (ISC_TRUE);
- result = dns_acl_match(addr, signer, acl, &ns_g_server->aclenv,
- &match, NULL);
+ result = dns_acl_match2(addr, signer, ecs_addr, ecs_addrlen, ecs_scope,
+ acl, &ns_g_server->aclenv, &match, NULL);
if (result == ISC_R_SUCCESS && match > 0)
return (ISC_TRUE);
return (ISC_FALSE);
@@ -1536,8 +1575,10 @@ ns_client_isself(dns_view_t *myview, dns_tsigkey_t *mykey,
tsig = dns_tsigkey_identity(mykey);
}
- if (allowed(&netsrc, tsig, view->matchclients) &&
- allowed(&netdst, tsig, view->matchdestinations))
+ if (allowed(&netsrc, tsig, NULL, 0, NULL,
+ view->matchclients) &&
+ allowed(&netdst, tsig, NULL, 0, NULL,
+ view->matchdestinations))
break;
}
return (ISC_TF(view == myview));
@@ -1718,6 +1759,81 @@ process_sit(ns_client_t *client, isc_buffer_t *buf, size_t optlen) {
}
#endif
+static isc_result_t
+process_ecs(ns_client_t *client, isc_buffer_t *buf, size_t optlen) {
+ isc_uint16_t family;
+ isc_uint8_t addrlen, addrbytes, scope, *paddr;
+ isc_netaddr_t caddr;
+ int i;
+
+ if (optlen < 4) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client subnet option too short");
+ return (DNS_R_FORMERR);
+ }
+
+ family = isc_buffer_getuint16(buf);
+ addrlen = isc_buffer_getuint8(buf);
+ scope = isc_buffer_getuint8(buf);
+ optlen -= 4;
+
+ if (scope != 0U) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client subnet option: invalid scope");
+ return (DNS_R_FORMERR);
+ }
+
+ memset(&caddr, 0, sizeof(caddr));
+ switch (family) {
+ case 1:
+ if (addrlen > 32U)
+ goto invalid_length;
+ caddr.family = AF_INET;
+ break;
+ case 2:
+ if (addrlen > 128U) {
+ invalid_length:
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client subnet option: invalid "
+ "address length (%u) for %s",
+ addrlen, family == 1 ? "IPv4" : "IPv6");
+ return (DNS_R_FORMERR);
+ }
+ caddr.family = AF_INET6;
+ break;
+ default:
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client subnet option: invalid family");
+ return (DNS_R_FORMERR);
+ }
+
+ addrbytes = (addrlen + 7) / 8;
+ if (isc_buffer_remaininglength(buf) < addrbytes) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client subnet option: address too short");
+ return (DNS_R_FORMERR);
+ }
+
+ paddr = (isc_uint8_t *) &caddr.type;
+ for (i = 0; i < addrbytes; i++) {
+ paddr[i] = isc_buffer_getuint8(buf);
+ optlen--;
+ }
+
+ memmove(&client->ecs_addr, &caddr, sizeof(caddr));
+ client->ecs_addrlen = addrlen;
+ client->ecs_scope = 0;
+ client->attributes |= NS_CLIENTATTR_HAVEECS;
+
+ isc_buffer_forward(buf, optlen);
+ return (ISC_R_SUCCESS);
+}
+
static isc_result_t
process_opt(ns_client_t *client, dns_rdataset_t *opt) {
dns_rdata_t rdata;
@@ -1788,6 +1904,15 @@ process_opt(ns_client_t *client, dns_rdataset_t *opt) {
client->attributes |= NS_CLIENTATTR_WANTEXPIRE;
isc_buffer_forward(&optbuf, optlen);
break;
+ case DNS_OPT_CLIENT_SUBNET:
+ result = process_ecs(client, &optbuf, optlen);
+ if (result != ISC_R_SUCCESS) {
+ ns_client_error(client, result);
+ goto cleanup;
+ }
+ isc_stats_increment(ns_g_server->nsstats,
+ dns_nsstatscounter_ecsopt);
+ break;
default:
isc_stats_increment(ns_g_server->nsstats,
dns_nsstatscounter_otheropt);
@@ -1925,7 +2050,6 @@ client_request(isc_task_t *task, isc_event_t *event) {
* client_newconn.
*/
if (!TCP_CLIENT(client)) {
-
if (ns_g_server->blackholeacl != NULL &&
dns_acl_match(&netaddr, NULL, ns_g_server->blackholeacl,
&ns_g_server->aclenv,
@@ -2033,6 +2157,10 @@ client_request(isc_task_t *task, isc_event_t *event) {
opt = NULL;
else
opt = dns_message_getopt(client->message);
+
+ client->ecs_addrlen = 0;
+ client->ecs_scope = 0;
+
if (opt != NULL) {
/*
* Are we dropping all EDNS queries?
@@ -2117,17 +2245,29 @@ client_request(isc_task_t *task, isc_event_t *event) {
client->message->rdclass == dns_rdataclass_any)
{
dns_name_t *tsig = NULL;
+ isc_netaddr_t *addr = NULL;
+ isc_uint8_t *scope = NULL;
sigresult = dns_message_rechecksig(client->message,
view);
- if (sigresult == ISC_R_SUCCESS)
- tsig = dns_tsigkey_identity(client->message->tsigkey);
+ if (sigresult == ISC_R_SUCCESS) {
+ dns_tsigkey_t *tsigkey;
- if (allowed(&netaddr, tsig, view->matchclients) &&
- allowed(&client->destaddr, tsig,
- view->matchdestinations) &&
- !((client->message->flags & DNS_MESSAGEFLAG_RD)
- == 0 && view->matchrecursiveonly))
+ tsigkey = client->message->tsigkey;
+ tsig = dns_tsigkey_identity(tsigkey);
+ }
+
+ if ((client->attributes & NS_CLIENTATTR_HAVEECS) != 0) {
+ addr = &client->ecs_addr;
+ scope = &client->ecs_scope;
+ }
+
+ if (allowed(&netaddr, tsig, addr, client->ecs_addrlen,
+ scope, view->matchclients) &&
+ allowed(&client->destaddr, tsig, NULL,
+ 0, NULL, view->matchdestinations) &&
+ !(view->matchrecursiveonly &&
+ (client->message->flags & DNS_MESSAGEFLAG_RD) == 0))
{
dns_view_attach(view, &client->view);
break;
@@ -2519,6 +2659,8 @@ client_create(ns_clientmgr_t *manager, ns_client_t **clientp) {
client->recursionquota = NULL;
client->interface = NULL;
client->peeraddr_valid = ISC_FALSE;
+ client->ecs_addrlen = 0;
+ client->ecs_scope = 0;
#ifdef ALLOW_FILTER_AAAA
client->filter_aaaa = dns_aaaa_ok;
#endif
@@ -3055,6 +3197,8 @@ ns_client_checkaclsilent(ns_client_t *client, isc_netaddr_t *netaddr,
{
isc_result_t result;
isc_netaddr_t tmpnetaddr;
+ isc_netaddr_t *ecs_addr = NULL;
+ isc_uint8_t ecs_addrlen = 0;
int match;
if (acl == NULL) {
@@ -3069,11 +3213,18 @@ ns_client_checkaclsilent(ns_client_t *client, isc_netaddr_t *netaddr,
netaddr = &tmpnetaddr;
}
- result = dns_acl_match(netaddr, client->signer, acl,
- &ns_g_server->aclenv, &match, NULL);
+ if ((client->attributes & NS_CLIENTATTR_HAVEECS) != 0) {
+ ecs_addr = &client->ecs_addr;
+ ecs_addrlen = client->ecs_addrlen;
+ }
+
+ result = dns_acl_match2(netaddr, client->signer,
+ ecs_addr, ecs_addrlen, NULL, acl,
+ &ns_g_server->aclenv, &match, NULL);
if (result != ISC_R_SUCCESS)
goto deny; /* Internal error, already logged. */
+
if (match > 0)
goto allow;
goto deny; /* Negative match or no match. */
diff --git a/bin/named/config.c b/bin/named/config.c
index f7647e76f7..dfd2852fd8 100644
--- a/bin/named/config.c
+++ b/bin/named/config.c
@@ -177,6 +177,11 @@ options {\n\
nsec3-test-zone no;\n\
allow-new-zones no;\n\
"
+#ifdef HAVE_GEOIP
+"\
+ geoip-use-ecs yes;\n\
+"
+#endif
#ifdef ALLOW_FILTER_AAAA
" filter-aaaa-on-v4 no;\n\
filter-aaaa-on-v6 no;\n\
diff --git a/bin/named/include/named/client.h b/bin/named/include/named/client.h
index c0c3171dc3..48ee578c88 100644
--- a/bin/named/include/named/client.h
+++ b/bin/named/include/named/client.h
@@ -137,9 +137,15 @@ struct ns_client {
isc_quota_t *tcpquota;
isc_quota_t *recursionquota;
ns_interface_t *interface;
+
isc_sockaddr_t peeraddr;
isc_boolean_t peeraddr_valid;
isc_netaddr_t destaddr;
+
+ isc_netaddr_t ecs_addr; /*%< EDNS client subnet */
+ isc_uint8_t ecs_addrlen;
+ isc_uint8_t ecs_scope;
+
struct in6_pktinfo pktinfo;
isc_dscp_t dscp;
isc_event_t ctlevent;
@@ -187,6 +193,7 @@ typedef ISC_LIST(ns_client_t) client_list_t;
#define NS_CLIENTATTR_WANTEXPIRE 0x0800 /*%< return seconds to expire */
#define NS_CLIENTATTR_HAVEEXPIRE 0x1000 /*%< return seconds to expire */
#define NS_CLIENTATTR_WANTOPT 0x2000 /*%< add opt to reply */
+#define NS_CLIENTATTR_HAVEECS 0x4000 /*%< sent an ECS option */
extern unsigned int ns_client_requests;
diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h
index e1d1db275b..0b241b7471 100644
--- a/bin/named/include/named/server.h
+++ b/bin/named/include/named/server.h
@@ -182,18 +182,19 @@ enum {
dns_nsstatscounter_nsidopt = 43,
dns_nsstatscounter_expireopt = 44,
dns_nsstatscounter_otheropt = 45,
+ dns_nsstatscounter_ecsopt = 46,
#ifdef ISC_PLATFORM_USESIT
- dns_nsstatscounter_sitopt = 46,
- dns_nsstatscounter_sitbadsize = 47,
- dns_nsstatscounter_sitbadtime = 48,
- dns_nsstatscounter_sitnomatch = 49,
- dns_nsstatscounter_sitmatch = 50,
- dns_nsstatscounter_sitnew = 51,
+ dns_nsstatscounter_sitopt = 47,
+ dns_nsstatscounter_sitbadsize = 48,
+ dns_nsstatscounter_sitbadtime = 49,
+ dns_nsstatscounter_sitnomatch = 50,
+ dns_nsstatscounter_sitmatch = 51,
+ dns_nsstatscounter_sitnew = 52,
- dns_nsstatscounter_max = 52
+ dns_nsstatscounter_max = 53
#else
- dns_nsstatscounter_max = 46
+ dns_nsstatscounter_max = 47
#endif
};
diff --git a/bin/named/server.c b/bin/named/server.c
index 72bbdd285f..eb7d02a0de 100644
--- a/bin/named/server.c
+++ b/bin/named/server.c
@@ -4684,6 +4684,9 @@ directory_callback(const char *clausename, const cfg_obj_t *obj, void *arg) {
static void
scan_interfaces(ns_server_t *server, isc_boolean_t verbose) {
isc_boolean_t match_mapped = server->aclenv.match_mapped;
+#ifdef HAVE_GEOIP
+ isc_boolean_t use_ecs = server->aclenv.geoip_use_ecs;
+#endif
ns_interfacemgr_scan(server->interfacemgr, verbose);
/*
@@ -4694,6 +4697,9 @@ scan_interfaces(ns_server_t *server, isc_boolean_t verbose) {
ns_interfacemgr_getaclenv(server->interfacemgr));
server->aclenv.match_mapped = match_mapped;
+#ifdef HAVE_GEOIP
+ server->aclenv.geoip_use_ecs = use_ecs;
+#endif
}
static isc_result_t
@@ -5554,6 +5560,11 @@ load_configuration(const char *filename, ns_server_t *server,
} else
ns_geoip_load(NULL);
ns_g_aclconfctx->geoip = ns_g_geoip;
+
+ obj = NULL;
+ result = ns_config_get(maps, "geoip-use-ecs", &obj);
+ INSIST(result == ISC_R_SUCCESS);
+ ns_g_server->aclenv.geoip_use_ecs = cfg_obj_asboolean(obj);
#endif /* HAVE_GEOIP */
/*
diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c
index 6e2fe3a3c3..f0584c3ac8 100644
--- a/bin/named/statschannel.c
+++ b/bin/named/statschannel.c
@@ -242,6 +242,7 @@ init_desc(void) {
"SitNoMatch");
SET_NSSTATDESC(sitmatch, "source identity token - match", "SitMatch");
#endif
+ SET_NSSTATDESC(ecsopt, "EDNS client subnet option recieved", "ECSOpt");
INSIST(i == dns_nsstatscounter_max);
/* Initialize resolver statistics */
diff --git a/bin/tests/system/acl/ns2/named6.conf b/bin/tests/system/acl/ns2/named6.conf
new file mode 100644
index 0000000000..1e384fb0d8
--- /dev/null
+++ b/bin/tests/system/acl/ns2/named6.conf
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+controls { /* empty */ };
+
+options {
+ query-source address 10.53.0.2;
+ notify-source 10.53.0.2;
+ transfer-source 10.53.0.2;
+ port 5300;
+ pid-file "named.pid";
+ listen-on { 10.53.0.2; };
+ listen-on-v6 { none; };
+ recursion no;
+ notify yes;
+ ixfr-from-differences yes;
+ check-integrity no;
+ allow-query-on { 10.53.0.2; };
+};
+
+include "../../common/controls.conf";
+
+zone "." {
+ type hint;
+ file "../../common/root.hint";
+};
+
+zone "example" {
+ type master;
+ file "example.db";
+};
+
+zone "tsigzone" {
+ type master;
+ file "tsigzone.db";
+ allow-transfer { ecs 10.53/16; !10/8; };
+};
diff --git a/bin/tests/system/acl/ns2/named7.conf b/bin/tests/system/acl/ns2/named7.conf
new file mode 100644
index 0000000000..1f1c9f333a
--- /dev/null
+++ b/bin/tests/system/acl/ns2/named7.conf
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+controls { /* empty */ };
+
+options {
+ query-source address 10.53.0.2;
+ notify-source 10.53.0.2;
+ transfer-source 10.53.0.2;
+ port 5300;
+ pid-file "named.pid";
+ listen-on { 10.53.0.2; };
+ listen-on-v6 { none; };
+ recursion no;
+ notify yes;
+ ixfr-from-differences yes;
+ check-integrity no;
+ allow-query-on { 10.53.0.2; };
+};
+
+include "../../common/controls.conf";
+
+view one {
+ match-clients { ecs 192.0.2/24; };
+
+ zone "." {
+ type hint;
+ file "../../common/root.hint";
+ };
+
+ zone "example" {
+ type master;
+ file "example.db";
+ };
+};
+
+view two {
+ zone "." {
+ type hint;
+ file "../../common/root.hint";
+ };
+
+ zone "example" {
+ type master;
+ file "example.db";
+ };
+};
diff --git a/bin/tests/system/acl/tests.sh b/bin/tests/system/acl/tests.sh
index b74c0c9f9f..27efd5cde8 100644
--- a/bin/tests/system/acl/tests.sh
+++ b/bin/tests/system/acl/tests.sh
@@ -150,5 +150,35 @@ $DIG +tcp soa example. \
@10.53.0.2 -b 10.53.0.3 -p 5300 > dig.out.${t}
grep "status: NOERROR" dig.out.${t} > /dev/null 2>&1 || { echo "I:test $t failed" ; status=1; }
+echo "I:testing EDNS client-subnet ACL processing"
+cp -f ns2/named6.conf ns2/named.conf
+$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 reload 2>&1 | sed 's/^/I:ns2 /'
+sleep 5
+
+# should fail
+t=`expr $t + 1`
+$DIG $DIGOPTS tsigzone. \
+ @10.53.0.2 -b 10.53.0.2 axfr -p 5300 > dig.out.${t}
+grep "^;" dig.out.${t} > /dev/null 2>&1 || { echo "I:test $t failed" ; status=1; }
+
+# should succeed
+t=`expr $t + 1`
+$DIG $DIGOPTS tsigzone. \
+ @10.53.0.2 -b 10.53.0.2 +subnet="10.53.0/24" axfr -p 5300 > dig.out.${t}
+grep "^;" dig.out.${t} > /dev/null 2>&1 && { echo "I:test $t failed" ; status=1; }
+
+echo "I:testing EDNS client-subnet response scope"
+cp -f ns2/named7.conf ns2/named.conf
+$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 reload 2>&1 | sed 's/^/I:ns2 /'
+sleep 5
+
+t=`expr $t + 1`
+$DIG example. soa @10.53.0.2 +subnet="10.53.0.1/32" -p 5300 > dig.out.${t}
+grep "CLIENT-SUBNET.*10.53.0.1/32/0" dig.out.${t} > /dev/null || { echo "I:test $t failed" ; status=1; }
+
+t=`expr $t + 1`
+$DIG example. soa @10.53.0.2 +subnet="192.0.2.128/32" -p 5300 > dig.out.${t}
+grep "CLIENT-SUBNET.*192.0.2.128/32/24" dig.out.${t} > /dev/null || { echo "I:test $t failed" ; status=1; }
+
echo "I:exit status: $status"
exit $status
diff --git a/bin/tests/system/geoip/clean.sh b/bin/tests/system/geoip/clean.sh
index 22d5e74466..6fa60e3e93 100644
--- a/bin/tests/system/geoip/clean.sh
+++ b/bin/tests/system/geoip/clean.sh
@@ -15,5 +15,5 @@
# PERFORMANCE OF THIS SOFTWARE.
rm -f ns2/named.conf
-rm -f ns2/example[1234567].db
+rm -f ns2/example*.db
rm -f dig.out.* rndc.out.*
diff --git a/bin/tests/system/geoip/data/GeoIP.csv b/bin/tests/system/geoip/data/GeoIP.csv
index f158a5b494..8e718540df 100644
--- a/bin/tests/system/geoip/data/GeoIP.csv
+++ b/bin/tests/system/geoip/data/GeoIP.csv
@@ -5,3 +5,4 @@
10.53.0.5/32 CL
10.53.0.6/32 DE
10.53.0.7/32 EH
+192.0.2/24 O1
diff --git a/bin/tests/system/geoip/data/GeoIP.dat b/bin/tests/system/geoip/data/GeoIP.dat
index 027c1757c1..345092f371 100644
Binary files a/bin/tests/system/geoip/data/GeoIP.dat and b/bin/tests/system/geoip/data/GeoIP.dat differ
diff --git a/bin/tests/system/geoip/data/README b/bin/tests/system/geoip/data/README
index 47a6858f59..cea325b321 100644
--- a/bin/tests/system/geoip/data/README
+++ b/bin/tests/system/geoip/data/README
@@ -18,8 +18,8 @@ GeoIPDoain.dat: Domain Name
GeoIPASNum.dat: AS Number
GeoIPNetSpeed.dat: Net Speed
-GeoIP.dat can also be generated using the open source 'geoip-csv-to-dat'
-utility:
+GeoIP.dat can also be egenerated using the open source 'geoip-csv-to-dat'
+utility (also known in some packages as "geoip-generator"):
$ geoip-csv-to-dat -i "BIND9 geoip test data v1" -o GeoIP.dat << EOF
"10.53.0.1","10.53.0.1","171245569","171245569","AU","Australia"
@@ -29,4 +29,5 @@ $ geoip-csv-to-dat -i "BIND9 geoip test data v1" -o GeoIP.dat << EOF
"10.53.0.5","10.53.0.5","171245573","171245573","CL","Chile"
"10.53.0.6","10.53.0.6","171245574","171245574","DE","Germany"
"10.53.0.7","10.53.0.7","171245575","171245575","EH","Western Sahara"
+"192.0.2.0","192.0.2.255","3221225984","3221226239","O1","Other"
EOF
diff --git a/bin/tests/system/geoip/ns2/named1.conf b/bin/tests/system/geoip/ns2/named1.conf
index 367b5c7fa6..d9356c45e1 100644
--- a/bin/tests/system/geoip/ns2/named1.conf
+++ b/bin/tests/system/geoip/ns2/named1.conf
@@ -95,6 +95,14 @@ view seven {
};
};
+view other {
+ match-clients { geoip db country country O1; };
+ zone "example" {
+ type master;
+ file "exampleother.db";
+ };
+};
+
view none {
match-clients { any; };
zone "example" {
diff --git a/bin/tests/system/geoip/ns2/named14.conf b/bin/tests/system/geoip/ns2/named14.conf
index f92d25216c..bce55dc0ef 100644
--- a/bin/tests/system/geoip/ns2/named14.conf
+++ b/bin/tests/system/geoip/ns2/named14.conf
@@ -24,10 +24,11 @@ options {
transfer-source 10.53.0.2;
port 5300;
pid-file "named.pid";
- listen-on { 10.53.0.2; };
+ listen-on { 127.0.0.1; 10.53.0.2; };
listen-on-v6 { none; };
recursion no;
geoip-directory "../data";
+ geoip-use-ecs no;
};
key rndc_key {
@@ -107,6 +108,6 @@ view none {
match-clients { any; };
zone "example" {
type master;
- file "example.db.in";
+ file "examplebogus.db";
};
};
diff --git a/bin/tests/system/geoip/setup.sh b/bin/tests/system/geoip/setup.sh
index 5de40eb6ef..2aaeaca15f 100644
--- a/bin/tests/system/geoip/setup.sh
+++ b/bin/tests/system/geoip/setup.sh
@@ -21,7 +21,7 @@ $SHELL clean.sh
cp ns2/named1.conf ns2/named.conf
-for i in 1 2 3 4 5 6 7; do
+for i in 1 2 3 4 5 6 7 other bogus; do
cp ns2/example.db.in ns2/example${i}.db
echo "@ IN TXT \"$i\"" >> ns2/example$i.db
done
diff --git a/bin/tests/system/geoip/tests.sh b/bin/tests/system/geoip/tests.sh
index 3e916aed31..19d05ce66f 100644
--- a/bin/tests/system/geoip/tests.sh
+++ b/bin/tests/system/geoip/tests.sh
@@ -38,6 +38,30 @@ done
[ $ret -eq 0 ] || echo "I:failed"
status=`expr $status + $ret`
+n=`expr $n + 1`
+echo "I:checking GeoIP country database by code (using client subnet) ($n)"
+ret=0
+lret=0
+for i in 1 2 3 4 5 6 7; do
+ $DIG $DIGOPTS txt example -b 127.0.0.1 +subnet="10.53.0.$i/0" > dig.out.ns2.test$n.$i || lret=1
+ j=`cat dig.out.ns2.test$n.$i | tr -d '"'`
+ [ "$i" = "$j" ] || lret=1
+ [ $lret -eq 1 ] && break
+done
+[ $lret -eq 1 ] && ret=1
+[ $ret -eq 0 ] || echo "I:failed"
+status=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo "I:checking response scope using client subnet ($n)"
+ret=0
+$DIG +tcp -p5300 @10.53.0.2 txt example -b 127.0.0.1 +subnet="10.53.0.1/32" > dig.out.ns2.test$n.1 || ret=1
+grep 'CLIENT-SUBNET.*10.53.0.1/32/32' dig.out.ns2.test$n.1 > /dev/null || ret=1
+$DIG +tcp -p5300 @10.53.0.2 txt example -b 127.0.0.1 +subnet="192.0.2.64/32" > dig.out.ns2.test$n.2 || ret=1
+grep 'CLIENT-SUBNET.*192.0.2.64/32/24' dig.out.ns2.test$n.2 > /dev/null || ret=1
+[ $ret -eq 0 ] || echo "I:failed"
+status=`expr $status + $ret`
+
echo "I:reloading server"
cp -f ns2/named2.conf ns2/named.conf
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 reload 2>&1 | sed 's/^/I:ns2 /'
@@ -115,6 +139,21 @@ done
[ $ret -eq 0 ] || echo "I:failed"
status=`expr $status + $ret`
+n=`expr $n + 1`
+echo "I:checking GeoIP region database (using client subnet) ($n)"
+ret=0
+lret=0
+for i in 1 2 3 4 5 6 7; do
+ $DIG $DIGOPTS txt example -b 127.0.0.1 +subnet="10.53.0.$i/32" > dig.out.ns2.test$n.$i || lret=1
+ j=`cat dig.out.ns2.test$n.$i | tr -d '"'`
+ [ "$i" = "$j" ] || lret=1
+ [ $lret -eq 1 ] && break
+done
+[ $lret -eq 1 ] && ret=1
+[ $ret -eq 0 ] || echo "I:failed"
+status=`expr $status + $ret`
+
+
echo "I:reloading server"
cp -f ns2/named6.conf ns2/named.conf
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 reload 2>&1 | sed 's/^/I:ns2 /'
@@ -134,6 +173,20 @@ done
[ $ret -eq 0 ] || echo "I:failed"
status=`expr $status + $ret`
+n=`expr $n + 1`
+echo "I:checking GeoIP city database (using client subnet) ($n)"
+ret=0
+lret=0
+for i in 1 2 3 4 5 6 7; do
+ $DIG $DIGOPTS txt example -b 127.0.0.1 +subnet="10.53.0.$i/32" > dig.out.ns2.test$n.$i || lret=1
+ j=`cat dig.out.ns2.test$n.$i | tr -d '"'`
+ [ "$i" = "$j" ] || lret=1
+ [ $lret -eq 1 ] && break
+done
+[ $lret -eq 1 ] && ret=1
+[ $ret -eq 0 ] || echo "I:failed"
+status=`expr $status + $ret`
+
echo "I:reloading server"
cp -f ns2/named7.conf ns2/named.conf
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 reload 2>&1 | sed 's/^/I:ns2 /'
@@ -153,6 +206,20 @@ done
[ $ret -eq 0 ] || echo "I:failed"
status=`expr $status + $ret`
+n=`expr $n + 1`
+echo "I:checking GeoIP isp database (using client subnet) ($n)"
+ret=0
+lret=0
+for i in 1 2 3 4 5 6 7; do
+ $DIG $DIGOPTS txt example -b 127.0.0.1 +subnet="10.53.0.$i/32" > dig.out.ns2.test$n.$i || lret=1
+ j=`cat dig.out.ns2.test$n.$i | tr -d '"'`
+ [ "$i" = "$j" ] || lret=1
+ [ $lret -eq 1 ] && break
+done
+[ $lret -eq 1 ] && ret=1
+[ $ret -eq 0 ] || echo "I:failed"
+status=`expr $status + $ret`
+
echo "I:reloading server"
cp -f ns2/named8.conf ns2/named.conf
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 reload 2>&1 | sed 's/^/I:ns2 /'
@@ -172,6 +239,20 @@ done
[ $ret -eq 0 ] || echo "I:failed"
status=`expr $status + $ret`
+n=`expr $n + 1`
+echo "I:checking GeoIP org database (using client subnet) ($n)"
+ret=0
+lret=0
+for i in 1 2 3 4 5 6 7; do
+ $DIG $DIGOPTS txt example -b 127.0.0.1 +subnet="10.53.0.$i/32" > dig.out.ns2.test$n.$i || lret=1
+ j=`cat dig.out.ns2.test$n.$i | tr -d '"'`
+ [ "$i" = "$j" ] || lret=1
+ [ $lret -eq 1 ] && break
+done
+[ $lret -eq 1 ] && ret=1
+[ $ret -eq 0 ] || echo "I:failed"
+status=`expr $status + $ret`
+
echo "I:reloading server"
cp -f ns2/named9.conf ns2/named.conf
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 reload 2>&1 | sed 's/^/I:ns2 /'
@@ -191,6 +272,20 @@ done
[ $ret -eq 0 ] || echo "I:failed"
status=`expr $status + $ret`
+n=`expr $n + 1`
+echo "I:checking GeoIP asnum database (using client subnet) ($n)"
+ret=0
+lret=0
+for i in 1 2 3 4 5 6 7; do
+ $DIG $DIGOPTS txt example -b 127.0.0.1 +subnet="10.53.0.$i/32" > dig.out.ns2.test$n.$i || lret=1
+ j=`cat dig.out.ns2.test$n.$i | tr -d '"'`
+ [ "$i" = "$j" ] || lret=1
+ [ $lret -eq 1 ] && break
+done
+[ $lret -eq 1 ] && ret=1
+[ $ret -eq 0 ] || echo "I:failed"
+status=`expr $status + $ret`
+
echo "I:reloading server"
cp -f ns2/named10.conf ns2/named.conf
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 reload 2>&1 | sed 's/^/I:ns2 /'
@@ -210,6 +305,20 @@ done
[ $ret -eq 0 ] || echo "I:failed"
status=`expr $status + $ret`
+n=`expr $n + 1`
+echo "I:checking GeoIP domain database (using client subnet) ($n)"
+ret=0
+lret=0
+for i in 1 2 3 4 5 6 7; do
+ $DIG $DIGOPTS txt example -b 127.0.0.1 +subnet="10.53.0.$i/32" > dig.out.ns2.test$n.$i || lret=1
+ j=`cat dig.out.ns2.test$n.$i | tr -d '"'`
+ [ "$i" = "$j" ] || lret=1
+ [ $lret -eq 1 ] && break
+done
+[ $lret -eq 1 ] && ret=1
+[ $ret -eq 0 ] || echo "I:failed"
+status=`expr $status + $ret`
+
echo "I:reloading server"
cp -f ns2/named11.conf ns2/named.conf
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 reload 2>&1 | sed 's/^/I:ns2 /'
@@ -248,6 +357,20 @@ done
[ $ret -eq 0 ] || echo "I:failed"
status=`expr $status + $ret`
+n=`expr $n + 1`
+echo "I:checking GeoIP netspeed database (using client subnet) ($n)"
+ret=0
+lret=0
+for i in 1 2 3 4; do
+ $DIG $DIGOPTS txt example -b 127.0.0.1 +subnet="10.53.0.$i/32" > dig.out.ns2.test$n.$i || lret=1
+ j=`cat dig.out.ns2.test$n.$i | tr -d '"'`
+ [ "$i" = "$j" ] || lret=1
+ [ $lret -eq 1 ] && break
+done
+[ $lret -eq 1 ] && ret=1
+[ $ret -eq 0 ] || echo "I:failed"
+status=`expr $status + $ret`
+
echo "I:reloading server"
cp -f ns2/named13.conf ns2/named.conf
$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 reload 2>&1 | sed 's/^/I:ns2 /'
@@ -280,5 +403,29 @@ done
[ $ret -eq 0 ] || echo "I:failed"
status=`expr $status + $ret`
+echo "I:reloading server"
+cp -f ns2/named14.conf ns2/named.conf
+$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 reload 2>&1 | sed 's/^/I:ns2 /'
+sleep 3
+
+n=`expr $n + 1`
+echo "I:checking geoip-use-ecs ($n)"
+ret=0
+lret=0
+for i in 1 2 3 4 5 6 7; do
+ $DIG $DIGOPTS txt example -b 10.53.0.$i > dig.out.ns2.test$n.$i || lret=1
+ j=`cat dig.out.ns2.test$n.$i | tr -d '"'`
+ [ "$i" = "$j" ] || lret=1
+ [ $lret -eq 1 ] && break
+
+ $DIG $DIGOPTS txt example -b 127.0.0.1 +subnet="10.53.0.$i/32" > dig.out.ns2.test$n.ecs.$i || lret=1
+ j=`cat dig.out.ns2.test$n.ecs.$i | tr -d '"'`
+ [ "$j" = "bogus" ] || lret=1
+ [ $lret -eq 1 ] && break
+done
+[ $lret -eq 1 ] && ret=1
+[ $ret -eq 0 ] || echo "I:failed"
+status=`expr $status + $ret`
+
echo "I:exit status: $status"
exit $status
diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml
index e0cf9264f4..8d34bf0b4f 100644
--- a/doc/arm/Bv9ARM-book.xml
+++ b/doc/arm/Bv9ARM-book.xml
@@ -3444,66 +3444,6 @@ $ORIGIN 0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.
-
-
- When BIND 9 is built with GeoIP support,
- ACLs can also be used for geographic access restrictions.
- This is done by specifying an ACL element of the form:
- geoip db database field value
-
-
- The field indicates which field
- to search for a match. Available fields are "country",
- "region", "city", "continent", "postal" (postal code),
- "metro" (metro code), "area" (area code), "tz" (timezone),
- "isp", "org", "asnum", "domain" and "netspeed".
-
-
- value is the value to search
- for within the database. A string may be quoted if it
- contains spaces or other special characters. If this is
- an "asnum" search, then the leading "ASNNNN" string can be
- used, otherwise the full description must be used (e.g.
- "ASNNNN Example Company Name"). If this is a "country"
- search and the string is two characters long, then it must
- be a standard ISO-3166-1 two-letter country code, and if it
- is three characters long then it must be an ISO-3166-1
- three-letter country code; otherwise it is the full name
- of the country. Similarly, if this is a "region" search
- and the string is two characters long, then it must be a
- standard two-letter state or province abbreviation;
- otherwise it is the full name of the state or province.
-
-
- The database field indicates which
- GeoIP database to search for a match. In most cases this is
- unnecessary, because most search fields can only be found in
- a single database. However, searches for country can be
- answered from the "city", "region", or "country" databases,
- and searches for region (i.e., state or province) can be
- answered from the "city" or "region" databases. For these
- search types, specifying a database
- will force the query to be answered from that database and no
- other. If database is not
- specified, then these queries will be answered from the "city",
- database if it is installed, or the "region" database if it is
- installed, or the "country" database, in that order.
-
-
- Some example GeoIP ACLs:
-
- geoip country US;
-geoip country JAP;
-geoip db country country Canada;
-geoip db region region WA;
-geoip city "San Francisco";
-geoip region Oklahoma;
-geoip postal 95062;
-geoip tz "America/Los_Angeles";
-geoip org "Internet Systems Consortium";
-
-
-
controls Statement Grammar
@@ -4858,6 +4798,7 @@ badresp:1,adberr:0,findfail:0,valfail:0]
allow-update { address_match_list };
allow-update-forwarding { address_match_list };
automatic-interface-scan { yes_or_no };
+ geoip-use-ecs yes_or_no;
update-check-ksk yes_or_no;
dnssec-update-mode ( maintain | no-resign );
dnssec-dnskey-kskonly yes_or_no;
@@ -6240,6 +6181,20 @@ options {
+
+ geoip-use-ecs
+
+
+ When BIND is compiled with GeoIP support and configured
+ with "geoip" ACL elements, this option indicates whether
+ the EDNS Client Subnet option, if present in a request,
+ should be used for matching against the GeoIP database.
+ The default is
+ geoip-use-ecs yes.
+
+
+
+
has-old-clients
@@ -16188,11 +16143,11 @@ HOST-127.EXAMPLE. MX 0 .
Access Control Lists
Access Control Lists (ACLs) are address match lists that
- you can set up and nickname for future use in allow-notify,
- allow-query, allow-query-on,
- allow-recursion, allow-recursion-on,
+ you can set up and nickname for future use in
+ allow-notify, allow-query,
+ allow-query-on, allow-recursion,
blackhole, allow-transfer,
- etc.
+ match-clients, etc.
Using ACLs allows you to have finer control over who can access
@@ -16202,11 +16157,19 @@ HOST-127.EXAMPLE. MX 0 .
It is a good idea to use ACLs, and to
control access to your server. Limiting access to your server by
- outside parties can help prevent spoofing and denial of service (DoS) attacks against
- your server.
+ outside parties can help prevent spoofing and denial of service
+ (DoS) attacks against your server.
- Here is an example of how to properly apply ACLs:
+ ACLs match clients on the basis of up to three characteristics:
+ 1) The client's IP address; 2) the TSIG or SIG(0) key that was
+ used to sign the request, if any; and 3) an address prefix
+ encoded in an EDNS Client Subnet option, if any.
+
+
+ ACLs
+
+ Here is an example of ACLs based on client addresses:
@@ -16239,10 +16202,137 @@ zone "example.com" {
- This allows recursive queries of the server from the outside
- unless recursion has been previously disabled.
+ This allows authoritative queries for "example.com" from any
+ address, but recursive queries only from the networks specified
+ in "our-nets", and no queries at all from the networks
+ specified in "bogusnets".
+
+
+ In addition to network addresses and prefixes, which are
+ matched against the source address of the DNS request, ACLs
+ may include elements, which specify the
+ name of a TSIG or SIG(0) key, or
+ elements, which specify a network prefix but are only matched
+ if that prefix matches an EDNS client subnet option included
+ in the request.
+
+
+ The EDNS Client Subnet (ECS) option is used by a recursive
+ resolver to inform an authoritative name server of the network
+ address block from which the original query was received, enabling
+ authoritative servers to give different answers to the same
+ resolver for different resolver clients. An ACL containing
+ an element of the form
+ ecs prefix
+ will match if a request arrives in containing an ECS option
+ encoding an address within that prefix. If the request has no
+ ECS option, then "ecs" elements are simply ignored. Addresses
+ in ACLs that are not prefixed with "ecs" are matched only
+ against the source address.
+
+
+ When BIND 9 is built with GeoIP support,
+ ACLs can also be used for geographic access restrictions.
+ This is done by specifying an ACL element of the form:
+ geoip db database field value
+
+
+ The field indicates which field
+ to search for a match. Available fields are "country",
+ "region", "city", "continent", "postal" (postal code),
+ "metro" (metro code), "area" (area code), "tz" (timezone),
+ "isp", "org", "asnum", "domain" and "netspeed".
+
+
+ value is the value to search
+ for within the database. A string may be quoted if it
+ contains spaces or other special characters. If this is
+ an "asnum" search, then the leading "ASNNNN" string can be
+ used, otherwise the full description must be used (e.g.
+ "ASNNNN Example Company Name"). If this is a "country"
+ search and the string is two characters long, then it must
+ be a standard ISO-3166-1 two-letter country code, and if it
+ is three characters long then it must be an ISO-3166-1
+ three-letter country code; otherwise it is the full name
+ of the country. Similarly, if this is a "region" search
+ and the string is two characters long, then it must be a
+ standard two-letter state or province abbreviation;
+ otherwise it is the full name of the state or province.
+
+
+ The database field indicates which
+ GeoIP database to search for a match. In most cases this is
+ unnecessary, because most search fields can only be found in
+ a single database. However, searches for country can be
+ answered from the "city", "region", or "country" databases,
+ and searches for region (i.e., state or province) can be
+ answered from the "city" or "region" databases. For these
+ search types, specifying a database
+ will force the query to be answered from that database and no
+ other. If database is not
+ specified, then these queries will be answered from the "city",
+ database if it is installed, or the "region" database if it is
+ installed, or the "country" database, in that order.
+
+
+ By default, if a DNS query includes an EDNS Client Subnet (ECS)
+ option which encodes a non-zero address prefix, then GeoIP ACLs
+ will be matched against that address prefix. Otherwise, they
+ are matched against the source address of the query. To
+ prevent GeoIP ACLs from matching against ECS options, set
+ the geoip-use-ecs to no.
+
+
+ Some example GeoIP ACLs:
+
+ geoip country US;
+geoip country JAP;
+geoip db country country Canada;
+geoip db region region WA;
+geoip city "San Francisco";
+geoip region Oklahoma;
+geoip postal 95062;
+geoip tz "America/Los_Angeles";
+geoip org "Internet Systems Consortium";
+
+
+
+ ACLs use a "first-match" logic rather than "best-match":
+ if an address prefix matches an ACL element, then that ACL
+ is considered to have matched even if a later element would
+ have matched more specifically. For example, the ACL
+ { 10/8; !10.0.0.1; } would actually
+ match a query from 10.0.0.1, because the first element
+ indicated that the query should be accepted, and the second
+ element is ignored.
+
+
+ When using "nested" ACLs (that is, ACLs included or referenced
+ within other ACLs), a negative match of a nested ACL will
+ the containing ACL to continue looking for matches. This
+ enables complex ACLs to be constructed, in which multiple
+ client characteristics can be checked at the same time. For
+ example, to construct an ACL which allows queries only when
+ it originates from a particular network and
+ only when it is signed with a particular key, use:
+
+
+allow-query { !{ !10/8; any; }; key example; };
+
+
+ Within the nested ACL, any address that is
+ not in the 10/8 network prefix will
+ be rejected, and this will terminate processing of the
+ ACL. Any address that is in the 10/8
+ network prefix will be accepted, but this causes a negative
+ match of the nested ACL, so the containing ACL continues
+ processing. The query will then be accepted if it is signed
+ by the key "example", and rejected otherwise. The ACL, then,
+ will only matches when both conditions
+ are true.
+
Chroot and Setuid
diff --git a/lib/dns/acl.c b/lib/dns/acl.c
index 880fc3648e..11efa50e35 100644
--- a/lib/dns/acl.c
+++ b/lib/dns/acl.c
@@ -15,8 +15,6 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id: acl.c,v 1.55 2011/06/17 23:47:49 tbox Exp $ */
-
/*! \file */
#include
@@ -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
diff --git a/lib/dns/geoip.c b/lib/dns/geoip.c
index ec6beb7acb..e375e33819 100644
--- a/lib/dns/geoip.c
+++ b/lib/dns/geoip.c
@@ -72,6 +72,7 @@ typedef struct geoip_state {
unsigned int family;
isc_uint32_t ipnum;
geoipv6_t ipnum6;
+ isc_uint8_t scope;
GeoIPRecord *record;
GeoIPRegion *region;
const char *text;
@@ -157,7 +158,7 @@ clean_state(geoip_state_t *state) {
static isc_result_t
set_state(unsigned int family, isc_uint32_t ipnum, const geoipv6_t *ipnum6,
- dns_geoip_subtype_t subtype, GeoIPRecord *record,
+ isc_uint8_t scope, dns_geoip_subtype_t subtype, GeoIPRecord *record,
GeoIPRegion *region, char *name, const char *text, int id)
{
isc_result_t result;
@@ -198,6 +199,7 @@ set_state(unsigned int family, isc_uint32_t ipnum, const geoipv6_t *ipnum6,
state->family = family;
state->subtype = subtype;
+ state->scope = scope;
state->record = record;
state->region = region;
state->name = name;
@@ -232,10 +234,12 @@ get_state(void) {
static const char *
country_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
unsigned int family,
- isc_uint32_t ipnum, const geoipv6_t *ipnum6)
+ isc_uint32_t ipnum, const geoipv6_t *ipnum6,
+ isc_uint8_t *scope)
{
geoip_state_t *prev_state = NULL;
const char *text = NULL;
+ GeoIPLookup gl;
REQUIRE(db != NULL);
@@ -253,42 +257,55 @@ country_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
((prev_state->family == AF_INET && prev_state->ipnum == ipnum) ||
(prev_state->family == AF_INET6 && ipnum6 != NULL &&
memcmp(prev_state->ipnum6.s6_addr, ipnum6->s6_addr, 16) == 0)))
+ {
text = prev_state->text;
+ if (scope != NULL)
+ *scope = prev_state->scope;
+ }
if (text == NULL) {
switch (subtype) {
case dns_geoip_country_code:
if (family == AF_INET)
- text = GeoIP_country_code_by_ipnum(db, ipnum);
+ text = GeoIP_country_code_by_ipnum_gl(db,
+ ipnum, &gl);
#ifdef HAVE_GEOIP_V6
else
- text = GeoIP_country_code_by_ipnum_v6(db,
- *ipnum6);
+ text = GeoIP_country_code_by_ipnum_v6_gl(db,
+ *ipnum6, &gl);
#endif
break;
case dns_geoip_country_code3:
if (family == AF_INET)
- text = GeoIP_country_code3_by_ipnum(db, ipnum);
+ text = GeoIP_country_code3_by_ipnum_gl(db,
+ ipnum, &gl);
#ifdef HAVE_GEOIP_V6
else
- text = GeoIP_country_code3_by_ipnum_v6(db,
- *ipnum6);
+ text = GeoIP_country_code3_by_ipnum_v6_gl(db,
+ *ipnum6, &gl);
#endif
break;
case dns_geoip_country_name:
if (family == AF_INET)
- text = GeoIP_country_name_by_ipnum(db, ipnum);
+ text = GeoIP_country_name_by_ipnum_gl(db,
+ ipnum, &gl);
#ifdef HAVE_GEOIP_V6
else
- text = GeoIP_country_name_by_ipnum_v6(db,
- *ipnum6);
+ text = GeoIP_country_name_by_ipnum_v6_gl(db,
+ *ipnum6, &gl);
#endif
break;
default:
INSIST(0);
}
- set_state(family, ipnum, ipnum6, subtype,
+ if (text == NULL)
+ return (NULL);
+
+ if (scope != NULL)
+ *scope = gl.netmask;
+
+ set_state(family, ipnum, ipnum6, gl.netmask, subtype,
NULL, NULL, NULL, text, 0);
}
@@ -377,7 +394,9 @@ is_city(dns_geoip_subtype_t subtype) {
*/
static GeoIPRecord *
city_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
- unsigned int family, isc_uint32_t ipnum, const geoipv6_t *ipnum6)
+ unsigned int family, isc_uint32_t ipnum,
+ const geoipv6_t *ipnum6,
+ isc_uint8_t *scope)
{
GeoIPRecord *record = NULL;
geoip_state_t *prev_state = NULL;
@@ -397,7 +416,11 @@ city_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
((prev_state->family == AF_INET && prev_state->ipnum == ipnum) ||
(prev_state->family == AF_INET6 &&
memcmp(prev_state->ipnum6.s6_addr, ipnum6->s6_addr, 16) == 0)))
+ {
record = prev_state->record;
+ if (scope != NULL)
+ *scope = record->netmask;
+ }
if (record == NULL) {
if (family == AF_INET)
@@ -409,15 +432,17 @@ city_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
if (record == NULL)
return (NULL);
- set_state(family, ipnum, ipnum6, subtype,
+ if (scope != NULL)
+ *scope = record->netmask;
+
+ set_state(family, ipnum, ipnum6, record->netmask, subtype,
record, NULL, NULL, NULL, 0);
}
return (record);
}
-static char *
-region_string(GeoIPRegion *region, dns_geoip_subtype_t subtype, int *maxlen) {
+static char * region_string(GeoIPRegion *region, dns_geoip_subtype_t subtype, int *maxlen) {
const char *s;
char *deconst;
@@ -459,9 +484,12 @@ is_region(dns_geoip_subtype_t subtype) {
* outside the Region database.
*/
static GeoIPRegion *
-region_lookup(GeoIP *db, dns_geoip_subtype_t subtype, isc_uint32_t ipnum) {
+region_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
+ isc_uint32_t ipnum, isc_uint8_t *scope)
+{
GeoIPRegion *region = NULL;
geoip_state_t *prev_state = NULL;
+ GeoIPLookup gl;
REQUIRE(db != NULL);
@@ -469,14 +497,21 @@ region_lookup(GeoIP *db, dns_geoip_subtype_t subtype, isc_uint32_t ipnum) {
if (prev_state != NULL && prev_state->ipnum == ipnum &&
is_region(prev_state->subtype))
+ {
region = prev_state->region;
+ if (scope != NULL)
+ *scope = prev_state->scope;
+ }
if (region == NULL) {
- region = GeoIP_region_by_ipnum(db, ipnum);
+ region = GeoIP_region_by_ipnum_gl(db, ipnum, &gl);
if (region == NULL)
return (NULL);
- set_state(AF_INET, ipnum, NULL,
+ if (scope != NULL)
+ *scope = gl.netmask;
+
+ set_state(AF_INET, ipnum, NULL, gl.netmask,
subtype, NULL, region, NULL, NULL, 0);
}
@@ -489,9 +524,12 @@ region_lookup(GeoIP *db, dns_geoip_subtype_t subtype, isc_uint32_t ipnum) {
* or was for a search of a different subtype.
*/
static char *
-name_lookup(GeoIP *db, dns_geoip_subtype_t subtype, isc_uint32_t ipnum) {
+name_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
+ isc_uint32_t ipnum, isc_uint8_t *scope)
+{
char *name = NULL;
geoip_state_t *prev_state = NULL;
+ GeoIPLookup gl;
REQUIRE(db != NULL);
@@ -499,14 +537,21 @@ name_lookup(GeoIP *db, dns_geoip_subtype_t subtype, isc_uint32_t ipnum) {
if (prev_state != NULL && prev_state->ipnum == ipnum &&
prev_state->subtype == subtype)
+ {
name = prev_state->name;
+ if (scope != NULL)
+ *scope = prev_state->scope;
+ }
if (name == NULL) {
- name = GeoIP_name_by_ipnum(db, ipnum);
+ name = GeoIP_name_by_ipnum_gl(db, ipnum, &gl);
if (name == NULL)
return (NULL);
- set_state(AF_INET, ipnum, NULL,
+ if (scope != NULL)
+ *scope = gl.netmask;
+
+ set_state(AF_INET, ipnum, NULL, gl.netmask,
subtype, NULL, NULL, name, NULL, 0);
}
@@ -519,9 +564,12 @@ name_lookup(GeoIP *db, dns_geoip_subtype_t subtype, isc_uint32_t ipnum) {
* different subtype.
*/
static int
-netspeed_lookup(GeoIP *db, dns_geoip_subtype_t subtype, isc_uint32_t ipnum) {
+netspeed_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
+ isc_uint32_t ipnum, isc_uint8_t *scope)
+{
geoip_state_t *prev_state = NULL;
isc_boolean_t found = ISC_FALSE;
+ GeoIPLookup gl;
int id = -1;
REQUIRE(db != NULL);
@@ -531,12 +579,20 @@ netspeed_lookup(GeoIP *db, dns_geoip_subtype_t subtype, isc_uint32_t ipnum) {
if (prev_state != NULL && prev_state->ipnum == ipnum &&
prev_state->subtype == subtype) {
id = prev_state->id;
+ if (scope != NULL)
+ *scope = prev_state->scope;
found = ISC_TRUE;
}
if (!found) {
- id = GeoIP_id_by_ipnum(db, ipnum);
- set_state(AF_INET, ipnum, NULL,
+ id = GeoIP_id_by_ipnum_gl(db, ipnum, &gl);
+ if (id == 0)
+ return (0);
+
+ if (scope != NULL)
+ *scope = gl.netmask;
+
+ set_state(AF_INET, ipnum, NULL, gl.netmask,
subtype, NULL, NULL, NULL, NULL, id);
}
@@ -599,7 +655,7 @@ fix_subtype(const isc_netaddr_t *reqaddr, const dns_geoip_databases_t *geoip,
#endif /* HAVE_GEOIP */
isc_boolean_t
-dns_geoip_match(const isc_netaddr_t *reqaddr,
+dns_geoip_match(const isc_netaddr_t *reqaddr, isc_uint8_t *scope,
const dns_geoip_databases_t *geoip,
const dns_geoip_elem_t *elt)
{
@@ -662,7 +718,7 @@ dns_geoip_match(const isc_netaddr_t *reqaddr,
INSIST(elt->as_string != NULL);
- cs = country_lookup(db, subtype, family, ipnum, ipnum6);
+ cs = country_lookup(db, subtype, family, ipnum, ipnum6, scope);
if (cs != NULL && strncasecmp(elt->as_string, cs, maxlen) == 0)
return (ISC_TRUE);
break;
@@ -682,7 +738,8 @@ dns_geoip_match(const isc_netaddr_t *reqaddr,
if (db == NULL)
return (ISC_FALSE);
- record = city_lookup(db, subtype, family, ipnum, ipnum6);
+ record = city_lookup(db, subtype, family,
+ ipnum, ipnum6, scope);
if (record == NULL)
break;
@@ -697,7 +754,8 @@ dns_geoip_match(const isc_netaddr_t *reqaddr,
if (db == NULL)
return (ISC_FALSE);
- record = city_lookup(db, subtype, family, ipnum, ipnum6);
+ record = city_lookup(db, subtype, family,
+ ipnum, ipnum6, scope);
if (record == NULL)
break;
@@ -710,7 +768,8 @@ dns_geoip_match(const isc_netaddr_t *reqaddr,
if (db == NULL)
return (ISC_FALSE);
- record = city_lookup(db, subtype, family, ipnum, ipnum6);
+ record = city_lookup(db, subtype, family,
+ ipnum, ipnum6, scope);
if (record == NULL)
break;
@@ -731,7 +790,7 @@ dns_geoip_match(const isc_netaddr_t *reqaddr,
if (family == AF_INET6)
return (ISC_FALSE);
- region = region_lookup(geoip->region, subtype, ipnum);
+ region = region_lookup(geoip->region, subtype, ipnum, scope);
if (region == NULL)
break;
@@ -765,7 +824,7 @@ dns_geoip_match(const isc_netaddr_t *reqaddr,
if (family == AF_INET6)
return (ISC_FALSE);
- s = name_lookup(db, subtype, ipnum);
+ s = name_lookup(db, subtype, ipnum, scope);
if (s != NULL) {
size_t l;
if (strcasecmp(elt->as_string, s) == 0)
@@ -790,7 +849,7 @@ dns_geoip_match(const isc_netaddr_t *reqaddr,
if (family == AF_INET6)
return (ISC_FALSE);
- id = netspeed_lookup(geoip->netspeed, subtype, ipnum);
+ id = netspeed_lookup(geoip->netspeed, subtype, ipnum, scope);
if (id == elt->as_int)
return (ISC_TRUE);
break;
diff --git a/lib/dns/include/dns/acl.h b/lib/dns/include/dns/acl.h
index ebcc6c7910..d44af1ec7d 100644
--- a/lib/dns/include/dns/acl.h
+++ b/lib/dns/include/dns/acl.h
@@ -103,6 +103,7 @@ struct dns_aclenv {
isc_boolean_t match_mapped;
#ifdef HAVE_GEOIP
dns_geoip_databases_t *geoip;
+ isc_boolean_t geoip_use_ecs;
#endif
};
@@ -212,12 +213,28 @@ dns_acl_match(const isc_netaddr_t *reqaddr,
const dns_aclenv_t *env,
int *match,
const dns_aclelement_t **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);
/*%<
* General, low-level ACL matching. This is expected to
* be useful even for weird stuff like the topology and sortlist statements.
*
* Match the address 'reqaddr', and optionally the key name 'reqsigner',
- * against 'acl'. 'reqsigner' may be NULL.
+ * and optionally the client prefix 'ecs' of length 'ecslen'
+ * (reported via EDNS client subnet option) against 'acl'.
+ *
+ * 'reqsigner' and 'ecs' may be NULL. If an ACL matches against 'ecs'
+ * and 'ecslen', then 'scope' will be set to indicate the netmask that
+ * matched.
*
* If there is a match, '*match' will be set to an integer whose absolute
* value corresponds to the order in which the matching value was inserted
@@ -244,6 +261,16 @@ dns_aclelement_match(const isc_netaddr_t *reqaddr,
const dns_aclelement_t *e,
const dns_aclenv_t *env,
const dns_aclelement_t **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);
/*%<
* Like dns_acl_match, but matches against the single ACL element 'e'
* rather than a complete ACL, and returns ISC_TRUE iff it matched.
diff --git a/lib/dns/include/dns/geoip.h b/lib/dns/include/dns/geoip.h
index 35a4036a12..a656783223 100644
--- a/lib/dns/include/dns/geoip.h
+++ b/lib/dns/include/dns/geoip.h
@@ -108,7 +108,7 @@ typedef struct dns_geoip_databases {
ISC_LANG_BEGINDECLS
isc_boolean_t
-dns_geoip_match(const isc_netaddr_t *reqaddr,
+dns_geoip_match(const isc_netaddr_t *reqaddr, isc_uint8_t *scope,
const dns_geoip_databases_t *geoip,
const dns_geoip_elem_t *elt);
diff --git a/lib/dns/include/dns/iptable.h b/lib/dns/include/dns/iptable.h
index 2ce8e18124..512e73da06 100644
--- a/lib/dns/include/dns/iptable.h
+++ b/lib/dns/include/dns/iptable.h
@@ -51,6 +51,10 @@ dns_iptable_create(isc_mem_t *mctx, dns_iptable_t **target);
isc_result_t
dns_iptable_addprefix(dns_iptable_t *tab, isc_netaddr_t *addr,
isc_uint16_t bitlen, isc_boolean_t pos);
+isc_result_t
+dns_iptable_addprefix2(dns_iptable_t *tab, isc_netaddr_t *addr,
+ isc_uint16_t bitlen, isc_boolean_t pos,
+ isc_boolean_t is_ecs);
/*
* Add an IP prefix to an existing IP table
*/
diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h
index de121c764d..183ecec755 100644
--- a/lib/dns/include/dns/message.h
+++ b/lib/dns/include/dns/message.h
@@ -112,7 +112,7 @@
#define DNS_OPT_SIT 65001 /*%< SIT opt code */
/*%< The number of EDNS options we know about. */
-#define DNS_EDNSOPTIONS 4
+#define DNS_EDNSOPTIONS 5
#define DNS_MESSAGE_REPLYPRESERVE (DNS_MESSAGEFLAG_RD|DNS_MESSAGEFLAG_CD)
#define DNS_MESSAGEEXTFLAG_REPLYPRESERVE (DNS_MESSAGEEXTFLAG_DO)
diff --git a/lib/dns/iptable.c b/lib/dns/iptable.c
index 701950533c..9413774947 100644
--- a/lib/dns/iptable.c
+++ b/lib/dns/iptable.c
@@ -14,8 +14,6 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id: iptable.c,v 1.15 2009/02/18 23:47:48 tbox Exp $ */
-
#include
#include
@@ -63,16 +61,24 @@ isc_boolean_t dns_iptable_pos = ISC_TRUE;
isc_result_t
dns_iptable_addprefix(dns_iptable_t *tab, isc_netaddr_t *addr,
isc_uint16_t bitlen, isc_boolean_t pos)
+{
+ return(dns_iptable_addprefix2(tab, addr, bitlen, pos, ISC_FALSE));
+}
+
+isc_result_t
+dns_iptable_addprefix2(dns_iptable_t *tab, isc_netaddr_t *addr,
+ isc_uint16_t bitlen, isc_boolean_t pos,
+ isc_boolean_t is_ecs)
{
isc_result_t result;
isc_prefix_t pfx;
isc_radix_node_t *node = NULL;
- int family;
+ int i;
INSIST(DNS_IPTABLE_VALID(tab));
INSIST(tab->radix);
- NETADDR_TO_PREFIX_T(addr, pfx, bitlen);
+ NETADDR_TO_PREFIX_T(addr, pfx, bitlen, is_ecs);
result = isc_radix_insert(tab->radix, &node, NULL, &pfx);
if (result != ISC_R_SUCCESS) {
@@ -81,28 +87,20 @@ dns_iptable_addprefix(dns_iptable_t *tab, isc_netaddr_t *addr,
}
/* If a node already contains data, don't overwrite it */
- family = pfx.family;
- if (family == AF_UNSPEC) {
+ if (pfx.family == AF_UNSPEC) {
/* "any" or "none" */
INSIST(pfx.bitlen == 0);
- if (pos) {
- if (node->data[0] == NULL)
- node->data[0] = &dns_iptable_pos;
- if (node->data[1] == NULL)
- node->data[1] = &dns_iptable_pos;
- } else {
- if (node->data[0] == NULL)
- node->data[0] = &dns_iptable_neg;
- if (node->data[1] == NULL)
- node->data[1] = &dns_iptable_neg;
+ for (i = 0; i < 4; i++) {
+ if (node->data[i] == NULL)
+ node->data[i] = pos ? &dns_iptable_pos
+ : &dns_iptable_neg;
}
} else {
/* any other prefix */
- if (node->data[ISC_IS6(family)] == NULL) {
- if (pos)
- node->data[ISC_IS6(family)] = &dns_iptable_pos;
- else
- node->data[ISC_IS6(family)] = &dns_iptable_neg;
+ int offset = ISC_RADIX_OFF(&pfx);
+ if (node->data[offset] == NULL) {
+ node->data[offset] = pos ? &dns_iptable_pos
+ : &dns_iptable_neg;
}
}
@@ -118,7 +116,7 @@ dns_iptable_merge(dns_iptable_t *tab, dns_iptable_t *source, isc_boolean_t pos)
{
isc_result_t result;
isc_radix_node_t *node, *new_node;
- int max_node = 0;
+ int i, max_node = 0;
RADIX_WALK (source->radix->head, node) {
new_node = NULL;
@@ -135,20 +133,15 @@ dns_iptable_merge(dns_iptable_t *tab, dns_iptable_t *source, isc_boolean_t pos)
* could be a security risk. To prevent this, we
* just leave the negative nodes negative.
*/
- if (!pos) {
- if (node->data[0] &&
- *(isc_boolean_t *) node->data[0] == ISC_TRUE)
- new_node->data[0] = &dns_iptable_neg;
-
- if (node->data[1] &&
- *(isc_boolean_t *) node->data[1] == ISC_TRUE)
- new_node->data[1] = &dns_iptable_neg;
+ for (i = 0; i < 4; i++) {
+ if (!pos) {
+ if (node->data[i] &&
+ *(isc_boolean_t *) node->data[i])
+ new_node->data[i] = &dns_iptable_neg;
+ }
+ if (node->node_num[i] > max_node)
+ max_node = node->node_num[i];
}
-
- if (node->node_num[0] > max_node)
- max_node = node->node_num[0];
- if (node->node_num[1] > max_node)
- max_node = node->node_num[1];
} RADIX_WALK_END;
tab->radix->num_added_node += max_node;
diff --git a/lib/dns/message.c b/lib/dns/message.c
index 88c9239eb6..06b9068dd4 100644
--- a/lib/dns/message.c
+++ b/lib/dns/message.c
@@ -15,8 +15,6 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id$ */
-
/*! \file */
/***
diff --git a/lib/dns/tests/geoip_test.c b/lib/dns/tests/geoip_test.c
index a39ba727b4..60994d2ce9 100644
--- a/lib/dns/tests/geoip_test.c
+++ b/lib/dns/tests/geoip_test.c
@@ -136,8 +136,8 @@ load_geoip(const char *dir) {
}
static isc_boolean_t
-do_lookup_string(const char *addr, dns_geoip_subtype_t subtype,
- const char *string)
+do_lookup_string(const char *addr, isc_uint8_t *scope,
+ dns_geoip_subtype_t subtype, const char *string)
{
dns_geoip_elem_t elt;
struct in_addr in4;
@@ -149,12 +149,12 @@ do_lookup_string(const char *addr, dns_geoip_subtype_t subtype,
elt.subtype = subtype;
strcpy(elt.as_string, string);
- return (dns_geoip_match(&na, &geoip, &elt));
+ return (dns_geoip_match(&na, scope, &geoip, &elt));
}
static isc_boolean_t
-do_lookup_string_v6(const char *addr, dns_geoip_subtype_t subtype,
- const char *string)
+do_lookup_string_v6(const char *addr, isc_uint8_t *scope,
+ dns_geoip_subtype_t subtype, const char *string)
{
dns_geoip_elem_t elt;
struct in6_addr in6;
@@ -166,11 +166,13 @@ do_lookup_string_v6(const char *addr, dns_geoip_subtype_t subtype,
elt.subtype = subtype;
strcpy(elt.as_string, string);
- return (dns_geoip_match(&na, &geoip, &elt));
+ return (dns_geoip_match(&na, scope, &geoip, &elt));
}
static isc_boolean_t
-do_lookup_int(const char *addr, dns_geoip_subtype_t subtype, int id) {
+do_lookup_int(const char *addr, isc_uint8_t *scope,
+ dns_geoip_subtype_t subtype, int id)
+{
dns_geoip_elem_t elt;
struct in_addr in4;
isc_netaddr_t na;
@@ -181,7 +183,7 @@ do_lookup_int(const char *addr, dns_geoip_subtype_t subtype, int id) {
elt.subtype = subtype;
elt.as_int = id;
- return (dns_geoip_match(&na, &geoip, &elt));
+ return (dns_geoip_match(&na, scope, &geoip, &elt));
}
/*
@@ -196,6 +198,7 @@ ATF_TC_HEAD(country, tc) {
ATF_TC_BODY(country, tc) {
isc_result_t result;
isc_boolean_t match;
+ isc_uint8_t scope;
UNUSED(tc);
@@ -210,16 +213,30 @@ ATF_TC_BODY(country, tc) {
atf_tc_skip("Database not available");
}
- match = do_lookup_string("10.53.0.1", dns_geoip_country_code, "AU");
+ match = do_lookup_string("10.53.0.1", &scope,
+ dns_geoip_country_code, "AU");
ATF_CHECK(match);
+ ATF_CHECK_EQ(scope, 32);
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", &scope,
dns_geoip_country_code3, "AUS");
ATF_CHECK(match);
+ ATF_CHECK_EQ(scope, 32);
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", &scope,
dns_geoip_country_name, "Australia");
ATF_CHECK(match);
+ ATF_CHECK_EQ(scope, 32);
+
+ match = do_lookup_string("192.0.2.128", &scope,
+ dns_geoip_country_code, "O1");
+ ATF_CHECK(match);
+ ATF_CHECK_EQ(scope, 24);
+
+ match = do_lookup_string("192.0.2.128", &scope,
+ dns_geoip_country_name, "Other");
+ ATF_CHECK(match);
+ ATF_CHECK_EQ(scope, 24);
dns_test_end();
}
@@ -232,6 +249,7 @@ ATF_TC_HEAD(country_v6, tc) {
ATF_TC_BODY(country_v6, tc) {
isc_result_t result;
isc_boolean_t match;
+ isc_uint8_t scope;
UNUSED(tc);
@@ -246,17 +264,20 @@ ATF_TC_BODY(country_v6, tc) {
atf_tc_skip("Database not available");
}
- match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", &scope,
dns_geoip_country_code, "AU");
ATF_CHECK(match);
+ ATF_CHECK_EQ(scope, 128);
- match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", &scope,
dns_geoip_country_code3, "AUS");
ATF_CHECK(match);
+ ATF_CHECK_EQ(scope, 128);
- match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", &scope,
dns_geoip_country_name, "Australia");
ATF_CHECK(match);
+ ATF_CHECK_EQ(scope, 128);
dns_test_end();
}
@@ -283,42 +304,42 @@ ATF_TC_BODY(city, tc) {
atf_tc_skip("Database not available");
}
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", NULL,
dns_geoip_city_continentcode, "NA");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", NULL,
dns_geoip_city_countrycode, "US");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", NULL,
dns_geoip_city_countrycode3, "USA");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", NULL,
dns_geoip_city_countryname, "United States");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", NULL,
dns_geoip_city_region, "CA");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", NULL,
dns_geoip_city_regionname, "California");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", NULL,
dns_geoip_city_name, "Redwood City");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", NULL,
dns_geoip_city_postalcode, "94063");
ATF_CHECK(match);
- match = do_lookup_int("10.53.0.1", dns_geoip_city_areacode, 650);
+ match = do_lookup_int("10.53.0.1", NULL, dns_geoip_city_areacode, 650);
ATF_CHECK(match);
- match = do_lookup_int("10.53.0.1", dns_geoip_city_metrocode, 807);
+ match = do_lookup_int("10.53.0.1", NULL, dns_geoip_city_metrocode, 807);
ATF_CHECK(match);
dns_test_end();
@@ -346,36 +367,36 @@ ATF_TC_BODY(city_v6, tc) {
atf_tc_skip("Database not available");
}
- match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL,
dns_geoip_city_continentcode, "NA");
ATF_CHECK(match);
- match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL,
dns_geoip_city_countrycode, "US");
ATF_CHECK(match);
- match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL,
dns_geoip_city_countrycode3, "USA");
ATF_CHECK(match);
- match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL,
dns_geoip_city_countryname,
"United States");
ATF_CHECK(match);
- match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL,
dns_geoip_city_region, "CA");
ATF_CHECK(match);
- match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL,
dns_geoip_city_regionname, "California");
ATF_CHECK(match);
- match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL,
dns_geoip_city_name, "Redwood City");
ATF_CHECK(match);
- match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
+ match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL,
dns_geoip_city_postalcode, "94063");
ATF_CHECK(match);
@@ -405,15 +426,15 @@ ATF_TC_BODY(region, tc) {
atf_tc_skip("Database not available");
}
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", NULL,
dns_geoip_region_code, "CA");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", NULL,
dns_geoip_region_name, "California");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.1",
+ match = do_lookup_string("10.53.0.1", NULL,
dns_geoip_region_countrycode, "US");
ATF_CHECK(match);
@@ -447,30 +468,30 @@ ATF_TC_BODY(best, tc) {
atf_tc_skip("Database not available");
}
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_countrycode, "US");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_countrycode3, "USA");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_countryname, "United States");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_regionname, "Virginia");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_region, "VA");
ATF_CHECK(match);
GeoIP_delete(geoip.city_v4);
geoip.city_v4 = NULL;
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_countrycode, "AU");
ATF_CHECK(match);
@@ -478,26 +499,26 @@ ATF_TC_BODY(best, tc) {
* Note, region doesn't support code3 or countryname, so
* the next two would be answered from the country database instead
*/
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_countrycode3, "CAN");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_countryname, "Canada");
ATF_CHECK(match);
GeoIP_delete(geoip.region);
geoip.region = NULL;
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_countrycode, "CA");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_countrycode3, "CAN");
ATF_CHECK(match);
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_countryname, "Canada");
ATF_CHECK(match);
@@ -528,7 +549,7 @@ ATF_TC_BODY(asnum, tc) {
}
- match = do_lookup_string("10.53.0.3", dns_geoip_as_asnum,
+ match = do_lookup_string("10.53.0.3", NULL, dns_geoip_as_asnum,
"AS100003 Three Network Labs");
ATF_CHECK(match);
@@ -557,7 +578,7 @@ ATF_TC_BODY(isp, tc) {
atf_tc_skip("Database not available");
}
- match = do_lookup_string("10.53.0.1", dns_geoip_isp_name,
+ match = do_lookup_string("10.53.0.1", NULL, dns_geoip_isp_name,
"One Systems, Inc.");
ATF_CHECK(match);
@@ -586,7 +607,7 @@ ATF_TC_BODY(org, tc) {
atf_tc_skip("Database not available");
}
- match = do_lookup_string("10.53.0.2", dns_geoip_org_name,
+ match = do_lookup_string("10.53.0.2", NULL, dns_geoip_org_name,
"Two Technology Ltd.");
ATF_CHECK(match);
@@ -615,7 +636,7 @@ ATF_TC_BODY(domain, tc) {
atf_tc_skip("Database not available");
}
- match = do_lookup_string("10.53.0.4",
+ match = do_lookup_string("10.53.0.4", NULL,
dns_geoip_domain_name, "four.com");
ATF_CHECK(match);
@@ -644,16 +665,16 @@ ATF_TC_BODY(netspeed, tc) {
atf_tc_skip("Database not available");
}
- match = do_lookup_int("10.53.0.1", dns_geoip_netspeed_id, 0);
+ match = do_lookup_int("10.53.0.1", NULL, dns_geoip_netspeed_id, 0);
ATF_CHECK(match);
- match = do_lookup_int("10.53.0.2", dns_geoip_netspeed_id, 1);
+ match = do_lookup_int("10.53.0.2", NULL, dns_geoip_netspeed_id, 1);
ATF_CHECK(match);
- match = do_lookup_int("10.53.0.3", dns_geoip_netspeed_id, 2);
+ match = do_lookup_int("10.53.0.3", NULL, dns_geoip_netspeed_id, 2);
ATF_CHECK(match);
- match = do_lookup_int("10.53.0.4", dns_geoip_netspeed_id, 3);
+ match = do_lookup_int("10.53.0.4", NULL, dns_geoip_netspeed_id, 3);
ATF_CHECK(match);
dns_test_end();
diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in
index 862b2b03fe..422f21c3ee 100644
--- a/lib/dns/win32/libdns.def.in
+++ b/lib/dns/win32/libdns.def.in
@@ -25,9 +25,11 @@ dns_acl_isany
dns_acl_isinsecure
dns_acl_isnone
dns_acl_match
+dns_acl_match2
dns_acl_merge
dns_acl_none
dns_aclelement_match
+dns_aclelement_match2
dns_aclenv_copy
dns_aclenv_destroy
dns_aclenv_init
diff --git a/lib/isc/include/isc/radix.h b/lib/isc/include/isc/radix.h
index 1c1887f1d0..4d0b3b08f1 100644
--- a/lib/isc/include/isc/radix.h
+++ b/lib/isc/include/isc/radix.h
@@ -14,8 +14,6 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id: radix.h,v 1.13 2008/12/01 23:47:45 tbox Exp $ */
-
/*
* This source was adapted from MRT's RCS Ids:
* Id: radix.h,v 1.6 1999/08/03 03:32:53 masaki Exp
@@ -34,7 +32,7 @@
#ifndef _RADIX_H
#define _RADIX_H
-#define NETADDR_TO_PREFIX_T(na,pt,bits) \
+#define NETADDR_TO_PREFIX_T(na,pt,bits,isecs) \
do { \
memset(&(pt), 0, sizeof(pt)); \
if((na) != NULL) { \
@@ -50,6 +48,7 @@
(pt).family = AF_UNSPEC; \
(pt).bitlen = 0; \
} \
+ (pt).ecs = isecs; \
isc_refcount_init(&(pt).refcount, 0); \
} while(0)
@@ -57,6 +56,7 @@ typedef struct isc_prefix {
isc_mem_t *mctx;
unsigned int family; /* AF_INET | AF_INET6, or AF_UNSPEC for "any" */
unsigned int bitlen; /* 0 for "any" */
+ isc_boolean_t ecs; /* ISC_TRUE for an EDNS client subnet address */
isc_refcount_t refcount;
union {
struct in_addr sin;
@@ -81,23 +81,32 @@ typedef void (*isc_radix_processfunc_t)(isc_prefix_t *, void **);
* return the one that was added first.
*
* An IPv4 prefix and an IPv6 prefix may share a radix tree node if they
- * have the same length and bit pattern (e.g., 127/8 and 7f::/8). To
- * disambiguate between them, node_num and data are two-element arrays;
- * node_num[0] and data[0] are used for IPv4 addresses, node_num[1]
- * and data[1] for IPv6 addresses. The only exception is a prefix of
- * 0/0 (aka "any" or "none"), which is always stored as IPv4 but matches
- * IPv6 addresses too.
+ * have the same length and bit pattern (e.g., 127/8 and 7f::/8). Also,
+ * a node that matches a client address may also match an EDNS client
+ * subnet address. To disambiguate between these, node_num and data
+ * are four-element arrays;
+ *
+ * - node_num[0] and data[0] are used for IPv4 client addresses
+ * - node_num[1] and data[1] for IPv4 client subnet addresses
+ * - node_num[2] and data[2] are used for IPv6 client addresses
+ * - node_num[3] and data[3] for IPv6 client subnet addresses
+ *
+ * A prefix of 0/0 (aka "any" or "none"), is always stored as IPv4,
+ * but matches IPv6 addresses too, as well as all client subnet
+ * addresses.
*/
-#define ISC_IS6(family) ((family) == AF_INET6 ? 1 : 0)
+#define ISC_RADIX_OFF(p) \
+ ((((p)->family == AF_INET6) ? 1 : 0) + ((p)->ecs ? 2 : 0))
+
typedef struct isc_radix_node {
isc_mem_t *mctx;
isc_uint32_t bit; /* bit length of the prefix */
isc_prefix_t *prefix; /* who we are in radix tree */
struct isc_radix_node *l, *r; /* left and right children */
struct isc_radix_node *parent; /* may be used */
- void *data[2]; /* pointers to IPv4 and IPV6 data */
- int node_num[2]; /* which node this was in the tree,
+ void *data[4]; /* pointers to IPv4 and IPV6 data */
+ int node_num[4]; /* which node this was in the tree,
or -1 for glue nodes */
} isc_radix_node_t;
diff --git a/lib/isc/radix.c b/lib/isc/radix.c
index df26615fa9..ebe277048e 100644
--- a/lib/isc/radix.c
+++ b/lib/isc/radix.c
@@ -70,6 +70,7 @@ _new_prefix(isc_mem_t *mctx, isc_prefix_t **target, int family, void *dest,
}
prefix->family = family;
+ prefix->ecs = ISC_FALSE;
prefix->mctx = NULL;
isc_mem_attach(mctx, &prefix->mctx);
@@ -182,12 +183,13 @@ _clear_radix(isc_radix_tree_t *radix, isc_radix_destroyfunc_t func) {
if (Xrn->prefix != NULL) {
_deref_prefix(Xrn->prefix);
- if (func != NULL && (Xrn->data[0] != NULL ||
- Xrn->data[1] != NULL))
+ if (func != NULL)
func(Xrn->data);
} else {
INSIST(Xrn->data[0] == NULL &&
- Xrn->data[1] == NULL);
+ Xrn->data[1] == NULL &&
+ Xrn->data[2] == NULL &&
+ Xrn->data[3] == NULL);
}
isc_mem_put(radix->mctx, Xrn, sizeof(*Xrn));
@@ -242,8 +244,7 @@ isc_radix_search(isc_radix_tree_t *radix, isc_radix_node_t **target,
isc_radix_node_t *stack[RADIX_MAXBITS + 1];
u_char *addr;
isc_uint32_t bitlen;
- int tfamily = -1;
- int cnt = 0;
+ int toff = -1, cnt = 0;
REQUIRE(radix != NULL);
REQUIRE(prefix != NULL);
@@ -281,13 +282,15 @@ isc_radix_search(isc_radix_tree_t *radix, isc_radix_node_t **target,
if (_comp_with_mask(isc_prefix_tochar(node->prefix),
isc_prefix_tochar(prefix),
- node->prefix->bitlen)) {
- if (node->node_num[ISC_IS6(prefix->family)] != -1 &&
- ((*target == NULL) ||
- (*target)->node_num[ISC_IS6(tfamily)] >
- node->node_num[ISC_IS6(prefix->family)])) {
+ node->prefix->bitlen))
+ {
+ int off = ISC_RADIX_OFF(prefix);
+ if (node->node_num[off] != -1 &&
+ ((*target == NULL) ||
+ (*target)->node_num[toff] > node->node_num[off]))
+ {
*target = node;
- tfamily = prefix->family;
+ toff = off;
}
}
}
@@ -327,7 +330,8 @@ isc_radix_insert(isc_radix_tree_t *radix, isc_radix_node_t **target,
if (node == NULL)
return (ISC_R_NOMEMORY);
node->bit = bitlen;
- node->node_num[0] = node->node_num[1] = -1;
+ for (i = 0; i < 4; i++)
+ node->node_num[i] = -1;
node->prefix = NULL;
result = _ref_prefix(radix->mctx, &node->prefix, prefix);
if (result != ISC_R_SUCCESS) {
@@ -346,25 +350,24 @@ isc_radix_insert(isc_radix_tree_t *radix, isc_radix_node_t **target,
* added to num_added_node at the end of
* the merge operation--we don't do it here.
*/
- if (source->node_num[0] != -1)
- node->node_num[0] = radix->num_added_node +
- source->node_num[0];
- if (source->node_num[1] != -1)
- node->node_num[1] = radix->num_added_node +
- source->node_num[1];
- node->data[0] = source->data[0];
- node->data[1] = source->data[1];
+ for (i = 0; i < 4; i++) {
+ if (source->node_num[i] != -1)
+ node->node_num[i] =
+ radix->num_added_node +
+ source->node_num[i];
+ node->data[i] = source->data[i];
+ }
} else {
+ int next = ++radix->num_added_node;
if (fam == AF_UNSPEC) {
/* "any" or "none" */
- node->node_num[0] = node->node_num[1] =
- ++radix->num_added_node;
+ for (i = 0; i < 4; i++)
+ node->node_num[i] = next;
} else {
- node->node_num[ISC_IS6(fam)] =
- ++radix->num_added_node;
+ node->node_num[ISC_RADIX_OFF(prefix)] = next;
}
- node->data[0] = NULL;
- node->data[1] = NULL;
+
+ memset(node->data, 0, sizeof(node->data));
}
radix->head = node;
radix->num_active_node++;
@@ -426,37 +429,33 @@ isc_radix_insert(isc_radix_tree_t *radix, isc_radix_node_t **target,
if (node->prefix != NULL) {
/* Set node_num only if it hasn't been set before */
if (source != NULL) {
- /* Merging node */
- if (node->node_num[0] == -1 &&
- source->node_num[0] != -1) {
- node->node_num[0] =
- radix->num_added_node +
- source->node_num[0];
- node->data[0] = source->data[0];
- }
- if (node->node_num[1] == -1 &&
- source->node_num[0] != -1) {
- node->node_num[1] =
- radix->num_added_node +
- source->node_num[1];
- node->data[1] = source->data[1];
+ /* Merging nodes */
+ for (i = 0; i < 4; i++) {
+ if (node->node_num[i] == -1 &&
+ source->node_num[i] != -1) {
+ node->node_num[i] =
+ radix->num_added_node +
+ source->node_num[i];
+ node->data[i] = source->data[i];
+ }
}
} else {
if (fam == AF_UNSPEC) {
/* "any" or "none" */
int next = radix->num_added_node + 1;
- if (node->node_num[0] == -1) {
- node->node_num[0] = next;
- radix->num_added_node = next;
- }
- if (node->node_num[1] == -1) {
- node->node_num[1] = next;
- radix->num_added_node = next;
+ for (i = 0; i < 4; i++) {
+ if (node->node_num[i] == -1) {
+ node->node_num[i] =
+ next;
+ radix->num_added_node =
+ next;
+ }
}
} else {
- if (node->node_num[ISC_IS6(fam)] == -1)
- node->node_num[ISC_IS6(fam)]
- = ++radix->num_added_node;
+ int off = ISC_RADIX_OFF(prefix);
+ if (node->node_num[off] == -1)
+ node->node_num[off] =
+ ++radix->num_added_node;
}
}
*target = node;
@@ -468,27 +467,27 @@ isc_radix_insert(isc_radix_tree_t *radix, isc_radix_node_t **target,
return (result);
}
INSIST(node->data[0] == NULL && node->node_num[0] == -1 &&
- node->data[1] == NULL && node->node_num[1] == -1);
+ node->data[1] == NULL && node->node_num[1] == -1 &&
+ node->data[2] == NULL && node->node_num[2] == -1 &&
+ node->data[3] == NULL && node->node_num[3] == -1);
if (source != NULL) {
/* Merging node */
- if (source->node_num[0] != -1) {
- node->node_num[0] = radix->num_added_node +
- source->node_num[0];
- node->data[0] = source->data[0];
- }
- if (source->node_num[1] != -1) {
- node->node_num[1] = radix->num_added_node +
- source->node_num[1];
- node->data[1] = source->data[1];
+ for (i = 0; i < 4; i++) {
+ int cur = radix->num_added_node;
+ if (source->node_num[i] != -1) {
+ node->node_num[i] =
+ source->node_num[i] + cur;
+ node->data[i] = source->data[i];
+ }
}
} else {
+ int next = ++radix->num_added_node;
if (fam == AF_UNSPEC) {
/* "any" or "none" */
- node->node_num[0] = node->node_num[1] =
- ++radix->num_added_node;
+ for (i = 0; i < 4; i++)
+ node->node_num[i] = next;
} else {
- node->node_num[ISC_IS6(fam)] =
- ++radix->num_added_node;
+ node->node_num[ISC_RADIX_OFF(prefix)] = next;
}
}
*target = node;
@@ -518,30 +517,30 @@ isc_radix_insert(isc_radix_tree_t *radix, isc_radix_node_t **target,
}
new_node->parent = NULL;
new_node->l = new_node->r = NULL;
- new_node->node_num[0] = new_node->node_num[1] = -1;
+ for (i = 0; i < 4; i++)
+ new_node->node_num[i] = -1;
radix->num_active_node++;
if (source != NULL) {
/* Merging node */
- if (source->node_num[0] != -1)
- new_node->node_num[0] = radix->num_added_node +
- source->node_num[0];
- if (source->node_num[1] != -1)
- new_node->node_num[1] = radix->num_added_node +
- source->node_num[1];
- new_node->data[0] = source->data[0];
- new_node->data[1] = source->data[1];
+ for (i = 0; i < 4; i++) {
+ int cur = radix->num_added_node;
+ if (source->node_num[i] != -1) {
+ new_node->node_num[i] =
+ source->node_num[i] + cur;
+ new_node->data[i] = source->data[i];
+ }
+ }
} else {
+ int next = ++radix->num_added_node;
if (fam == AF_UNSPEC) {
/* "any" or "none" */
- new_node->node_num[0] = new_node->node_num[1] =
- ++radix->num_added_node;
+ for (i = 0; i < 4; i++)
+ new_node->node_num[i] = next;
} else {
- new_node->node_num[ISC_IS6(fam)] =
- ++radix->num_added_node;
+ new_node->node_num[ISC_RADIX_OFF(prefix)] = next;
}
- new_node->data[0] = NULL;
- new_node->data[1] = NULL;
+ memset(new_node->data, 0, sizeof(new_node->data));
}
if (node->bit == differ_bit) {
@@ -583,8 +582,10 @@ isc_radix_insert(isc_radix_tree_t *radix, isc_radix_node_t **target,
glue->bit = differ_bit;
glue->prefix = NULL;
glue->parent = node->parent;
- glue->data[0] = glue->data[1] = NULL;
- glue->node_num[0] = glue->node_num[1] = -1;
+ for (i = 0; i < 4; i++) {
+ glue->data[i] = NULL;
+ glue->node_num[i] = -1;
+ }
radix->num_active_node++;
if (differ_bit < radix->maxbits &&
BIT_TEST(addr[differ_bit>>3], 0x80 >> (differ_bit & 07))) {
@@ -627,7 +628,7 @@ isc_radix_remove(isc_radix_tree_t *radix, isc_radix_node_t *node) {
_deref_prefix(node->prefix);
node->prefix = NULL;
- node->data[0] = node->data[1] = NULL;
+ memset(node->data, 0, sizeof(node->data));
return;
}
diff --git a/lib/isccfg/aclconf.c b/lib/isccfg/aclconf.c
index 83d5ae6509..03dd4401ec 100644
--- a/lib/isccfg/aclconf.c
+++ b/lib/isccfg/aclconf.c
@@ -696,6 +696,7 @@ cfg_acl_fromconfig2(const cfg_obj_t *caml, const cfg_obj_t *cctx,
/* Network prefix */
isc_netaddr_t addr;
unsigned int bitlen;
+ isc_boolean_t setpos, setecs;
cfg_obj_asnetprefix(ce, &addr, &bitlen);
if (family != 0 && family != addr.family) {
@@ -713,8 +714,10 @@ cfg_acl_fromconfig2(const cfg_obj_t *caml, const cfg_obj_t *cctx,
* If nesting ACLs (nest_level != 0), we negate
* the nestedacl element, not the iptable entry.
*/
- result = dns_iptable_addprefix(iptab, &addr, bitlen,
- ISC_TF(nest_level != 0 || !neg));
+ setpos = ISC_TF(nest_level != 0 || !neg);
+ setecs = cfg_obj_istype(ce, &cfg_type_ecsprefix);
+ result = dns_iptable_addprefix2(iptab, &addr, bitlen,
+ setpos, setecs);
if (result != ISC_R_SUCCESS)
goto cleanup;
diff --git a/lib/isccfg/include/isccfg/namedconf.h b/lib/isccfg/include/isccfg/namedconf.h
index 507da06587..67cca35bbc 100644
--- a/lib/isccfg/include/isccfg/namedconf.h
+++ b/lib/isccfg/include/isccfg/namedconf.h
@@ -54,4 +54,7 @@ LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_sessionkey;
LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_keyref;
/*%< A key reference, used as an ACL element */
+/*%< An EDNS client subnet address, used as an ACL element */
+LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_ecsprefix;
+
#endif /* ISCCFG_NAMEDCONF_H */
diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c
index f8b3bad316..03f4b01d51 100644
--- a/lib/isccfg/namedconf.c
+++ b/lib/isccfg/namedconf.c
@@ -950,9 +950,12 @@ options_clauses[] = {
{ "flush-zones-on-shutdown", &cfg_type_boolean, 0 },
#ifdef HAVE_GEOIP
{ "geoip-directory", &cfg_type_qstringornone, 0 },
+ { "geoip-use-ecs", &cfg_type_boolean, 0 },
#else
{ "geoip-directory", &cfg_type_qstringornone,
CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "geoip-use-ecs", &cfg_type_qstringornone,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
#endif /* HAVE_GEOIP */
{ "has-old-clients", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
{ "heartbeat-interval", &cfg_type_uint32, 0 },
@@ -2281,6 +2284,16 @@ doc_geoip(cfg_printer_t *pctx, const cfg_type_t *type) {
}
#endif /* HAVE_GEOIP */
+/*%
+ * An EDNS client subnet address
+ */
+
+static keyword_type_t ecs_kw = { "ecs", &cfg_type_netprefix };
+LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_ecsprefix = {
+ "edns_client_subnet", parse_keyvalue, print_keyvalue, doc_keyvalue,
+ &cfg_rep_netprefix, &ecs_kw
+};
+
/*%
* A "controls" statement is represented as a map with the multivalued
* "inet" and "unix" clauses.
@@ -2570,6 +2583,9 @@ parse_addrmatchelt(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
if (pctx->token.type == isc_tokentype_string &&
(strcasecmp(TOKEN_STRING(pctx), "key") == 0)) {
CHECK(cfg_parse_obj(pctx, &cfg_type_keyref, ret));
+ } else if (pctx->token.type == isc_tokentype_string &&
+ (strcasecmp(TOKEN_STRING(pctx), "ecs") == 0)) {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_ecsprefix, ret));
} else if (pctx->token.type == isc_tokentype_string &&
(strcasecmp(TOKEN_STRING(pctx), "geoip") == 0)) {
#ifdef HAVE_GEOIP