From 248c6d54bbbfccf18271a96db55193ec1a945f5d Mon Sep 17 00:00:00 2001 From: Gautham <41098605+ahgamut@users.noreply.github.com> Date: Thu, 10 Jun 2021 08:05:44 +0530 Subject: [PATCH] Added getnameinfo with only name lookup (#172) Added necessary constants (DNS_TYPE_PTR, NI_NUMERICHOST etc.). Implementation of getnameinfo is similar to getaddrinfo, with internal functions: * ResolveDnsReverse: performs rDNS query and parses the PTR record * ResolveHostsReverse: reads /etc/hosts to map hostname to address Earlier, the HOSTS.txt would only need to be sorted at loading time, because the only kind of lookup was name -> address. Now since address -> name lookups are also possible, so the HostsTxt struct, the sorting method (and the related tests) was changed to reflect this. --- libc/dns/consts.h | 4 +- libc/dns/dns.h | 5 + libc/dns/gethoststxt.c | 3 +- libc/dns/getnameinfo.c | 107 ++++++++++++++++++++++ libc/dns/hoststxt.h | 9 +- libc/dns/resolvednsreverse.c | 132 +++++++++++++++++++++++++++ libc/dns/resolvehostsreverse.c | 44 +++++++++ libc/dns/resolvehoststxt.c | 2 + libc/dns/sorthoststxt.c | 24 ++++- libc/sock/sock.h | 8 +- test/libc/dns/parsehoststxt_test.c | 2 +- test/libc/dns/resolvehoststxt_test.c | 4 +- 12 files changed, 334 insertions(+), 10 deletions(-) create mode 100644 libc/dns/getnameinfo.c create mode 100644 libc/dns/resolvednsreverse.c create mode 100644 libc/dns/resolvehostsreverse.c diff --git a/libc/dns/consts.h b/libc/dns/consts.h index d49f4c0d835..1bf67dcb0ce 100644 --- a/libc/dns/consts.h +++ b/libc/dns/consts.h @@ -3,7 +3,9 @@ #include "libc/sock/sock.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) -#define DNS_TYPE_A 1 +#define DNS_TYPE_A 0x01 +#define DNS_TYPE_PTR 0x0c + #define DNS_CLASS_IN 1 #define kMinSockaddr4Size \ diff --git a/libc/dns/dns.h b/libc/dns/dns.h index 431b2c4cc36..31764a4f5eb 100644 --- a/libc/dns/dns.h +++ b/libc/dns/dns.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_DNS_DNS_H_ #define COSMOPOLITAN_LIBC_DNS_DNS_H_ +#include "libc/calls/weirdtypes.h" #include "libc/dns/resolvconf.h" #include "libc/sock/sock.h" @@ -56,11 +57,15 @@ struct addrinfo { int getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **) paramsnonnull((4)); int freeaddrinfo(struct addrinfo *); +int getnameinfo(const struct sockaddr *, socklen_t, char *, socklen_t, char *, + socklen_t, int); const char *gai_strerror(int); int CompareDnsNames(const char *, const char *) paramsnonnull(); int PascalifyDnsName(uint8_t *, size_t, const char *) paramsnonnull(); int ResolveDns(const struct ResolvConf *, int, const char *, struct sockaddr *, uint32_t) paramsnonnull(); +int ResolveDnsReverse(const struct ResolvConf *resolvconf, int, const char *, + char *, size_t) paramsnonnull(); struct addrinfo *newaddrinfo(uint16_t); COSMOPOLITAN_C_END_ diff --git a/libc/dns/gethoststxt.c b/libc/dns/gethoststxt.c index ab6a5cb0430..9e45abc020f 100644 --- a/libc/dns/gethoststxt.c +++ b/libc/dns/gethoststxt.c @@ -61,6 +61,7 @@ const struct HostsTxt *GetHostsTxt(void) { init = &g_hoststxt_init; if (!g_hoststxt) { g_hoststxt = &init->ht; + init->ht.sorted_by = HOSTSTXT_NOT_SORTED; init->ht.entries.n = pushpop(ARRAYLEN(init->entries)); init->ht.entries.p = init->entries; init->ht.strings.n = pushpop(ARRAYLEN(init->strings)); @@ -74,7 +75,7 @@ const struct HostsTxt *GetHostsTxt(void) { /* TODO(jart): Elevate robustness. */ } fclose(f); - SortHostsTxt(g_hoststxt); + SortHostsTxt(g_hoststxt, HOSTSTXT_SORTEDBYNAME); } return g_hoststxt; } diff --git a/libc/dns/getnameinfo.c b/libc/dns/getnameinfo.c new file mode 100644 index 00000000000..ef8af10d848 --- /dev/null +++ b/libc/dns/getnameinfo.c @@ -0,0 +1,107 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ This is free and unencumbered software released into the public domain. │ +│ │ +│ Anyone is free to copy, modify, publish, use, compile, sell, or │ +│ distribute this software, either in source code form or as a compiled │ +│ binary, for any purpose, commercial or non-commercial, and by any │ +│ means. │ +│ │ +│ In jurisdictions that recognize copyright laws, the author or authors │ +│ of this software dedicate any and all copyright interest in the │ +│ software to the public domain. We make this dedication for the benefit │ +│ of the public at large and to the detriment of our heirs and │ +│ successors. We intend this dedication to be an overt act of │ +│ relinquishment in perpetuity of all present and future rights to this │ +│ software under copyright law. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ +│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ +│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. │ +│ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR │ +│ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ +│ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR │ +│ OTHER DEALINGS IN THE SOFTWARE. │ +│ │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/safemacros.internal.h" +#include "libc/calls/calls.h" +#include "libc/dns/consts.h" +#include "libc/dns/dns.h" +#include "libc/dns/hoststxt.h" +#include "libc/dns/resolvconf.h" +#include "libc/fmt/conv.h" +#include "libc/fmt/fmt.h" +#include "libc/mem/mem.h" +#include "libc/sock/sock.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/af.h" +#include "libc/sysv/consts/inaddr.h" +#include "libc/sysv/errfuns.h" + +/** + * Resolves name/service for socket address. + * + * @param addr + * @param addrlen + * @param name + * @param namelen + * @param service + * @param servicelen + * @param flags + * + * @return 0 on success or EAI_xxx value + */ +int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *name, + socklen_t namelen, char *service, socklen_t servicelen, + int flags) { + char rdomain[1 + sizeof "255.255.255.255.in-addr.arpa"]; + char info[512]; + int rc, port; + uint8_t *ip; + unsigned int valid_flags; + + valid_flags = + (NI_NAMEREQD | NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN | NI_DGRAM); + + if (flags & ~(valid_flags)) return EAI_BADFLAGS; + if (!name && !service) return EAI_NONAME; + if (addr->sa_family != AF_INET || addrlen < sizeof(struct sockaddr_in)) + return EAI_FAMILY; + + ip = (uint8_t *)&(((struct sockaddr_in *)addr)->sin_addr); + sprintf(rdomain, "%d.%d.%d.%d.in-addr.arpa", ip[3], ip[2], ip[1], ip[0]); + info[0] = '\0'; + if (name != NULL && namelen != 0) { + if ((flags & NI_NUMERICHOST) && (flags & NI_NAMEREQD)) return EAI_NONAME; + + if ((flags & NI_NUMERICHOST) && + inet_ntop(AF_INET, ip, info, sizeof(info)) == NULL) + return EAI_SYSTEM; + else if (!info[0] && ResolveHostsReverse(GetHostsTxt(), AF_INET, ip, info, + sizeof(info)) < 0) + return EAI_SYSTEM; + else if (!info[0] && ResolveDnsReverse(GetResolvConf(), AF_INET, rdomain, + info, sizeof(info)) < 0) + return EAI_SYSTEM; + else if (!info[0] && (flags & NI_NAMEREQD)) + return EAI_NONAME; + else if (!info[0] && inet_ntop(AF_INET, ip, info, sizeof(info)) == NULL) + return EAI_SYSTEM; + + if (strlen(info) + 1 > namelen) return EAI_OVERFLOW; + strcpy(name, info); + } + + port = ntohs(((struct sockaddr_in *)addr)->sin_port); + info[0] = '\0'; + if (service != NULL && servicelen != 0) { + itoa(port, info, 10); + /* TODO: reverse lookup on /etc/services to get name of service */ + if (strlen(info) + 1 > servicelen) return EAI_OVERFLOW; + strcpy(service, info); + } + + return 0; +} diff --git a/libc/dns/hoststxt.h b/libc/dns/hoststxt.h index d03c4b54118..5f702ccc54d 100644 --- a/libc/dns/hoststxt.h +++ b/libc/dns/hoststxt.h @@ -21,7 +21,12 @@ struct HostsTxtStrings { char *p; }; +#define HOSTSTXT_NOT_SORTED 0 +#define HOSTSTXT_SORTEDBYNAME 1 +#define HOSTSTXT_SORTEDBYADDR 2 + struct HostsTxt { + int sorted_by; struct HostsTxtEntries entries; struct HostsTxtStrings strings; }; @@ -29,10 +34,12 @@ struct HostsTxt { const struct HostsTxt *GetHostsTxt(void) returnsnonnull; void FreeHostsTxt(struct HostsTxt **) paramsnonnull(); int ParseHostsTxt(struct HostsTxt *, FILE *) paramsnonnull(); -void SortHostsTxt(struct HostsTxt *) paramsnonnull(); +void SortHostsTxt(struct HostsTxt *, int) paramsnonnull(); int ResolveHostsTxt(const struct HostsTxt *, int, const char *, struct sockaddr *, uint32_t, const char **) paramsnonnull((1, 3)); +int ResolveHostsReverse(const struct HostsTxt *, int, const uint8_t *, char *, + size_t); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/dns/resolvednsreverse.c b/libc/dns/resolvednsreverse.c new file mode 100644 index 00000000000..5af5bfe6762 --- /dev/null +++ b/libc/dns/resolvednsreverse.c @@ -0,0 +1,132 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ This is free and unencumbered software released into the public domain. │ +│ │ +│ Anyone is free to copy, modify, publish, use, compile, sell, or │ +│ distribute this software, either in source code form or as a compiled │ +│ binary, for any purpose, commercial or non-commercial, and by any │ +│ means. │ +│ │ +│ In jurisdictions that recognize copyright laws, the author or authors │ +│ of this software dedicate any and all copyright interest in the │ +│ software to the public domain. We make this dedication for the benefit │ +│ of the public at large and to the detriment of our heirs and │ +│ successors. We intend this dedication to be an overt act of │ +│ relinquishment in perpetuity of all present and future rights to this │ +│ software under copyright law. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ +│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ +│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. │ +│ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR │ +│ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ +│ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR │ +│ OTHER DEALINGS IN THE SOFTWARE. │ +│ │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/bits.h" +#include "libc/calls/calls.h" +#include "libc/dns/consts.h" +#include "libc/dns/dns.h" +#include "libc/dns/dnsheader.h" +#include "libc/dns/dnsquestion.h" +#include "libc/dns/resolvconf.h" +#include "libc/mem/mem.h" +#include "libc/rand/rand.h" +#include "libc/runtime/runtime.h" +#include "libc/sock/sock.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/af.h" +#include "libc/sysv/consts/ipproto.h" +#include "libc/sysv/consts/sock.h" +#include "libc/sysv/errfuns.h" + +#define kMsgMax 512 + +/** + * Performs reverse DNS lookup with IP address. + * + * @param resolvconf can be GetResolvConf() + * @param af can be AF_INET, AF_UNSPEC + * @param name is a reversed IP address string ending with .in-addr.arpa + * @param buf to store the obtained hostname if any + * @param bufsize is size of buf + * + * @return 0 on success, or -1 w/ errno + * @error EAFNOSUPPORT, ENETDOWN, ENAMETOOLONG, EBADMSG + */ +int ResolveDnsReverse(const struct ResolvConf *resolvconf, int af, + const char *name, char *buf, size_t bufsize) { + int rc, fd, n; + struct DnsQuestion q; + struct DnsHeader h, h2; + uint8_t *p, *pe, msg[512]; + uint16_t rtype, rclass, rdlength; + + if (af != AF_INET && af != AF_UNSPEC) return eafnosupport(); + if (!resolvconf->nameservers.i) return 0; + memset(&h, 0, sizeof(h)); + rc = ebadmsg(); + h.id = rand32(); + h.bf1 = 1; /* recursion desired */ + h.qdcount = 1; + q.qname = name; + q.qtype = DNS_TYPE_PTR; + q.qclass = DNS_CLASS_IN; + memset(msg, 0, sizeof(msg)); + SerializeDnsHeader(msg, &h); + + if ((n = SerializeDnsQuestion(msg + 12, 500, &q)) == -1) return -1; + if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) return -1; + if (sendto(fd, msg, 12 + n, 0, resolvconf->nameservers.p, + sizeof(*resolvconf->nameservers.p)) == 12 + n && + (n = read(fd, msg, 512)) >= 12) { + DeserializeDnsHeader(&h2, msg); + if (h2.id == h.id) { + rc = 0; + if (h2.ancount) { + p = msg + 12; + pe = msg + n; + while (p < pe && h2.qdcount) { + p += strnlen((char *)p, pe - p) + 1 + 4; + h2.qdcount--; + } + if (p + 1 < pe) { + if ((p[0] & 0b11000000) == 0b11000000) { /* name pointer */ + p += 2; + } else { + p += strnlen((char *)p, pe - p) + 1; + } + if (p + 2 + 2 + 4 + 2 < pe) { + rtype = READ16BE(p), p += 2; + rclass = READ16BE(p), p += 2; + /* ttl */ p += 4; + rdlength = READ16BE(p), p += 2; + + if (p + rdlength <= pe && rtype == DNS_TYPE_PTR && + rclass == DNS_CLASS_IN) { + if (strnlen((char *)p, pe - p) + 1 > bufsize) + rc = -1; + else { + /* domain name starts with a letter */ + for (; !isalnum((char)(*p)) && p < pe; p++) rdlength--; + for (char *tmp = (char *)p; rdlength > 0 && *tmp != '\0'; + tmp++) { + /* each label is alphanumeric or hyphen + * any other character is assumed separator */ + if (!isalnum(*tmp) && *tmp != '-') *tmp = '.'; + rdlength--; + } + strcpy(buf, (char *)p); + } + } else + rc = -1; + } + } + } + } + } + close(fd); + return rc; +} diff --git a/libc/dns/resolvehostsreverse.c b/libc/dns/resolvehostsreverse.c new file mode 100644 index 00000000000..14eb21bb367 --- /dev/null +++ b/libc/dns/resolvehostsreverse.c @@ -0,0 +1,44 @@ + +#include "libc/alg/alg.h" +#include "libc/dns/consts.h" +#include "libc/dns/dns.h" +#include "libc/dns/hoststxt.h" +#include "libc/sock/sock.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/af.h" +#include "libc/sysv/errfuns.h" + +static int hoststxtcmpaddr(const uint8_t *ip1, const struct HostsTxtEntry *he2) { + uint32_t v1 = *((uint32_t *)ip1), v2 = *((uint32_t *)he2->ip); + return (v1 == v2 ? 0 : (v1 > v2 ? 1 : -1)); +} + +/** + * Finds name associated with address in HOSTS.TXT table. + * + * @param ht can be GetHostsTxt() + * @param af can be AF_INET + * @param ip is IP address in binary (sin_addr) + * @param buf is buffer to store the name + * @param bufsize is length of buf + * + * @return 1 if found, 0 if not found, or -1 w/ errno + * @error EAFNOSUPPORT + */ +int ResolveHostsReverse(const struct HostsTxt *ht, int af, const uint8_t *ip, + char *buf, size_t bufsize) { + struct HostsTxtEntry *entry; + if (af != AF_INET && af != AF_UNSPEC) return eafnosupport(); + if (!ht->entries.p) return -1; + + if (ht->sorted_by != HOSTSTXT_SORTEDBYADDR) + SortHostsTxt(ht, HOSTSTXT_SORTEDBYADDR); + + entry = bsearch(ip, ht->entries.p, ht->entries.i, + sizeof(struct HostsTxtEntry), (void *)hoststxtcmpaddr); + if (entry) { + strncpy(buf, &ht->strings.p[entry->name], bufsize); + return 1; + } + return 0; +} diff --git a/libc/dns/resolvehoststxt.c b/libc/dns/resolvehoststxt.c index f5858b2f303..9fe7082c91a 100644 --- a/libc/dns/resolvehoststxt.c +++ b/libc/dns/resolvehoststxt.c @@ -52,6 +52,8 @@ int ResolveHostsTxt(const struct HostsTxt *ht, int af, const char *name, struct sockaddr_in *addr4; struct HostsTxtEntry *entry; if (af != AF_INET && af != AF_UNSPEC) return eafnosupport(); + if (ht->sorted_by != HOSTSTXT_SORTEDBYNAME) + SortHostsTxt(ht, HOSTSTXT_SORTEDBYNAME); if ((entry = bsearch_r(name, ht->entries.p, ht->entries.i, sizeof(struct HostsTxtEntry), (void *)hoststxtgetcmp, ht->strings.p))) { diff --git a/libc/dns/sorthoststxt.c b/libc/dns/sorthoststxt.c index e32cc452c7a..d4345df4141 100644 --- a/libc/dns/sorthoststxt.c +++ b/libc/dns/sorthoststxt.c @@ -30,6 +30,17 @@ static int cmphoststxt(const struct HostsTxtEntry *e1, return CompareDnsNames(&strings[e1->name], &strings[e2->name]); } +/** + * Compares addresses in HOSTS.TXT table. + * @see ResolveHostsReverse() + */ +static int cmphostsaddr(const struct HostsTxtEntry *e1, + const struct HostsTxtEntry *e2) { + if (e1 == e2) return 0; + uint32_t v1 = *((uint32_t *)e1->ip), v2 = *((uint32_t *)e2->ip); + return (v1 == v2 ? 0 : (v1 > v2 ? 1 : -1)); +} + /** * Sorts entries in HOSTS.TXT table. * @@ -41,9 +52,16 @@ static int cmphoststxt(const struct HostsTxtEntry *e1, * possible to efficiently search for subdomains, once the initial sort * is done. */ -void SortHostsTxt(struct HostsTxt *ht) { +void SortHostsTxt(struct HostsTxt *ht, int sort_by) { if (ht->entries.p) { - qsort_r(ht->entries.p, ht->entries.i, sizeof(*ht->entries.p), - (void *)cmphoststxt, ht->strings.p); + if (sort_by == HOSTSTXT_SORTEDBYNAME) { + qsort_r(ht->entries.p, ht->entries.i, sizeof(*ht->entries.p), + (void *)cmphoststxt, ht->strings.p); + ht->sorted_by = HOSTSTXT_SORTEDBYNAME; + } else { + qsort(ht->entries.p, ht->entries.i, sizeof(*ht->entries.p), + (void *)cmphostsaddr); + ht->sorted_by = HOSTSTXT_SORTEDBYADDR; + } } } diff --git a/libc/sock/sock.h b/libc/sock/sock.h index 9573044aa9a..d54e607d109 100644 --- a/libc/sock/sock.h +++ b/libc/sock/sock.h @@ -9,7 +9,13 @@ COSMOPOLITAN_C_START_ #define INET_ADDRSTRLEN 22 -#define NI_DGRAM 0x10 +#define NI_NUMERICHOST 0x01 +#define NI_NUMERICSERV 0x02 +#define NI_NOFQDN 0x04 +#define NI_NAMEREQD 0x08 +#define NI_DGRAM 0x10 + +#define NI_MAXHOST 0xff #define NI_MAXSERV 0x20 #define htons(u16) bswap_16(u16) diff --git a/test/libc/dns/parsehoststxt_test.c b/test/libc/dns/parsehoststxt_test.c index 8d8adb9b940..e60367e9cae 100644 --- a/test/libc/dns/parsehoststxt_test.c +++ b/test/libc/dns/parsehoststxt_test.c @@ -48,7 +48,7 @@ TEST(ParseHostsTxt, testCorrectlyTokenizesAndSorts) { ASSERT_EQ(1, fwrite(kInput, strlen(kInput), 1, f)); rewind(f); ASSERT_EQ(0, ParseHostsTxt(ht, f)); - SortHostsTxt(ht); + SortHostsTxt(ht, HOSTSTXT_SORTEDBYNAME); ASSERT_EQ(4, ht->entries.i); EXPECT_STREQ("cat.example.", &ht->strings.p[ht->entries.p[0].name]); EXPECT_STREQ("cat.example.", &ht->strings.p[ht->entries.p[0].canon]); diff --git a/test/libc/dns/resolvehoststxt_test.c b/test/libc/dns/resolvehoststxt_test.c index 758ee4970a7..f073edb73d1 100644 --- a/test/libc/dns/resolvehoststxt_test.c +++ b/test/libc/dns/resolvehoststxt_test.c @@ -49,7 +49,7 @@ TEST(ResolveHostsTxt, testBasicLookups) { struct HostsTxt *ht = calloc(1, sizeof(struct HostsTxt)); FILE *f = fmemopen(kInput, strlen(kInput), "r+"); ASSERT_EQ(0, ParseHostsTxt(ht, f)); - SortHostsTxt(ht); + SortHostsTxt(ht, HOSTSTXT_SORTEDBYNAME); ASSERT_EQ(5, ht->entries.i); EXPECT_STREQ("127.0.0.1", EzIp4Lookup(ht, "localhost")); EXPECT_STREQ("203.0.113.1", EzIp4Lookup(ht, "lol")); @@ -66,7 +66,7 @@ TEST(ResolveHostsTxt, testCanonicalize) { struct HostsTxt *ht = calloc(1, sizeof(struct HostsTxt)); FILE *f = fmemopen(kInput, strlen(kInput), "r+"); ASSERT_EQ(0, ParseHostsTxt(ht, f)); - SortHostsTxt(ht); + SortHostsTxt(ht, HOSTSTXT_SORTEDBYNAME); ASSERT_EQ(5, ht->entries.i); EXPECT_STREQ("localhost", EzCanonicalize(ht, "localhost")); EXPECT_STREQ("lol.example.", EzCanonicalize(ht, "lol"));