Skip to content

Commit ec813dd

Browse files
committed
Answer NXDOMAIN for _.xxx.yyy.top.domain
When a DNS query name is too long, it is split into multiple domain components. However, recursive DNS servers that implement QNAME minimization may query each subdomain individually without revealing the full name. As part of this behavior, they often send a preliminary query for a hostname like "_" before attempting to resolve the full name. If this query is not handled correctly, it can lead to timeouts and failed connections. The most effective way to avoid this is to respond with an NXDOMAIN for such queries. Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
1 parent 63c386d commit ec813dd

3 files changed

Lines changed: 100 additions & 0 deletions

File tree

src/dns.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,73 @@ int dns_encode_a_response(char *buf, size_t buflen, struct query *q)
390390
return len;
391391
}
392392

393+
int dns_encode_nxdomain(char *buf, size_t buflen, struct query *q, const char *zone)
394+
{
395+
char rnamebuf[256];
396+
char nsbuf[256];
397+
HEADER *header;
398+
char *p;
399+
400+
if (buflen < sizeof(HEADER))
401+
return 0;
402+
403+
memset(buf, 0, buflen);
404+
header = (HEADER*)buf;
405+
406+
header->id = htons(q->id);
407+
header->qr = 1; // response
408+
header->opcode = 0;
409+
header->aa = 1; // authoritative
410+
header->tc = 0;
411+
header->rd = 0;
412+
header->ra = 0;
413+
header->rcode = 3; // NXDOMAIN
414+
415+
header->qdcount = htons(1);
416+
header->ancount = htons(0);
417+
header->nscount = htons(1); // incluiremos SOA
418+
header->arcount = htons(0);
419+
420+
p = buf + sizeof(HEADER);
421+
422+
// Question section
423+
putname(&p, buflen - (p - buf), q->name);
424+
CHECKLEN(4);
425+
putshort(&p, q->type);
426+
putshort(&p, C_IN);
427+
428+
// Authority section (SOA)
429+
{
430+
CHECKLEN(10);
431+
432+
putname(&p, buflen - (p - buf), zone); // zone name (owner of SOA)
433+
putshort(&p, T_SOA);
434+
putshort(&p, C_IN);
435+
putlong(&p, 60); // TTL
436+
437+
char *startp = p;
438+
p += 2; // skip rdlength (to be filled later)
439+
440+
// Primary NS and responsible mailbox
441+
snprintf(nsbuf, sizeof(nsbuf), "ns.%s", zone);
442+
putname(&p, buflen - (p - buf), nsbuf);
443+
snprintf(rnamebuf, sizeof(rnamebuf), "hostmaster.%s", zone);
444+
putname(&p, buflen - (p - buf), rnamebuf);
445+
446+
// SOA fields: serial, refresh, retry, expire, minimum
447+
putlong(&p, 1); // serial
448+
putlong(&p, 3600); // refresh
449+
putlong(&p, 1800); // retry
450+
putlong(&p, 604800); // expire
451+
putlong(&p, 60); // minimum
452+
453+
int soalen = p - startp - 2;
454+
putshort(&startp, soalen); // fill in rdlength
455+
}
456+
457+
return p - buf;
458+
}
459+
393460
#undef CHECKLEN
394461

395462
unsigned short dns_get_id(char *packet, size_t packetlen)

src/dns.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ int dns_encode(char *, size_t, struct query *, qr_t, const char *, size_t);
3131
int dns_encode_ns_response(char *buf, size_t buflen, struct query *q,
3232
char *topdomain);
3333
int dns_encode_a_response(char *buf, size_t buflen, struct query *q);
34+
int dns_encode_nxdomain(char *buf, size_t buflen, struct query *q, const char *zone);
3435
unsigned short dns_get_id(char *packet, size_t packetlen);
3536
int dns_decode(char *, size_t, struct query *, qr_t, char *, size_t);
3637

src/iodined.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,6 +1597,27 @@ handle_a_request(int dns_fd, struct query *q, int fakeip)
15971597
}
15981598
}
15991599

1600+
static void
1601+
handle_underscore_request(int dns_fd, struct query *q, const char *topdomain)
1602+
{
1603+
char buf[64*1024];
1604+
int len;
1605+
1606+
len = dns_encode_nxdomain(buf, sizeof(buf), q, topdomain);
1607+
if (len < 1) {
1608+
warnx("ens_encode_a_response doesn't fit");
1609+
return;
1610+
}
1611+
1612+
if (debug >= 2) {
1613+
fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes A reply\n",
1614+
format_addr(&q->from, q->fromlen), q->type, q->name, len);
1615+
}
1616+
if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) {
1617+
warn("a reply send error");
1618+
}
1619+
}
1620+
16001621
static void
16011622
forward_query(int bind_fd, struct query *q)
16021623
{
@@ -1719,6 +1740,17 @@ tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd)
17191740
return 0;
17201741
}
17211742

1743+
/* Handle A-type query for _.***.topdomain. It happens when
1744+
*
1745+
* https://github.com/isc-projects/bind9/commit/ae52c2117eba9fa0778125f4e10834d673ab811b
1746+
* */
1747+
if (q.type == T_A &&
1748+
(q.name[0] == '_') &&
1749+
q.name[1] == '.') {
1750+
handle_underscore_request(dns_fd, &q, topdomain);
1751+
return 0;
1752+
}
1753+
17221754
switch (q.type) {
17231755
case T_NULL:
17241756
case T_PRIVATE:

0 commit comments

Comments
 (0)