diff --git a/src/dns.c b/src/dns.c index 22d9ef4..9721f3e 100644 --- a/src/dns.c +++ b/src/dns.c @@ -390,6 +390,71 @@ int dns_encode_a_response(char *buf, size_t buflen, struct query *q) return len; } +int dns_encode_nxdomain(char *buf, size_t buflen, struct query *q, const char *zone) +{ + char rnamebuf[256]; + char nsbuf[256]; + HEADER *header; + char *soa_start; + char *p; + + if (buflen < sizeof(HEADER)) + return 0; + + memset(buf, 0, buflen); + header = (HEADER*)buf; + + header->id = htons(q->id); + header->qr = 1; // response + header->opcode = 0; + header->aa = 1; // authoritative + header->tc = 0; + header->rd = 0; + header->ra = 0; + header->rcode = 3; // NXDOMAIN + + header->qdcount = htons(1); + header->ancount = htons(0); + header->nscount = htons(1); // We'll include SOA + header->arcount = htons(0); + + p = buf + sizeof(HEADER); + + // Question section + putname(&p, buflen - (p - buf), q->name); + CHECKLEN(4); + putshort(&p, q->type); + putshort(&p, C_IN); + + // Authority section (SOA) + CHECKLEN(10); + putname(&p, buflen - (p - buf), zone); // zone name (owner of SOA) + putshort(&p, T_SOA); + putshort(&p, C_IN); + putlong(&p, 60); // TTL + + soa_start = p; + p += 2; // skip rdlength (to be filled later) + + // Primary NS and responsible mailbox + snprintf(nsbuf, sizeof(nsbuf), "ns.%s", zone); + putname(&p, buflen - (p - buf), nsbuf); + snprintf(rnamebuf, sizeof(rnamebuf), "hostmaster.%s", zone); + putname(&p, buflen - (p - buf), rnamebuf); + + // SOA fields: serial, refresh, retry, expire, minimum + putlong(&p, 1); // serial + putlong(&p, 3600); // refresh + putlong(&p, 1800); // retry + putlong(&p, 604800); // expire + putlong(&p, 60); // minimum + + int soalen = p - soa_start - 2; + putshort(&soa_start, soalen); // fill in rdlength + + return p - buf; +} + #undef CHECKLEN unsigned short dns_get_id(char *packet, size_t packetlen) diff --git a/src/dns.h b/src/dns.h index 660f610..aa179ab 100644 --- a/src/dns.h +++ b/src/dns.h @@ -31,6 +31,7 @@ int dns_encode(char *, size_t, struct query *, qr_t, const char *, size_t); int dns_encode_ns_response(char *buf, size_t buflen, struct query *q, char *topdomain); int dns_encode_a_response(char *buf, size_t buflen, struct query *q); +int dns_encode_nxdomain(char *buf, size_t buflen, struct query *q, const char *zone); unsigned short dns_get_id(char *packet, size_t packetlen); int dns_decode(char *, size_t, struct query *, qr_t, char *, size_t); diff --git a/src/iodined.c b/src/iodined.c index f2d1dc5..6b6d1a4 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -1597,6 +1597,27 @@ handle_a_request(int dns_fd, struct query *q, int fakeip) } } +static void +handle_underscore_request(int dns_fd, struct query *q, const char *topdomain) +{ + char buf[64*1024]; + int len; + + len = dns_encode_nxdomain(buf, sizeof(buf), q, topdomain); + if (len < 1) { + warnx("dns_encode_nxdomain doesn't fit"); + return; + } + + if (debug >= 2) { + fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes NXDOMAIN reply\n", + format_addr(&q->from, q->fromlen), q->type, q->name, len); + } + if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { + warn("nxdomain reply send error"); + } +} + static void forward_query(int bind_fd, struct query *q) { @@ -1719,6 +1740,18 @@ tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) return 0; } + /* Handle A-type query for _.***.topdomain. It happens when + * + * https://datatracker.ietf.org/doc/html/rfc7816 (qname minimisation) + * https://github.com/isc-projects/bind9/commit/ae52c2117eba9fa0778125f4e10834d673ab811b + * */ + if (q.type == T_A && + (q.name[0] == '_') && + q.name[1] == '.') { + handle_underscore_request(dns_fd, &q, topdomain); + return 0; + } + switch (q.type) { case T_NULL: case T_PRIVATE: diff --git a/src/windows.h b/src/windows.h index 065c243..d91126a 100644 --- a/src/windows.h +++ b/src/windows.h @@ -40,6 +40,7 @@ typedef unsigned int in_addr_t; #define T_CNAME DNS_TYPE_CNAME #define T_MX DNS_TYPE_MX #define T_TXT DNS_TYPE_TXT +#define T_SOA DNS_TYPE_SOA #define T_SRV DNS_TYPE_SRV #define C_IN 1