Don't return NXDOMAIN to empty non-terminals.

When a record is defined locally, eg an A record for one.two.example then
we already know that if we forward, eg an AAAA query for one.two.example,
and get back NXDOMAIN, then we need to alter that to NODATA. This is handled
by  check_for_local_domain(). But, if we forward two.example, because
one.two.example exists, then the answer to two.example should also be
a NODATA.

For most local records this is easy, just to substring matching.
for A, AAAA and CNAME records that are in the cache, it's more difficult.
The cache has no efficient way to find such records. The fix is to
insert empty (none of F_IPV4, F_IPV6 F_CNAME set) records for each
non-terminal.

The same considerations apply in auth mode, and the same basic mechanism
is used there too.
This commit is contained in:
Simon Kelley
2018-08-21 17:46:52 +01:00
parent c822620967
commit b6f926fbef
5 changed files with 179 additions and 28 deletions

View File

@@ -103,7 +103,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
{
char *name = daemon->namebuff;
unsigned char *p, *ansp;
int qtype, qclass;
int qtype, qclass, rc;
int nameoffset, axfroffset = 0;
int q, anscount = 0, authcount = 0;
struct crec *crecp;
@@ -283,11 +283,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
}
for (rec = daemon->mxnames; rec; rec = rec->next)
if (!rec->issrv && hostname_isequal(name, rec->name))
if (!rec->issrv && (rc = hostname_issubdomain(name, rec->name)))
{
nxdomain = 0;
if (qtype == T_MX)
if (rc == 2 && qtype == T_MX)
{
found = 1;
log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>");
@@ -298,11 +298,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
}
for (move = NULL, up = &daemon->mxnames, rec = daemon->mxnames; rec; rec = rec->next)
if (rec->issrv && hostname_isequal(name, rec->name))
if (rec->issrv && (rc = hostname_issubdomain(name, rec->name)))
{
nxdomain = 0;
if (qtype == T_SRV)
if (rc == 2 && qtype == T_SRV)
{
found = 1;
log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>");
@@ -333,10 +333,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
}
for (txt = daemon->rr; txt; txt = txt->next)
if (hostname_isequal(name, txt->name))
if ((rc = hostname_issubdomain(name, txt->name)))
{
nxdomain = 0;
if (txt->class == qtype)
if (rc == 2 && txt->class == qtype)
{
found = 1;
log_query(F_CONFIG | F_RRNAME, name, NULL, "<RR>");
@@ -347,10 +347,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
}
for (txt = daemon->txt; txt; txt = txt->next)
if (txt->class == C_IN && hostname_isequal(name, txt->name))
if (txt->class == C_IN && (rc = hostname_issubdomain(name, txt->name)))
{
nxdomain = 0;
if (qtype == T_TXT)
if (rc == 2 && qtype == T_TXT)
{
found = 1;
log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>");
@@ -361,10 +361,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
}
for (na = daemon->naptr; na; na = na->next)
if (hostname_isequal(name, na->name))
if ((rc = hostname_issubdomain(name, na->name)))
{
nxdomain = 0;
if (qtype == T_NAPTR)
if (rc == 2 && qtype == T_NAPTR)
{
found = 1;
log_query(F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>");
@@ -384,13 +384,13 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
#endif
for (intr = daemon->int_names; intr; intr = intr->next)
if (hostname_isequal(name, intr->name))
if ((rc = hostname_issubdomain(name, intr->name)))
{
struct addrlist *addrlist;
nxdomain = 0;
if (flag)
if (rc == 2 && flag)
for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == qtype &&
(local_query || filter_zone(zone, flag, &addrlist->addr)))
@@ -567,6 +567,8 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
goto cname_restart;
}
else if (cache_find_non_terminal(name, now))
nxdomain = 0;
log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL);
}

View File

@@ -25,6 +25,8 @@ static int insert_error;
static union bigname *big_free = NULL;
static int bignames_left, hash_size;
static void make_non_terminals(struct crec *source);
/* type->string mapping: this is also used by the name-hash function as a mixing table. */
static const struct {
unsigned int type;
@@ -648,6 +650,20 @@ void cache_end_insert(void)
new_chain = NULL;
}
int cache_find_non_terminal(char *name, time_t now)
{
struct crec *crecp;
for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next)
if (!is_outdated_cname_pointer(crecp) &&
!is_expired(now, crecp) &&
(crecp->flags & F_FORWARD) &&
hostname_isequal(name, cache_get_name(crecp)))
return 1;
return 0;
}
struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot)
{
struct crec *ans;
@@ -818,6 +834,8 @@ static void add_hosts_cname(struct crec *target)
crec->addr.cname.uid = target->uid;
crec->uid = UID_NONE;
cache_hash(crec);
make_non_terminals(crec);
add_hosts_cname(crec); /* handle chains */
}
}
@@ -889,6 +907,7 @@ static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrl
cache->uid = index;
memcpy(&cache->addr.addr, addr, addrlen);
cache_hash(cache);
make_non_terminals(cache);
/* don't need to do alias stuff for second and subsequent addresses. */
if (!nameexists)
@@ -1102,6 +1121,7 @@ void cache_reload(void)
cache->addr.cname.uid = SRC_INTERFACE;
cache->uid = UID_NONE;
cache_hash(cache);
make_non_terminals(cache);
add_hosts_cname(cache); /* handle chains */
}
@@ -1119,6 +1139,7 @@ void cache_reload(void)
cache->addr.ds.digest = ds->digest_type;
cache->uid = ds->class;
cache_hash(cache);
make_non_terminals(cache);
}
#endif
@@ -1234,6 +1255,7 @@ static void add_dhcp_cname(struct crec *target, time_t ttd)
aliasc->addr.cname.uid = target->uid;
aliasc->uid = UID_NONE;
cache_hash(aliasc);
make_non_terminals(aliasc);
add_dhcp_cname(aliasc, ttd);
}
}
@@ -1322,12 +1344,104 @@ void cache_add_dhcp_entry(char *host_name, int prot,
crec->name.namep = host_name;
crec->uid = UID_NONE;
cache_hash(crec);
make_non_terminals(crec);
add_dhcp_cname(crec, ttd);
}
}
#endif
/* Called when we put a local or DHCP name into the cache.
Creates empty cache entries for subnames (ie,
for three.two.one, for two.one and one), without
F_IPV4 or F_IPV6 or F_CNAME set. These convert
NXDOMAIN answers to NoData ones. */
static void make_non_terminals(struct crec *source)
{
char *name = cache_get_name(source);
struct crec* crecp, *tmp, **up;
int type = F_HOSTS | F_CONFIG;
#ifdef HAVE_DHCP
if (source->flags & F_DHCP)
type = F_DHCP;
#endif
/* First delete any empty entries for our new real name. Note that
we only delete empty entries deriving from DHCP for a new DHCP-derived
entry and vice-versa for HOSTS and CONFIG. This ensures that
non-terminals from DHCP go when we reload DHCP and
for HOSTS/CONFIG when we re-read. */
for (up = hash_bucket(name), crecp = *up; crecp; crecp = tmp)
{
tmp = crecp->hash_next;
if (!is_outdated_cname_pointer(crecp) &&
(crecp->flags & F_FORWARD) &&
(crecp->flags & type) &&
!(crecp->flags & (F_IPV4 | F_IPV6 | F_CNAME)) &&
hostname_isequal(name, cache_get_name(crecp)))
{
*up = crecp->hash_next;
#ifdef HAVE_DHCP
if (type & F_DHCP)
{
crecp->next = dhcp_spare;
dhcp_spare = crecp;
}
else
#endif
free(crecp);
break;
}
else
up = &crecp->hash_next;
}
while ((name = strchr(name, '.')))
{
name++;
/* Look for one existing, don't need another */
for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next)
if (!is_outdated_cname_pointer(crecp) &&
(crecp->flags & F_FORWARD) &&
(crecp->flags & type) &&
hostname_isequal(name, cache_get_name(crecp)))
break;
if (crecp)
{
/* If the new name expires later, transfer that time to
empty non-terminal entry. */
if (!(crecp->flags & F_IMMORTAL))
{
if (source->flags & F_IMMORTAL)
crecp->flags |= F_IMMORTAL;
else if (difftime(crecp->ttd, source->ttd) < 0)
crecp->ttd = source->ttd;
}
continue;
}
#ifdef HAVE_DHCP
if ((source->flags & F_DHCP) && dhcp_spare)
{
crecp = dhcp_spare;
dhcp_spare = dhcp_spare->next;
}
else
#endif
crecp = whine_malloc(sizeof(struct crec));
*crecp = *source;
crecp->flags &= ~(F_IPV4 | F_IPV6 | F_CNAME | F_REVERSE);
crecp->flags |= F_NAMEP;
crecp->name.namep = name;
cache_hash(crecp);
}
}
#ifndef NO_ID
int cache_make_stat(struct txt_record *t)
{
@@ -1443,7 +1557,6 @@ static char *sanitise(char *name)
void dump_cache(time_t now)
{
struct server *serv, *serv1;
char *t = "";
my_syslog(LOG_INFO, _("time %lu"), (unsigned long)now);
my_syslog(LOG_INFO, _("cache size %d, %d/%d cache insertions re-used unexpired cache entries."),
@@ -1489,6 +1602,7 @@ void dump_cache(time_t now)
for (i=0; i<hash_size; i++)
for (cache = hash_table[i]; cache; cache = cache->hash_next)
{
char *t = " ";
char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache);
*a = 0;
if (strlen(n) == 0 && !(cache->flags & F_REVERSE))

View File

@@ -1135,6 +1135,7 @@ void next_uid(struct crec *crecp);
void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg);
char *record_source(unsigned int index);
char *querystr(char *desc, unsigned short type);
int cache_find_non_terminal(char *name, time_t now);
struct crec *cache_find_by_addr(struct crec *crecp,
struct all_addr *addr, time_t now,
unsigned int prot);
@@ -1250,6 +1251,7 @@ void *whine_malloc(size_t size);
int sa_len(union mysockaddr *addr);
int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2);
int hostname_isequal(const char *a, const char *b);
int hostname_issubdomain(char *a, char *b);
time_t dnsmasq_time(void);
int netmask_length(struct in_addr mask);
int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask);

