]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
[master] ECS authoritative support
authorEvan Hunt <each@isc.org>
Fri, 29 Aug 2014 05:05:57 +0000 (22:05 -0700)
committerEvan Hunt <each@isc.org>
Fri, 29 Aug 2014 05:05:57 +0000 (22:05 -0700)
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]

35 files changed:
CHANGES
README
bin/named/client.c
bin/named/config.c
bin/named/include/named/client.h
bin/named/include/named/server.h
bin/named/server.c
bin/named/statschannel.c
bin/tests/system/acl/ns2/named6.conf [new file with mode: 0644]
bin/tests/system/acl/ns2/named7.conf [new file with mode: 0644]
bin/tests/system/acl/tests.sh
bin/tests/system/geoip/clean.sh
bin/tests/system/geoip/data/GeoIP.csv
bin/tests/system/geoip/data/GeoIP.dat
bin/tests/system/geoip/data/README
bin/tests/system/geoip/ns2/named1.conf
bin/tests/system/geoip/ns2/named14.conf
bin/tests/system/geoip/setup.sh
bin/tests/system/geoip/tests.sh
doc/arm/Bv9ARM-book.xml
lib/dns/acl.c
lib/dns/geoip.c
lib/dns/include/dns/acl.h
lib/dns/include/dns/geoip.h
lib/dns/include/dns/iptable.h
lib/dns/include/dns/message.h
lib/dns/iptable.c
lib/dns/message.c
lib/dns/tests/geoip_test.c
lib/dns/win32/libdns.def.in
lib/isc/include/isc/radix.h
lib/isc/radix.c
lib/isccfg/aclconf.c
lib/isccfg/include/isccfg/namedconf.h
lib/isccfg/namedconf.c

diff --git a/CHANGES b/CHANGES
index e7c9c89b5483a07af9e2fd2be1c6c360399cd307..47f40b53eeab58d3093c0e5d7fdab2074cf95731 100644 (file)
--- 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 8298ae1f8893973d935cd16e07b99d68bee58e30..23ef8a83282e47a06ff681f6094ac590c3de2a23 100644 (file)
--- 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
index 68d14a942d91f991bbe2bf74ee8b87d9bebfdbc5..f2fe82fa782ede7289851fae20ab9533ff37f90b 100644 (file)
 #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 (allowed(&netaddr, tsig, view->matchclients) &&
-                           allowed(&client->destaddr, tsig,
-                                   view->matchdestinations) &&
-                           !((client->message->flags & DNS_MESSAGEFLAG_RD)
-                             == 0 && view->matchrecursiveonly))
+                       if (sigresult == ISC_R_SUCCESS) {
+                               dns_tsigkey_t *tsigkey;
+
+                               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. */
index f7647e76f77390285e784b0b87ae0a0ddd85fb8c..dfd2852fd87bdfe53b4cf0a4248418ee12634447 100644 (file)
@@ -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\
index c0c3171dc37815718af2bfb174b308358a831587..48ee578c8879187fa4f1cd7e4812128231e6545f 100644 (file)
@@ -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;
 
index e1d1db275be75217270d30e84c5b45f80d5b3f1c..0b241b7471b02893089dba05d78c9ae6f765105e 100644 (file)
@@ -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_max = 52
+       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 = 53
 #else
-       dns_nsstatscounter_max = 46
+       dns_nsstatscounter_max = 47
 #endif
 };
 
