From 74ce4de82ee6193c74d82496e36c72ecaa408ee5 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 11 Apr 2012 12:20:44 +1000 Subject: [PATCH] 3306. [bug] Improve DNS64 reverse zone performance. [RT #28563] 3305. [func] Add wire format lookup method to sdb. [RT #28563] --- CHANGES | 4 + bin/named/builtin.c | 224 +++++++++++++++++++++----------- bin/named/server.c | 2 +- bin/tests/system/dns64/tests.sh | 17 +++ lib/dns/include/dns/sdb.h | 9 +- lib/dns/sdb.c | 84 ++++++++---- 6 files changed, 234 insertions(+), 106 deletions(-) diff --git a/CHANGES b/CHANGES index ffbababe04..22352699a6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +3306. [bug] Improve DNS64 reverse zone performance. [RT #28563] + +3305. [func] Add wire format lookup method to sdb. [RT #28563] + 3304. [bug] Use hmctx, not mctx when freeing rbtdb->heaps. [RT #28571] 3303. [bug] named could die when reloading. [RT #28606] diff --git a/bin/named/builtin.c b/bin/named/builtin.c index 88cc4693bf..7f61be09eb 100644 --- a/bin/named/builtin.c +++ b/bin/named/builtin.c @@ -69,35 +69,79 @@ static builtin_t empty_builtin = { do_empty_lookup, NULL, NULL }; static builtin_t dns64_builtin = { do_dns64_lookup, NULL, NULL }; static dns_sdbimplementation_t *builtin_impl; +static dns_sdbimplementation_t *dns64_impl; -static const char hex[] = "0123456789abcdef"; -static const char HEX[] = "0123456789ABCDEF"; +/* + * Pre computed HEX * 16 or 1 table. + */ +static const unsigned char hex16[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*00*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*10*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*20*/ + 0, 16, 32, 48, 64, 80, 96,112,128,144, 1, 1, 1, 1, 1, 1, /*30*/ + 1,160,176,192,208,224,240, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*40*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*50*/ + 1,160,176,192,208,224,240, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*60*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*70*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*80*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*90*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*A0*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*B0*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*C0*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*D0*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*E0*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /*F0*/ +}; + +const unsigned char decimal[] = "0123456789"; + +static size_t +dns64_rdata(unsigned char *v, size_t start, unsigned char *rdata) { + size_t i, j = 0; + + for (i = 0; i < 4; i++) { + unsigned char c = v[start++]; + if (start == 7) + start++; + if (c > 99) { + rdata[j++] = 3; + rdata[j++] = decimal[c/100]; c = c % 100; + rdata[j++] = decimal[c/10]; c = c % 10; + rdata[j++] = decimal[c]; + } else if (c > 9) { + rdata[j++] = 2; + rdata[j++] = decimal[c/10]; c = c % 10; + rdata[j++] = decimal[c]; + } else { + rdata[j++] = 1; + rdata[j++] = decimal[c]; + } + } + memcpy(&rdata[j], "\07in-addr\04arpa", 14); + return (j + 14); +} static isc_result_t -dns64_cname(const char *zone, const char *name, dns_sdblookup_t *lookup) { - size_t zlen, nlen, j; - const char *s; - unsigned char v[16]; +dns64_cname(const dns_name_t *zone, const dns_name_t *name, + dns_sdblookup_t *lookup) +{ + size_t zlen, nlen, j, len; + unsigned char v[16], n; unsigned int i; - char reverse[sizeof("123.123.123.123.in-addr.arpa.")]; + unsigned char rdata[sizeof("123.123.123.123.in-addr.arpa.")]; + unsigned char *ndata; /* - * The sum the length of the relative name and the length of the zone - * name for a IPv6 reverse lookup comes to 71. + * The combined length of the zone and name is 74. * - * The reverse of 2001::10.0.0.1 (dns64 2001::/96) has a zone of - * "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.2.ip6.arpa" - * and a name of "1.0.0.0.0.0.a.0". The sum of the lengths of these - * two strings is 71. + * The minimum zone length is 10 ((3)ip6(4)arpa(0)). * - * The minimum length for a ip6.arpa zone name is 8. - * - * The length of name should always be odd as we are expecting + * The length of name should always be even as we are expecting * a series of nibbles. */ - zlen = strlen(zone); - nlen = strlen(name); - if ((zlen + nlen) > 71U || zlen < 8U || (nlen % 2) != 1U) + zlen = zone->length; + nlen = name->length; + if ((zlen + nlen) > 74U || zlen < 10U || (nlen % 2) != 0U) return (ISC_R_NOTFOUND); /* @@ -116,25 +160,20 @@ dns64_cname(const char *zone, const char *name, dns_sdblookup_t *lookup) { * are byte aligned and we correctly return ISC_R_NOTFOUND or * ISC_R_SUCCESS. We will not generate a CNAME in this case. */ - i = (nlen % 4) == 1U ? 1 : 0; + ndata = name->ndata; + i = (nlen % 4) == 2U ? 1 : 0; j = nlen; memset(v, 0, sizeof(v)); - while (j >= 1U) { + while (j != 0) { INSIST((i/2) < sizeof(v)); - if (j > 1U && name[1] != '.') + if (ndata[0] != 1) return (ISC_R_NOTFOUND); - v[i/2] >>= 4; - if ((s = strchr(hex, name[0])) != NULL) - v[i/2] |= (s - hex) << 4; - else if ((s = strchr(HEX, name[0])) != NULL) - v[i/2] |= (s - HEX) << 4; - else + n = hex16[ndata[1]&0xff]; + if (n == 1) return (ISC_R_NOTFOUND); - if (j > 1U) - j -= 2; - else - j -= 1; - name += 2; + v[i/2] = n | (v[i/2]>>4); + j -= 2; + ndata += 2; i++; } @@ -144,90 +183,91 @@ dns64_cname(const char *zone, const char *name, dns_sdblookup_t *lookup) { * it corresponds to a empty node in the zone or there should be * a CNAME. */ +#define ZLEN(x) (10 + (x)/2) switch (zlen) { - case 24: /* prefix len 32 */ - /* - * If the total length is not 71 then this is a empty node - * so return success. - */ - if (nlen + zlen != 71U) - return (ISC_R_SUCCESS); - snprintf(reverse, sizeof(reverse), "%u.%u.%u.%u.in-addr.arpa.", - v[8], v[9], v[10], v[11]); - break; - case 28: /* prefix len 40 */ + case ZLEN(32): /* prefix len 32 */ /* * The nibbles that map to this byte must be zero for 'name' * to exist in the zone. */ - if (nlen > 11U && v[nlen/4 - 3] != 0) + if (nlen > 16U && v[(nlen-1)/4 - 4] != 0) return (ISC_R_NOTFOUND); /* - * If the total length is not 71 then this is a empty node + * If the total length is not 74 then this is a empty node * so return success. */ - if (nlen + zlen != 71U) + if (nlen + zlen != 74U) return (ISC_R_SUCCESS); - snprintf(reverse, sizeof(reverse), "%u.%u.%u.%u.in-addr.arpa.", - v[6], v[8], v[9], v[10]); + len = dns64_rdata(v, 8, rdata); break; - case 32: /* prefix len 48 */ + case ZLEN(40): /* prefix len 40 */ /* * The nibbles that map to this byte must be zero for 'name' * to exist in the zone. */ - if (nlen > 7U && v[nlen/4 - 2] != 0) + if (nlen > 12U && v[(nlen-1)/4 - 3] != 0) return (ISC_R_NOTFOUND); /* - * If the total length is not 71 then this is a empty node + * If the total length is not 74 then this is a empty node * so return success. */ - if (nlen + zlen != 71U) + if (nlen + zlen != 74U) return (ISC_R_SUCCESS); - snprintf(reverse, sizeof(reverse), "%u.%u.%u.%u.in-addr.arpa.", - v[5], v[6], v[8], v[9]); + len = dns64_rdata(v, 6, rdata); break; - case 36: /* prefix len 56 */ + case ZLEN(48): /* prefix len 48 */ /* * The nibbles that map to this byte must be zero for 'name' * to exist in the zone. */ - if (nlen > 3U && v[nlen/4 - 1] != 0) + if (nlen > 8U && v[(nlen-1)/4 - 2] != 0) return (ISC_R_NOTFOUND); /* - * If the total length is not 71 then this is a empty node + * If the total length is not 74 then this is a empty node * so return success. */ - if (nlen + zlen != 71U) + if (nlen + zlen != 74U) return (ISC_R_SUCCESS); - snprintf(reverse, sizeof(reverse), "%u.%u.%u.%u.in-addr.arpa.", - v[4], v[5], v[6], v[8]); + len = dns64_rdata(v, 5, rdata); break; - case 40: /* prefix len 64 */ + case ZLEN(56): /* prefix len 56 */ /* * The nibbles that map to this byte must be zero for 'name' * to exist in the zone. */ - if (v[nlen/4] != 0) + if (nlen > 4U && v[(nlen-1)/4 - 1] != 0) return (ISC_R_NOTFOUND); /* - * If the total length is not 71 then this is a empty node + * If the total length is not 74 then this is a empty node * so return success. */ - if (nlen + zlen != 71U) + if (nlen + zlen != 74U) return (ISC_R_SUCCESS); - snprintf(reverse, sizeof(reverse), "%u.%u.%u.%u.in-addr.arpa.", - v[3], v[4], v[5], v[6]); + len = dns64_rdata(v, 4, rdata); break; - case 56: /* prefix len 96 */ + case ZLEN(64): /* prefix len 64 */ /* - * If the total length is not 71 then this is a empty node + * The nibbles that map to this byte must be zero for 'name' + * to exist in the zone. + */ + if (v[(nlen-1)/4] != 0) + return (ISC_R_NOTFOUND); + /* + * If the total length is not 74 then this is a empty node * so return success. */ - if (nlen + zlen != 71U) + if (nlen + zlen != 74U) return (ISC_R_SUCCESS); - snprintf(reverse, sizeof(reverse), "%u.%u.%u.%u.in-addr.arpa.", - v[0], v[1], v[2], v[3]); + len = dns64_rdata(v, 3, rdata); + break; + case ZLEN(96): /* prefix len 96 */ + /* + * If the total length is not 74 then this is a empty node + * so return success. + */ + if (nlen + zlen != 74U) + return (ISC_R_SUCCESS); + len = dns64_rdata(v, 0, rdata); break; default: /* @@ -236,7 +276,7 @@ dns64_cname(const char *zone, const char *name, dns_sdblookup_t *lookup) { */ return (ISC_R_NOTFOUND); } - return (dns_sdb_putrr(lookup, "CNAME", 600, reverse)); + return (dns_sdb_putrdata(lookup, dns_rdatatype_cname, 600, rdata, len)); } static isc_result_t @@ -252,12 +292,26 @@ builtin_lookup(const char *zone, const char *name, void *dbdata, if (strcmp(name, "@") == 0) return (b->do_lookup(lookup)); - else if (b->do_lookup == do_dns64_lookup) - return (dns64_cname(zone, name, lookup)); else return (ISC_R_NOTFOUND); } +static isc_result_t +dns64_lookup(const dns_name_t *zone, const dns_name_t *name, void *dbdata, + dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +{ + builtin_t *b = (builtin_t *) dbdata; + + UNUSED(methods); + UNUSED(clientinfo); + + if (name->labels == 0 && name->length == 0) + return (b->do_lookup(lookup)); + else + return (dns64_cname(zone, name, lookup)); +} + static isc_result_t put_txt(dns_sdblookup_t *lookup, const char *text) { unsigned char buf[256]; @@ -486,7 +540,17 @@ static dns_sdbmethods_t builtin_methods = { builtin_authority, NULL, /* allnodes */ builtin_create, - builtin_destroy + builtin_destroy, + NULL +}; + +static dns_sdbmethods_t dns64_methods = { + NULL, + builtin_authority, + NULL, /* allnodes */ + builtin_create, + builtin_destroy, + dns64_lookup, }; isc_result_t @@ -496,11 +560,17 @@ ns_builtin_init(void) { DNS_SDBFLAG_RELATIVERDATA, ns_g_mctx, &builtin_impl) == ISC_R_SUCCESS); + RUNTIME_CHECK(dns_sdb_register("_dns64", &dns64_methods, NULL, + DNS_SDBFLAG_RELATIVEOWNER | + DNS_SDBFLAG_RELATIVERDATA | + DNS_SDBFLAG_DNS64, + ns_g_mctx, &dns64_impl) + == ISC_R_SUCCESS); return (ISC_R_SUCCESS); } void ns_builtin_deinit(void) { dns_sdb_unregister(&builtin_impl); + dns_sdb_unregister(&dns64_impl); } - diff --git a/bin/named/server.c b/bin/named/server.c index 9e35ac0733..ddfb730f4c 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -1368,7 +1368,7 @@ dns64_reverse(dns_view_t *view, isc_mem_t *mctx, isc_netaddr_t *na, { char *cp; char reverse[48+sizeof("ip6.arpa.")]; - const char *dns64_dbtype[4] = { "_builtin", "dns64", ".", "." }; + const char *dns64_dbtype[4] = { "_dns64", "dns64", ".", "." }; const char *sep = ": view "; const char *viewname = view->name; const unsigned char *s6; diff --git a/bin/tests/system/dns64/tests.sh b/bin/tests/system/dns64/tests.sh index 2bad86cdb0..c776ea40d1 100644 --- a/bin/tests/system/dns64/tests.sh +++ b/bin/tests/system/dns64/tests.sh @@ -1264,6 +1264,23 @@ do status=`expr $status + $ret` done +rev=`$ARPANAME 2001:aaaa::10.0.0.1` +regex='..\(.*.IP6.ARPA\)' +rev=`expr "${rev}" : "${regex}"` +fin=`expr "${rev}" : "............${regex}"` +while test "${rev}" != "${fin}" +do + ret=0 + echo "I: checking $rev ($n)" + $DIG $DIGOPTS $rev ptr @10.53.0.2 > dig.out.ns2.test$n || ret=1 + grep -i "status: NOERROR" dig.out.ns2.test$n > /dev/null || ret=1 + grep -i "ANSWER: 0," dig.out.ns2.test$n > /dev/null || ret=1 + n=`expr $n + 1` + if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` + rev=`expr "${rev}" : "${regex}"` +done + echo "I: checking dns64-server and dns64-contact ($n)" $DIG $DIGOPTS soa 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.a.a.a.a.1.0.0.2.ip6.arpa @10.53.0.2 > dig.out.ns2.test$n || ret=1 grep "SOA.dns64.example.net..hostmaster.example.net." dig.out.ns2.test$n > /dev/null || ret=1 diff --git a/lib/dns/include/dns/sdb.h b/lib/dns/include/dns/sdb.h index 1f142bee88..75dfd7e13a 100644 --- a/lib/dns/include/dns/sdb.h +++ b/lib/dns/include/dns/sdb.h @@ -62,7 +62,12 @@ typedef isc_result_t dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo); - +typedef isc_result_t +(*dns_sdblookup2func_t)(const dns_name_t *zone, const dns_name_t *name, + void *dbdata, dns_sdblookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); + typedef isc_result_t (*dns_sdbauthorityfunc_t)(const char *zone, void *dbdata, dns_sdblookup_t *); @@ -84,6 +89,7 @@ typedef struct dns_sdbmethods { dns_sdballnodesfunc_t allnodes; dns_sdbcreatefunc_t create; dns_sdbdestroyfunc_t destroy; + dns_sdblookup2func_t lookup2; } dns_sdbmethods_t; /*** @@ -95,6 +101,7 @@ ISC_LANG_BEGINDECLS #define DNS_SDBFLAG_RELATIVEOWNER 0x00000001U #define DNS_SDBFLAG_RELATIVERDATA 0x00000002U #define DNS_SDBFLAG_THREADSAFE 0x00000004U +#define DNS_SDBFLAG_DNS64 0x00000008U isc_result_t dns_sdb_register(const char *drivername, const dns_sdbmethods_t *methods, diff --git a/lib/dns/sdb.c b/lib/dns/sdb.c index 43017b7bd2..25f76364d5 100644 --- a/lib/dns/sdb.c +++ b/lib/dns/sdb.c @@ -216,12 +216,13 @@ dns_sdb_register(const char *drivername, const dns_sdbmethods_t *methods, REQUIRE(drivername != NULL); REQUIRE(methods != NULL); - REQUIRE(methods->lookup != NULL); + REQUIRE(methods->lookup != NULL || methods->lookup2 != NULL); REQUIRE(mctx != NULL); REQUIRE(sdbimp != NULL && *sdbimp == NULL); REQUIRE((flags & ~(DNS_SDBFLAG_RELATIVEOWNER | DNS_SDBFLAG_RELATIVERDATA | - DNS_SDBFLAG_THREADSAFE)) == 0); + DNS_SDBFLAG_THREADSAFE| + DNS_SDBFLAG_DNS64)) == 0); imp = isc_mem_get(mctx, sizeof(dns_sdbimplementation_t)); if (imp == NULL) @@ -280,8 +281,9 @@ initial_size(unsigned int len) { } isc_result_t -dns_sdb_putrdata(dns_sdblookup_t *lookup, dns_rdatatype_t typeval, dns_ttl_t ttl, - const unsigned char *rdatap, unsigned int rdlen) +dns_sdb_putrdata(dns_sdblookup_t *lookup, dns_rdatatype_t typeval, + dns_ttl_t ttl, const unsigned char *rdatap, + unsigned int rdlen) { dns_rdatalist_t *rdatalist; dns_rdata_t *rdata; @@ -338,7 +340,6 @@ dns_sdb_putrdata(dns_sdblookup_t *lookup, dns_rdatatype_t typeval, dns_ttl_t ttl return (result); } - isc_result_t dns_sdb_putrr(dns_sdblookup_t *lookup, const char *type, dns_ttl_t ttl, const char *data) @@ -738,6 +739,8 @@ findnodeext(dns_db_t *db, dns_name_t *name, isc_boolean_t create, char namestr[DNS_NAME_MAXTEXT + 1]; isc_boolean_t isorigin; dns_sdbimplementation_t *imp; + dns_name_t relname; + unsigned int labels; REQUIRE(VALID_SDB(sdb)); REQUIRE(create == ISC_FALSE); @@ -748,34 +751,47 @@ findnodeext(dns_db_t *db, dns_name_t *name, isc_boolean_t create, imp = sdb->implementation; - isc_buffer_init(&b, namestr, sizeof(namestr)); - if ((imp->flags & DNS_SDBFLAG_RELATIVEOWNER) != 0) { - dns_name_t relname; - unsigned int labels; + isorigin = dns_name_equal(name, &sdb->common.origin); - labels = dns_name_countlabels(name) - - dns_name_countlabels(&db->origin); - dns_name_init(&relname, NULL); - dns_name_getlabelsequence(name, 0, labels, &relname); - result = dns_name_totext(&relname, ISC_TRUE, &b); - if (result != ISC_R_SUCCESS) - return (result); + if (imp->methods->lookup2 != NULL) { + if ((imp->flags & DNS_SDBFLAG_RELATIVEOWNER) != 0) { + labels = dns_name_countlabels(name) - + dns_name_countlabels(&db->origin); + dns_name_init(&relname, NULL); + dns_name_getlabelsequence(name, 0, labels, &relname); + name = &relname; + } } else { - result = dns_name_totext(name, ISC_TRUE, &b); - if (result != ISC_R_SUCCESS) - return (result); + isc_buffer_init(&b, namestr, sizeof(namestr)); + if ((imp->flags & DNS_SDBFLAG_RELATIVEOWNER) != 0) { + + labels = dns_name_countlabels(name) - + dns_name_countlabels(&db->origin); + dns_name_init(&relname, NULL); + dns_name_getlabelsequence(name, 0, labels, &relname); + result = dns_name_totext(&relname, ISC_TRUE, &b); + if (result != ISC_R_SUCCESS) + return (result); + } else { + result = dns_name_totext(name, ISC_TRUE, &b); + if (result != ISC_R_SUCCESS) + return (result); + } + isc_buffer_putuint8(&b, 0); } - isc_buffer_putuint8(&b, 0); result = createnode(sdb, &node); if (result != ISC_R_SUCCESS) return (result); - isorigin = dns_name_equal(name, &sdb->common.origin); - MAYBE_LOCK(sdb); - result = imp->methods->lookup(sdb->zone, namestr, sdb->dbdata, - node, methods, clientinfo); + if (imp->methods->lookup2 != NULL) + result = imp->methods->lookup2(&sdb->common.origin, name, + sdb->dbdata, node, methods, + clientinfo); + else + result = imp->methods->lookup(sdb->zone, namestr, sdb->dbdata, + node, methods, clientinfo); MAYBE_UNLOCK(sdb); if (result != ISC_R_SUCCESS && !(result == ISC_R_NOTFOUND && @@ -814,13 +830,13 @@ findext(dns_db_t *db, dns_name_t *name, dns_dbversion_t *version, unsigned int nlabels, olabels; isc_result_t result; unsigned int i; + unsigned int flags; REQUIRE(VALID_SDB(sdb)); REQUIRE(nodep == NULL || *nodep == NULL); REQUIRE(version == NULL || version == (void *) &dummy); UNUSED(options); - UNUSED(sdb); if (!dns_name_issubdomain(name, &db->origin)) return (DNS_R_NXDOMAIN); @@ -837,8 +853,9 @@ findext(dns_db_t *db, dns_name_t *name, dns_dbversion_t *version, } result = DNS_R_NXDOMAIN; - - for (i = olabels; i <= nlabels; i++) { + flags = sdb->implementation->flags; + i = (flags & DNS_SDBFLAG_DNS64) != 0 ? nlabels : olabels; + for (; i <= nlabels; i++) { /* * Look up the next label. */ @@ -857,6 +874,18 @@ findext(dns_db_t *db, dns_name_t *name, dns_dbversion_t *version, if (result != ISC_R_SUCCESS) return (result); + /* + * DNS64 zone's don't have DNAME or NS records. + */ + if ((flags & DNS_SDBFLAG_DNS64) != 0) + goto skip; + + /* + * DNS64 zone's don't have DNAME or NS records. + */ + if ((flags & DNS_SDBFLAG_DNS64) != 0) + goto skip; + /* * Look for a DNAME at the current label, unless this is * the qname. @@ -906,6 +935,7 @@ findext(dns_db_t *db, dns_name_t *name, dns_dbversion_t *version, continue; } + skip: /* * If we're looking for ANY, we're done. */