View File

@@ -986,40 +986,35 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
/* check if name matches local names ie from /etc/hosts or DHCP or local mx names. */
int check_for_local_domain(char *name, time_t now)
{
struct crec *crecp;
struct mx_srv_record *mx;
struct txt_record *txt;
struct interface_name *intr;
struct ptr_record *ptr;
struct naptr *naptr;
/* Note: the call to cache_find_by_name is intended to find any record which matches
ie A, AAAA, CNAME. */
if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_NO_RR)) &&
(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
return 1;
for (naptr = daemon->naptr; naptr; naptr = naptr->next)
if (hostname_isequal(name, naptr->name))
if (hostname_issubdomain(name, naptr->name))
return 1;
for (mx = daemon->mxnames; mx; mx = mx->next)
if (hostname_isequal(name, mx->name))
if (hostname_issubdomain(name, mx->name))
return 1;
for (txt = daemon->txt; txt; txt = txt->next)
if (hostname_isequal(name, txt->name))
if (hostname_issubdomain(name, txt->name))
return 1;
for (intr = daemon->int_names; intr; intr = intr->next)
if (hostname_isequal(name, intr->name))
if (hostname_issubdomain(name, intr->name))
return 1;
for (ptr = daemon->ptr; ptr; ptr = ptr->next)
if (hostname_isequal(name, ptr->name))
if (hostname_issubdomain(name, ptr->name))
return 1;
if (cache_find_non_terminal(name, now))
return 1;
return 0;
}

View File

@@ -355,6 +355,44 @@ int hostname_isequal(const char *a, const char *b)
return 1;
}
/* is b equal to or a subdomain of a return 2 for equal, 1 for subdomain */
int hostname_issubdomain(char *a, char *b)
{
char *ap, *bp;
unsigned int c1, c2;
/* move to the end */
for (ap = a; *ap; ap++);
for (bp = b; *bp; bp++);
/* a shorter than b or a empty. */
if ((bp - b) < (ap - a) || ap == a)
return 0;
do
{
c1 = (unsigned char) *(--ap);
c2 = (unsigned char) *(--bp);
if (c1 >= 'A' && c1 <= 'Z')
c1 += 'a' - 'A';
if (c2 >= 'A' && c2 <= 'Z')
c2 += 'a' - 'A';
if (c1 != c2)
return 0;
} while (ap != a);
if (bp == b)
return 2;
if (*(--bp) == '.')
return 1;
return 0;
}
time_t dnsmasq_time(void)
{
#ifdef HAVE_BROKEN_RTC