index 72bbdd285f9fdf9d3696f8c952b0a9f235badfa4..eb7d02a0dea1550eb0f02f261ca021dcf1abb8e0 100644 (file)
@@ -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 */
 
        /*
index 6e2fe3a3c3aa9155f005e4d1fc02869eed51d1e2..f0584c3ac8308a026e4c1636692adb4350c7a15e 100644 (file)
@@ -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 (file)
index 0000000..1e384fb
--- /dev/null
@@ -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 (file)
index 0000000..1f1c9f3
--- /dev/null
@@ -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";
+       };
+};
index b74c0c9f9f9a33a9ce1ed2b7c90a7d82d82b036d..27efd5cde8daa2808f3b3b8a097a16e5a63fbec0 100644 (file)
@@ -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
index 22d5e74466cfd34e4469a835d4466829c26fab2b..6fa60e3e9389c3580e822a1e1d34e683195268a4 100644 (file)
@@ -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.*
index f158a5b4945c47ba0e9584c0ab2a9b7eb1c10cbe..8e718540df062c26ce95118a11dd19bbcace4cce 100644 (file)
@@ -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
index 027c1757c159e3a9d0e0dddae16689e8fa71bd28..345092f371209a4f2d02181be05eeb21c6440517 100644 (file)
Binary files a/bin/tests/system/geoip/data/GeoIP.dat and b/bin/tests/system/geoip/data/GeoIP.dat differ
index 47a6858f59358444108a45dd04da41f1d9df98e6..cea325b321bdf89ec29abe9d57dbbc1023b88281 100644 (file)
@@ -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
index 367b5c7fa614378157648d4e7840ff8df80af032..d9356c45e1f5e3352b7cd6240a21b0f10a217b50 100644 (file)
@@ -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" {
index f92d25216c2920dd0859182329b688eea50316cd..bce55dc0ef816c30990e2f2e87150304524a5b8f 100644 (file)
@@ -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";
        };
 };
index 5de40eb6efbf4598c74846aedc727d1cfc58c792..2aaeaca15fa727b6314a4d718479632906d8862b 100644 (file)
@@ -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
index 3e916aed31349ac2e22631d4bff755015fefd818..19d05ce66f3f423455097e66f89ab4f4e606a07e 100644 (file)
@@ -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
index e0cf9264f4b17c8f70e4b62be83a894e7826d163..8d34bf0b4f8dcf992be966535995b0d8fa6dabf9 100644 (file)
@@ -3444,66 +3444,6 @@ $ORIGIN 0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.
            </tbody>
          </tgroup>
        </informaltable>
-
-       <para>
-         When <acronym>BIND</acronym> 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:
-         <command>geoip <optional>db <replaceable>database</replaceable></optional> <replaceable>field</replaceable> <replaceable>value</replaceable></command>
-       </para>
-       <para>
-         The <replaceable>field</replaceable> 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".
-       </para>
-       <para>
-         <replaceable>value</replaceable> 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.
-       </para>
-       <para>
-         The <replaceable>database</replaceable> 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 <replaceable>database</replaceable>
-         will force the query to be answered from that database and no
-         other.  If <replaceable>database</replaceable> 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.
-       </para>
-       <para>
-         Some example GeoIP ACLs:
-       </para>
-       <programlisting>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";
-</programlisting>
-
-
       </sect2>
       <sect2>
        <title><command>controls</command> Statement Grammar</title>
@@ -4858,6 +4798,7 @@ badresp:1,adberr:0,findfail:0,valfail:0]
     <optional> allow-update { <replaceable>address_match_list</replaceable> }; </optional>
     <optional> allow-update-forwarding { <replaceable>address_match_list</replaceable> }; </optional>
     <optional> automatic-interface-scan { <replaceable>yes_or_no</replaceable> }; </optional>
+    <optional> geoip-use-ecs <replaceable>yes_or_no</replaceable>;</optional>
     <optional> update-check-ksk <replaceable>yes_or_no</replaceable>; </optional>
     <optional> dnssec-update-mode ( <replaceable>maintain</replaceable> | <replaceable>no-resign</replaceable> ); </optional>
     <optional> dnssec-dnskey-kskonly <replaceable>yes_or_no</replaceable>; </optional>
@@ -6240,6 +6181,20 @@ options {
              </listitem>
            </varlistentry>
 
+           <varlistentry>
+             <term><command>geoip-use-ecs</command></term>
+             <listitem>
+               <para>
+                 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 
+                 <command>geoip-use-ecs</command> <userinput>yes</userinput>.
+               </para>
+             </listitem>
+           </varlistentry>
+
            <varlistentry>
              <term><command>has-old-clients</command></term>
              <listitem>
@@ -16188,11 +16143,11 @@ HOST-127.EXAMPLE. MX 0 .
        <title>Access Control Lists</title>
        <para>
          Access Control Lists (ACLs) are address match lists that
-         you can set up and nickname for future use in <command>allow-notify</command>,
-         <command>allow-query</command>, <command>allow-query-on</command>,
-         <command>allow-recursion</command>, <command>allow-recursion-on</command>,
+         you can set up and nickname for future use in
+         <command>allow-notify</command>, <command>allow-query</command>,
+         <command>allow-query-on</command>, <command>allow-recursion</command>,
          <command>blackhole</command>, <command>allow-transfer</command>,
-         etc.
+         <command>match-clients</command>, etc.
        </para>
        <para>
          Using ACLs allows you to have finer control over who can access
@@ -16202,11 +16157,19 @@ HOST-127.EXAMPLE. MX 0 .
        <para>
          It is a <emphasis>good idea</emphasis> 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.
        </para>
        <para>
-         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.
+       </para>
+       <para>
+         ACLs 
+       <para>
+         Here is an example of ACLs based on client addresses:
        </para>
 
 <programlisting>
@@ -16239,10 +16202,137 @@ zone "example.com" {
 </programlisting>
 
        <para>
-         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".
+       </para>
+       <para>
+         In addition to network addresses and prefixes, which are
+         matched against the source address of the DNS request, ACLs
+         may include <option>key</option> elements, which specify the
+         name of a TSIG or SIG(0) key, or <option>ecs</option>
+         elements, which specify a network prefix but are only matched
+         if that prefix matches an EDNS client subnet option included
+         in the request.
+       </para>
+       <para>
+         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
+         <command>ecs <replaceable>prefix</replaceable></command>
+         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.
+       </para>
+       <para>
+         When <acronym>BIND</acronym> 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:
+         <command>geoip <optional>db <replaceable>database</replaceable></optional> <replaceable>field</replaceable> <replaceable>value</replaceable></command>
+       </para>
+       <para>
+         The <replaceable>field</replaceable> 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".
+       </para>
+       <para>
+         <replaceable>value</replaceable> 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.
+       </para>
+       <para>
+         The <replaceable>database</replaceable> 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 <replaceable>database</replaceable>
+         will force the query to be answered from that database and no
+         other.  If <replaceable>database</replaceable> 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.
+       </para>
+       <para>
+         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 <command>geoip-use-ecs</option> to <literal>no</literal>.
+       </para>
+       <para>
+         Some example GeoIP ACLs:
+       </para>
+       <programlisting>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";
+</programlisting>
+
+       <para>
+         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
+         <command> { 10/8; !10.0.0.1; }</command> 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.
+       </para>
+       <para>
+         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 <emphasis>and</emphasis>
+         only when it is signed with a particular key, use:
+       </para>
+       <programlisting>
+allow-query { !{ !10/8; any; }; key example; };
+</programlisting>
+       <para>
+         Within the nested ACL, any address that is
+         <emphasis>not</emphasis> in the 10/8 network prefix will
+         be rejected, and this will terminate processing of the
+         ACL.  Any address that <emphasis>is</emphasis> 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 <emphasis>both</emphasis> conditions
+         are true.
        </para>
       </sect1>
+
       <sect1>
        <title><command>Chroot</command> and <command>Setuid</command></title>
        <para>
index 880fc3648e1ee1d376f6ceeeb15560fcb3ced287..11efa50e356ecd48aed1f46b8170c3c4be129b87 100644 (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
index ec6beb7acbe276063bb4bb197c500584b2042671..e375e3381938c0ca413952e93b016ab8e745de69 100644 (file)
@@ -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;
index ebcc6c791099c16357843237697a825d9973fc3b..d44af1ec7d4dcf3a6c224d7679be57c6a036b8fa 100644 (file)
@@ -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.
index 35a4036a12060572daf140e98fd85d578c56b927..a65678322398f8f3b196e56e6ec1a4e47b2b37d3 100644 (file)
@@ -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);
 
index 2ce8e18124b971305964546804218175f8928e68..512e73da061ca46287bb4587b638ec45f4582f1b 100644 (file)
@@ -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
  */
