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