index de121c764d38ce3b1298c55419aaf8c2b3b88a99..183ecec7558e8d59ab2b7dd62621f95c8ffcfb8f 100644 (file)
 #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)
index 701950533c9e6ab3d8fc693dd41bc4d6d2eabb7b..9413774947f45e7dc9af5694a32446dd226270a6 100644 (file)
@@ -14,8 +14,6 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: iptable.c,v 1.15 2009/02/18 23:47:48 tbox Exp $ */
-
 #include <config.h>
 
 #include <isc/mem.h>
@@ -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;
index 88c9239eb6d31886295ddbf3a5332cfbcb799a84..06b9068dd48c2bb6ec702710aee1af8ae85b244f 100644 (file)
@@ -15,8 +15,6 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id$ */
-
 /*! \file */
 
 /***
index a39ba727b4750a5a7c4d26b7d8e5e6e07e21218c..60994d2ce949db54da58f48177e0f156f35a8129 100644 (file)
@@ -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();
index 862b2b03feb66658a5f90c56fab295f1c0b45328..422f21c3eefb38b74dc327963c4cb1296faea97e 100644 (file)
@@ -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
index 1c1887f1d0c02f4217dd145a30bc4ea551ca3184..4d0b3b08f1ae20be410086ccd2eb80b4126c0708 100644 (file)
@@ -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;
 
index df26615fa9bfb0f2e74cdb48a85620775c7cc2b2..ebe277048eb0c23c08141a50057fda811878515e 100644 (file)
@@ -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;
        }
 
index 83d5ae6509f01875e9380d47e9607e2daa52ac78..03dd4401ecd47807891adb3601ef5f214bcaa0de 100644 (file)
@@ -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;
 
index 507da0658730d9dc923c0ad80a8cab1ecca850e6..67cca35bbc7607edf992ffe1e4ce86318c35c4b5 100644 (file)
@@ -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 */
index f8b3bad316197d46d49c5db1ce0a4b9fa4dee7b0..03f4b01d5187a9cc49c12ecbc57e6adf1e6b11fc 100644 (file)
@@ -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