Major rewrite of the DNS server and domain handling code.

This should be largely transparent, but it drastically
improves performance and reduces memory foot-print when
configuring large numbers domains of the form
local=/adserver.com/
or
local=/adserver.com/#

Lookup times now grow as log-to-base-2 of the number of domains,
rather than greater than linearly, as before.
The change makes multiple addresses associated with a domain work
address=/example.com/1.2.3.4
address=/example.com/5.6.7.8
It also handles multiple upstream servers for a domain better; using
the same try/retry alogrithms as non domain-specific servers. This
also applies to DNSSEC-generated queries.

Finally, some of the oldest and gnarliest code in dnsmasq has had
a significant clean-up. It's far from perfect, but it _is_ better.
This commit is contained in:
Simon Kelley
2021-06-08 22:10:55 +01:00
parent 50ccf9c585
commit 12a9aa7c62
12 changed files with 1691 additions and 1401 deletions

View File

@@ -1605,16 +1605,13 @@ int cache_make_stat(struct txt_record *t)
serv->flags &= ~SERV_COUNTED;
for (serv = daemon->servers; serv; serv = serv->next)
if (!(serv->flags &
(SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)))
if (!(serv->flags & SERV_COUNTED))
{
char *new, *lenp;
int port, newlen, bytes_avail, bytes_needed;
unsigned int queries = 0, failed_queries = 0;
for (serv1 = serv; serv1; serv1 = serv1->next)
if (!(serv1->flags &
(SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)) &&
sockaddr_isequal(&serv->addr, &serv1->addr))
if (!(serv1->flags & SERV_COUNTED) && sockaddr_isequal(&serv->addr, &serv1->addr))
{
serv1->flags |= SERV_COUNTED;
queries += serv1->queries;
@@ -1689,15 +1686,12 @@ void dump_cache(time_t now)
serv->flags &= ~SERV_COUNTED;
for (serv = daemon->servers; serv; serv = serv->next)
if (!(serv->flags &
(SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)))
if (!(serv->flags & SERV_COUNTED))
{
int port;
unsigned int queries = 0, failed_queries = 0;
for (serv1 = serv; serv1; serv1 = serv1->next)
if (!(serv1->flags &
(SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)) &&
sockaddr_isequal(&serv->addr, &serv1->addr))
if (!(serv1->flags & SERV_COUNTED) && sockaddr_isequal(&serv->addr, &serv1->addr))
{
serv1->flags |= SERV_COUNTED;
queries += serv1->queries;
@@ -1885,14 +1879,14 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg)
{
unsigned int rcode = addr->log.rcode;
if (rcode == SERVFAIL)
dest = "SERVFAIL";
else if (rcode == REFUSED)
dest = "REFUSED";
else if (rcode == NOTIMP)
dest = "not implemented";
else
sprintf(daemon->addrbuff, "%u", rcode);
if (rcode == SERVFAIL)
dest = "SERVFAIL";
else if (rcode == REFUSED)
dest = "REFUSED";
else if (rcode == NOTIMP)
dest = "not implemented";
else
sprintf(daemon->addrbuff, "%u", rcode);
}
else
inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,

View File

@@ -377,7 +377,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
/* 0.0.0.0 for server address == NULL, for Dbus */
if (addr.in.sin_family == AF_INET &&
addr.in.sin_addr.s_addr == 0)
flags |= SERV_NO_ADDR;
flags |= SERV_LITERAL_ADDRESS;
if (strings)
{

View File

@@ -498,7 +498,7 @@ struct crec {
#define F_NO_RR (1u<<25)
#define F_IPSET (1u<<26)
#define F_NOEXTRA (1u<<27)
#define F_SERVFAIL (1u<<28) /* currently unused. */
#define F_DOMAINSRV (1u<<28)
#define F_RCODE (1u<<29)
#define F_SRV (1u<<30)
@@ -524,19 +524,20 @@ union mysockaddr {
#define IFACE_PERMANENT 4
#define SERV_FROM_RESOLV 1 /* 1 for servers from resolv, 0 for command line. */
#define SERV_NO_ADDR 2 /* no server, this domain is local only */
#define SERV_LITERAL_ADDRESS 4 /* addr is the answer, not the server */
#define SERV_HAS_DOMAIN 8 /* server for one domain only */
/* The actual values here matter, since we sort on them to get records in the order
IPv6 addr, IPv4 addr, all zero return, no-data return, send upstream. */
#define SERV_LITERAL_ADDRESS 1 /* addr is the answer, or NoDATA is the answer, depending on the next three flags */
#define SERV_ALL_ZEROS 2 /* return all zeros for A and AAAA */
#define SERV_4ADDR 4 /* addr is IPv4 */
#define SERV_6ADDR 8 /* addr is IPv6 */
#define SERV_HAS_SOURCE 16 /* source address defined */
#define SERV_FOR_NODOTS 32 /* server for names with no domain part only */
#define SERV_WARNED_RECURSIVE 64 /* avoid warning spam */
#define SERV_FROM_DBUS 128 /* 1 if source is DBus */
#define SERV_MARK 256 /* for mark-and-delete */
#define SERV_TYPE (SERV_HAS_DOMAIN | SERV_FOR_NODOTS)
#define SERV_COUNTED 512 /* workspace for log code */
#define SERV_USE_RESOLV 1024 /* forward this domain in the normal way */
#define SERV_NO_REBIND 2048 /* inhibit dns-rebind protection */
#define SERV_FROM_RESOLV 2048 /* 1 for servers from resolv, 0 for command line. */
#define SERV_FROM_FILE 4096 /* read from --servers-file */
#define SERV_LOOP 8192 /* server causes forwarding loop */
#define SERV_DO_DNSSEC 16384 /* Validate DNSSEC when using this server */
@@ -561,19 +562,46 @@ struct randfd_list {
struct randfd_list *next;
};
struct server {
int flags;
char *domain;
struct server *next;
int serial, arrayposn;
int last_server;
union mysockaddr addr, source_addr;
char interface[IF_NAMESIZE+1];
unsigned int ifindex; /* corresponding to interface, above */
struct serverfd *sfd;
char *domain; /* set if this server only handles a domain. */
int flags, tcpfd, edns_pktsz;
int tcpfd, edns_pktsz;
time_t pktsz_reduced;
unsigned int queries, failed_queries;
time_t forwardtime;
int forwardcount;
#ifdef HAVE_LOOP
u32 uid;
#endif
struct server *next;
};
/* First three fields must match struct server in next three definitions.. */
struct serv_addr4 {
int flags;
char *domain;
struct server *next;
struct in_addr addr;
};
struct serv_addr6 {
int flags;
char *domain;
struct server *next;
struct in6_addr addr;
};
struct serv_local {
int flags;
char *domain;
struct server *next;
};
struct ipsets {
@@ -662,6 +690,7 @@ struct hostsfile {
#define STAT_SECURE_WILDCARD 7
#define STAT_OK 8
#define STAT_ABANDONED 9
#define STAT_INPROGRESS 10
#define FREC_NOREBIND 1
#define FREC_CHECKING_DISABLED 2
@@ -1054,7 +1083,8 @@ extern struct daemon {
char *lease_change_command;
struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces;
struct bogus_addr *bogus_addr, *ignore_addr;
struct server *servers;
struct server *servers, *local_domains, **serverarray, *no_rebind;
int serverarraysz;
struct ipsets *ipsets;
int log_fac; /* log facility */
char *log_file; /* optional log file */
@@ -1126,9 +1156,6 @@ extern struct daemon {
struct serverfd *sfds;
struct irec *interfaces;
struct listener *listeners;
struct server *last_server;
time_t forwardtime;
int forwardcount;
struct server *srv_save; /* Used for resend on DoD */
size_t packet_len; /* " " */
int fd_save; /* " " */
@@ -1244,9 +1271,7 @@ unsigned char *skip_questions(struct dns_header *header, size_t plen);
unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen);
unsigned int extract_request(struct dns_header *header, size_t qlen,
char *name, unsigned short *typep);
size_t setup_reply(struct dns_header *header, size_t qlen,
union all_addr *addrp, unsigned int flags,
unsigned long ttl);
void setup_reply(struct dns_header *header, unsigned int flags);
int extract_addresses(struct dns_header *header, size_t qlen, char *name,
time_t now, char **ipsets, int is_sign, int check_rebind,
int no_cache_dnssec, int secure, int *doctored);
@@ -1695,3 +1720,13 @@ int do_arp_script_run(void);
void dump_init(void);
void dump_packet(int mask, void *packet, size_t len, union mysockaddr *src, union mysockaddr *dst);
#endif
/* domain-match.c */
void build_server_array(void);
int lookup_domain(char *qdomain, int flags, int *lowout, int *highout);
int filter_servers(int seed, int flags, int *lowout, int *highout);
int is_local_answer(time_t now, int first, char *name);
#ifdef HAVE_DNSSEC
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp);
#endif

379
src/domain-match.c Normal file
View File

@@ -0,0 +1,379 @@
/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 dated June, 1991, or
(at your option) version 3 dated 29 June, 2007.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dnsmasq.h"
static int order(char *qdomain, int leading_dot, size_t qlen, struct server *serv);
static int order_qsort(const void *a, const void *b);
static int order_servers(struct server *s, struct server *s2);
void build_server_array(void)
{
struct server *serv;
int count = 0;
for (serv = daemon->servers; serv; serv = serv->next)
count++;
for (serv = daemon->local_domains; serv; serv = serv->next)
count++;
if (count > daemon->serverarraysz)
{
struct server **new;
if ((new = whine_malloc(count * sizeof(struct server *))))
{
if (daemon->serverarray)
free(daemon->serverarray);
daemon->serverarray = new;
daemon->serverarraysz = count;
}
}
count = 0;
for (serv = daemon->servers; serv; serv = serv->next, count++)
{
daemon->serverarray[count] = serv;
serv->serial = count;
serv->last_server = -1;
}
for (serv = daemon->local_domains; serv; serv = serv->next, count++)
daemon->serverarray[count] = serv;
qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
/* servers need the location in the array to find all the whole
set of equivalent servers from a pointer to a single one. */
for (count = 0; count < daemon->serverarraysz; count++)
if (!(daemon->serverarray[count]->flags & SERV_LITERAL_ADDRESS))
daemon->serverarray[count]->arrayposn = count;
}
/* we're looking for the server whose domain is the longest exact match
to the RH end of qdomain, or a local address if the flags match.
Add '.' to the LHS of the query string so
server=/.example.com/ works.
A flag of F_SERVER returns an upstream server only.
A flag of F_DNSSECOK returns a DNSSEC capable server only and
also disables NODOTS servers from consideration.
A flag of F_DOMAINSRV returns a domain-specific server only.
return 0 if nothing found, 1 otherwise.
*/
int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
{
int rc, nodots, leading_dot = 1;
ssize_t qlen, maxlen;
int try, high, low = 0;
int nlow = 0, nhigh = 0;
char *cp;
/* may be no configured servers. */
if (daemon->serverarraysz == 0)
return 0;
maxlen = strlen(daemon->serverarray[0]->domain);
/* find query length and presence of '.' */
for (cp = qdomain, nodots = 1, qlen = 0; *cp; qlen++, cp++)
if (*cp == '.')
nodots = 0;
/* Handle empty name, and searches for DNSSEC queries without
diverting to NODOTS servers. */
if (qlen == 0 || flags & F_DNSSECOK)
nodots = 0;
/* No point trying to match more than the largest server domain */
if (qlen > maxlen)
{
qdomain += qlen - maxlen;
qlen = maxlen;
leading_dot = 0;
}
/* Search shorter and shorter RHS substrings for a match */
while (qlen >= 0)
{
/* Note that when we chop off a character, all the possible matches
MUST be at a larger index than the nearest failing match with one more
character, since the array is sorted longest to smallest. Hence
we don't reset low here. */
high = daemon->serverarraysz;
/* binary search */
do
{
try = (low + high)/2;
if ((rc = order(qdomain, leading_dot, qlen, daemon->serverarray[try])) == 0)
break;
if (rc < 0)
{
if (high == try)
break;
high = try;
}
else
{
if (low == try)
break;
low = try;
}
}
while (low != high);
if (rc == 0)
{
/* We've matched a setting which says to use servers without a domain.
Continue the search with empty query (the last character gets stripped
by the loop. */
if (daemon->serverarray[try]->flags & SERV_USE_RESOLV)
{
qdomain += qlen - 1;
qlen = 1;
}
else
{
/* We have a match, but it may only be (say) an IPv6 address, and
if the query wasn't for an AAAA record, it's no good, and we need
to continue generalising */
if (filter_servers(try, flags, &nlow, &nhigh))
break;
}
}
if (leading_dot)
leading_dot = 0;
else
{
qlen--;
qdomain++;
}
}
/* domain has no dots, and we have at least one server configured to handle such,
These servers always sort to the very end of the array.
A configured server eg server=/lan/ will take precdence. */
if (nodots &&
(daemon->serverarray[daemon->serverarraysz-1]->flags & SERV_FOR_NODOTS) &&
(nlow == nhigh || strlen(daemon->serverarray[nlow]->domain) == 0))
filter_servers(daemon->serverarraysz-1, flags, &nlow, &nhigh);
/* F_DOMAINSRV returns only domain-specific servers, so if we got to a
general server, return empty set. */
if (nlow != nhigh && (flags & F_DOMAINSRV) && strlen(daemon->serverarray[nlow]->domain) == 0)
nlow = nhigh;
if (lowout)
*lowout = nlow;
if (highout)
*highout = nhigh;
if (nlow == nhigh)
return 0;
return 1;
}
int filter_servers(int seed, int flags, int *lowout, int *highout)
{
int nlow = seed, nhigh = seed;
int i;
/* expand nlow and nhigh to cover all the records with the same domain
nlow is the first, nhigh - 1 is the last. nlow=nhigh means no servers,
which can happen below. */
while (nlow > 0 && order_servers(daemon->serverarray[nlow-1], daemon->serverarray[nlow]) == 0)
nlow--;
while (nhigh < daemon->serverarraysz-1 && order_servers(daemon->serverarray[nhigh], daemon->serverarray[nhigh+1]) == 0)
nhigh++;
nhigh++;
/* Now the servers are on order between low and high, in the order
return zero for both, IPv6 addr, IPv4 addr, no-data return, send upstream.
See which of those match our query in that priority order and narrow (low, high) */
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++);
if (i != nlow && (flags & F_IPV6))
nhigh = i;
else
{
nlow = i;
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_4ADDR); i++);
if (i != nlow && (flags & F_IPV4))
nhigh = i;
else
{
nlow = i;
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_ALL_ZEROS); i++);
if (i != nlow && (flags & (F_IPV4 | F_IPV6)))
nhigh = i;
else
{
nlow = i;
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
/* --local=/domain/, only return if we don't need a server. */
if (i != nlow && !(flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER)))
nhigh = i;
else
{
nlow = i;
/* If we want a server that can do DNSSEC, and this one can't,
return nothing. */
if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC))
nlow = nhigh;
}
}
}
}
*lowout = nlow;
*highout = nhigh;
return (nlow != nhigh);
}
int is_local_answer(time_t now, int first, char *name)
{
int flags = 0;
int rc = 0;
if ((flags = daemon->serverarray[first]->flags) & SERV_LITERAL_ADDRESS)
{
if (flags & SERV_4ADDR)
rc = F_IPV4;
else if (flags & SERV_6ADDR)
rc = F_IPV6;
else if (flags & SERV_ALL_ZEROS)
rc = F_IPV4 | F_IPV6;
else
rc = check_for_local_domain(name, now) ? F_NOERR : F_NXDOMAIN;
}
return rc;
}
#ifdef HAVE_DNSSEC
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp)
{
int first, last, index;
/* Find server to send DNSSEC query to. This will normally be the
same as for the original query, but may be another if
servers for domains are involved. */
if (!lookup_domain(keyname, F_DNSSECOK, &first, &last))
return -1;
for (index = first; index != last; index++)
if (daemon->serverarray[index] == server)
break;
/* No match to server used for original query.
Use newly looked up set. */
if (index == last)
index = daemon->serverarray[first]->last_server == -1 ?
first : daemon->serverarray[first]->last_server;
if (firstp)
*firstp = first;
if (lastp)
*lastp = last;
return index;
}
#endif
/* order by size, then by dictionary order */
static int order(char *qdomain, int leading_dot, size_t qlen, struct server *serv)
{
size_t dlen = 0;
int rc;
/* servers for dotless names always sort last
searched for name is never dotless. */
if (serv->flags & SERV_FOR_NODOTS)
return -1;
if (leading_dot)
qlen++;
dlen = strlen(serv->domain);
if (qlen < dlen)
return 1;
if (qlen > dlen)
return -1;
if (leading_dot && (rc = '.' - serv->domain[0]) != 0)
return rc;
return strcmp(qdomain, leading_dot ? &serv->domain[1] : serv->domain);
}
static int order_servers(struct server *s1, struct server *s2)
{
size_t dlen = strlen(s1->domain);
/* need full comparison of dotless servers in
order_qsort() and filter_servers() */
if (s1->flags & SERV_FOR_NODOTS)
return (s2->flags & SERV_FOR_NODOTS) ? 0 : 1;
return order(s1->domain, 0, dlen, s2);
}
static int order_qsort(const void *a, const void *b)
{
int rc;
struct server *s1 = *((struct server **)a);
struct server *s2 = *((struct server **)b);
rc = order_servers(s1, s2);
/* Sort all literal NODATA and local IPV4 or IPV6 responses together,
in a very specific order. */
if (rc == 0)
rc = (s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) -
(s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS));
/* Finally, order by appearance in /etc/resolv.conf etc, for --strict-order */
if (rc == 0)
if (!(s1->flags & SERV_LITERAL_ADDRESS))
rc = s1->serial - s2->serial;
return rc;
}

File diff suppressed because it is too large Load Diff

View File

@@ -30,8 +30,8 @@ void loop_send_probes()
/* Loop through all upstream servers not for particular domains, and send a query to that server which is
identifiable, via the uid. If we see that query back again, then the server is looping, and we should not use it. */
for (serv = daemon->servers; serv; serv = serv->next)
if (!(serv->flags &
(SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_LOOP)))
if (strlen(serv->domain) == 0 &&
!(serv->flags & (SERV_FOR_NODOTS | SERV_LOOP)))
{
ssize_t len = loop_make_probe(serv->uid);
int fd;
@@ -96,15 +96,15 @@ int detect_loop(char *query, int type)
uid = strtol(query, NULL, 16);
for (serv = daemon->servers; serv; serv = serv->next)
if (!(serv->flags &
(SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_LOOP)) &&
uid == serv->uid)
{
serv->flags |= SERV_LOOP;
check_servers(); /* log new state */
return 1;
}
if (strlen(serv->domain) == 0 &&
!(serv->flags & SERV_LOOP) &&
uid == serv->uid)
{
serv->flags |= SERV_LOOP;
check_servers(); /* log new state */
return 1;
}
return 0;
}

View File

@@ -31,7 +31,7 @@ int indextoname(int fd, int index, char *name)
safe_strncpy(name, ifr.ifr_name, IF_NAMESIZE);
return 1;
return 1;
}
@@ -1479,8 +1479,7 @@ void pre_allocate_sfds(void)
}
for (srv = daemon->servers; srv; srv = srv->next)
if (!(srv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)) &&
!allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) &&
if (!allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) &&
errno != 0 &&
option_bool(OPT_NOWILD))
{
@@ -1509,6 +1508,10 @@ void mark_servers(int flag)
serv->flags &= ~SERV_LOOP;
#endif
}
for (serv = daemon->local_domains; serv; serv = serv->next)
if (serv->flags & flag)
serv->flags |= SERV_MARK;
}
void cleanup_servers(void)
@@ -1523,14 +1526,27 @@ void cleanup_servers(void)
{
server_gone(serv);
*up = serv->next;
if (serv->domain)
free(serv->domain);
free(serv->domain);
free(serv);
}
else
up = &serv->next;
}
for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = tmp)
{
tmp = serv->next;
if (serv->flags & SERV_MARK)
{
*up = serv->next;
free(serv->domain);
free(serv);
}
else
up = &serv->next;
}
#ifdef HAVE_LOOP
/* Now we have a new set of servers, test for loops. */
loop_send_probes();
@@ -1543,76 +1559,80 @@ void add_update_server(int flags,
const char *interface,
const char *domain)
{
struct server *serv, *next = NULL;
char *domain_str = NULL;
struct server *serv;
char *domain_str;
if (!domain)
domain = "";
/* If the server is USE_RESOLV or LITERAL_ADDRES, it lives on the local_domains chain.
NOTE that we can get local=/domain/ here, but NOT address=/domain/1.2.3.4 */
#define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS)
/* See if there is a suitable candidate, and unmark */
for (serv = daemon->servers; serv; serv = serv->next)
if (serv->flags & SERV_MARK)
{
if (domain)
{
if (!(serv->flags & SERV_HAS_DOMAIN) || !hostname_isequal(domain, serv->domain))
continue;
}
else
{
if (serv->flags & SERV_HAS_DOMAIN)
continue;
}
break;
}
for (serv = (flags & SERV_IS_LOCAL) ? daemon->local_domains : daemon->servers; serv; serv = serv->next)
if ((serv->flags & SERV_MARK) && hostname_isequal(domain, serv->domain))
break;
if (serv)
{
domain_str = serv->domain;
next = serv->next;
}
domain_str = serv->domain;
else if ((serv = whine_malloc(sizeof (struct server))))
{
/* Not found, create a new one. */
if (domain && !(domain_str = whine_malloc(strlen(domain)+1)))
if (!(domain_str = whine_malloc(strlen(domain)+1)))
{
free(serv);
serv = NULL;
}
else
{
struct server *s;
/* Add to the end of the chain, for order */
if (!daemon->servers)
daemon->servers = serv;
{
strcpy(domain_str, domain);
if (flags & SERV_IS_LOCAL)
{
serv->next = daemon->local_domains;
daemon->local_domains = serv;
}
else
{
for (s = daemon->servers; s->next; s = s->next);
s->next = serv;
struct server *s;
/* Add to the end of the chain, for order */
if (!daemon->servers)
daemon->servers = serv;
else
{
for (s = daemon->servers; s->next; s = s->next);
s->next = serv;
}
serv->next = NULL;
}
if (domain)
strcpy(domain_str, domain);
}
}
if (serv)
{
memset(serv, 0, sizeof(struct server));
if (!(flags & SERV_IS_LOCAL))
memset(serv, 0, sizeof(struct server));
serv->flags = flags;
serv->domain = domain_str;
serv->next = next;
serv->queries = serv->failed_queries = 0;
#ifdef HAVE_LOOP
serv->uid = rand32();
#endif
if (domain)
serv->flags |= SERV_HAS_DOMAIN;
if (interface)
safe_strncpy(serv->interface, interface, sizeof(serv->interface));
if (addr)
serv->addr = *addr;
if (source_addr)
serv->source_addr = *source_addr;
if (!(flags & SERV_IS_LOCAL))
{
serv->queries = serv->failed_queries = 0;
#ifdef HAVE_LOOP
serv->uid = rand32();
#endif
if (interface)
safe_strncpy(serv->interface, interface, sizeof(serv->interface));
if (addr)
serv->addr = *addr;
if (source_addr)
serv->source_addr = *source_addr;
}
}
}
@@ -1623,7 +1643,7 @@ void check_servers(void)
struct serverfd *sfd, *tmp, **up;
int port = 0, count;
int locals = 0;
/* interface may be new since startup */
if (!option_bool(OPT_NOWILD))
enumerate_interfaces(0);
@@ -1634,114 +1654,117 @@ void check_servers(void)
for (count = 0, serv = daemon->servers; serv; serv = serv->next)
{
if (!(serv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)))
{
/* Init edns_pktsz for newly created server records. */
if (serv->edns_pktsz == 0)
serv->edns_pktsz = daemon->edns_pktsz;
/* Init edns_pktsz for newly created server records. */
if (serv->edns_pktsz == 0)
serv->edns_pktsz = daemon->edns_pktsz;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
{
if (!(serv->flags & SERV_FOR_NODOTS))
serv->flags |= SERV_DO_DNSSEC;
if (option_bool(OPT_DNSSEC_VALID))
{
if (!(serv->flags & SERV_FOR_NODOTS))
serv->flags |= SERV_DO_DNSSEC;
/* Disable DNSSEC validation when using server=/domain/.... servers
unless there's a configured trust anchor. */
if (strlen(serv->domain) != 0)
{
struct ds_config *ds;
char *domain = serv->domain;
/* Disable DNSSEC validation when using server=/domain/.... servers
unless there's a configured trust anchor. */
if (serv->flags & SERV_HAS_DOMAIN)
{
struct ds_config *ds;
char *domain = serv->domain;
/* .example.com is valid */
while (*domain == '.')
domain++;
for (ds = daemon->ds; ds; ds = ds->next)
if (ds->name[0] != 0 && hostname_isequal(domain, ds->name))
break;
if (!ds)
serv->flags &= ~SERV_DO_DNSSEC;
}
/* .example.com is valid */
while (*domain == '.')
domain++;
for (ds = daemon->ds; ds; ds = ds->next)
if (ds->name[0] != 0 && hostname_isequal(domain, ds->name))
break;
if (!ds)
serv->flags &= ~SERV_DO_DNSSEC;
}
}
#endif
port = prettyprint_addr(&serv->addr, daemon->namebuff);
/* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */
if (serv->addr.sa.sa_family == AF_INET &&
serv->addr.in.sin_addr.s_addr == 0)
{
serv->flags |= SERV_MARK;
continue;
}
for (iface = daemon->interfaces; iface; iface = iface->next)
if (sockaddr_isequal(&serv->addr, &iface->addr))
break;
if (iface)
{
my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff);
serv->flags |= SERV_MARK;
continue;
}
/* Do we need a socket set? */
if (!serv->sfd &&
!(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) &&
errno != 0)
{
my_syslog(LOG_WARNING,
_("ignoring nameserver %s - cannot make/bind socket: %s"),
daemon->namebuff, strerror(errno));
serv->flags |= SERV_MARK;
continue;
}
if (serv->sfd)
serv->sfd->used = 1;
port = prettyprint_addr(&serv->addr, daemon->namebuff);
/* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */
if (serv->addr.sa.sa_family == AF_INET &&
serv->addr.in.sin_addr.s_addr == 0)
{
serv->flags |= SERV_MARK;
continue;
}
if (!(serv->flags & SERV_NO_REBIND) && !(serv->flags & SERV_LITERAL_ADDRESS))
for (iface = daemon->interfaces; iface; iface = iface->next)
if (sockaddr_isequal(&serv->addr, &iface->addr))
break;
if (iface)
{
if (++count > SERVERS_LOGGED)
continue;
if (serv->flags & (SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_USE_RESOLV))
{
char *s1, *s2, *s3 = "";
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC))
s3 = _("(no DNSSEC)");
#endif
if (!(serv->flags & SERV_HAS_DOMAIN))
s1 = _("unqualified"), s2 = _("names");
else if (strlen(serv->domain) == 0)
s1 = _("default"), s2 = "";
else
s1 = _("domain"), s2 = serv->domain;
if (serv->flags & SERV_NO_ADDR)
{
count--;
if (++locals <= LOCALS_LOGGED)
my_syslog(LOG_INFO, _("using only locally-known addresses for %s %s"), s1, s2);
}
else if (serv->flags & SERV_USE_RESOLV)
my_syslog(LOG_INFO, _("using standard nameservers for %s %s"), s1, s2);
else
my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s %s"), daemon->namebuff, port, s1, s2, s3);
}
#ifdef HAVE_LOOP
else if (serv->flags & SERV_LOOP)
my_syslog(LOG_INFO, _("NOT using nameserver %s#%d - query loop detected"), daemon->namebuff, port);
#endif
else if (serv->interface[0] != 0)
my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface);
else
my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port);
my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff);
serv->flags |= SERV_MARK;
continue;
}
/* Do we need a socket set? */
if (!serv->sfd &&
!(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) &&
errno != 0)
{
my_syslog(LOG_WARNING,
_("ignoring nameserver %s - cannot make/bind socket: %s"),
daemon->namebuff, strerror(errno));
serv->flags |= SERV_MARK;
continue;
}
if (serv->sfd)
serv->sfd->used = 1;
if (++count > SERVERS_LOGGED)
continue;
if (strlen(serv->domain) != 0 || (serv->flags & SERV_FOR_NODOTS))
{
char *s1, *s2, *s3 = "";
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC))
s3 = _("(no DNSSEC)");
#endif
if (serv->flags & SERV_FOR_NODOTS)
s1 = _("unqualified"), s2 = _("names");
else if (strlen(serv->domain) == 0)
s1 = _("default"), s2 = "";
else
s1 = _("domain"), s2 = serv->domain;
my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s %s"), daemon->namebuff, port, s1, s2, s3);
}
#ifdef HAVE_LOOP
else if (serv->flags & SERV_LOOP)
my_syslog(LOG_INFO, _("NOT using nameserver %s#%d - query loop detected"), daemon->namebuff, port);
#endif
else if (serv->interface[0] != 0)
my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface);
else
my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port);
}
for (count = 0, serv = daemon->local_domains; serv; serv = serv->next)
{
if (++count > SERVERS_LOGGED)
continue;
if ((serv->flags & SERV_LITERAL_ADDRESS) &&
!(serv->flags & (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS)))
{
count--;
if (++locals <= LOCALS_LOGGED)
my_syslog(LOG_INFO, _("using only locally-known addresses for %s"), serv->domain);
}
else if (serv->flags & SERV_USE_RESOLV)
my_syslog(LOG_INFO, _("using standard nameservers for %s"), serv->domain);
}
if (locals > LOCALS_LOGGED)
@@ -1764,6 +1787,7 @@ void check_servers(void)
}
cleanup_servers();
build_server_array();
}
/* Return zero if no servers found, in that case we keep polling.

View File

@@ -644,6 +644,9 @@ static char *canonicalise_opt(char *s)
if (!s)
return 0;
if (strlen(s) == 0)
return "";
unhide_metas(s);
if (!(ret = canonicalise(s, &nomem)) && nomem)
{
@@ -816,14 +819,14 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
char *interface_opt = NULL;
int scope_index = 0;
char *scope_id;
if (!arg || strlen(arg) == 0)
if (strcmp(arg, "#") == 0)
{
*flags |= SERV_NO_ADDR;
*interface = 0;
if (flags)
*flags |= SERV_USE_RESOLV;
return NULL;
}
if ((source = split_chr(arg, '@')) && /* is there a source. */
(portno = split_chr(source, '#')) &&
!atoi_check16(portno, &source_port))
@@ -921,7 +924,7 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
static struct server *add_rev4(struct in_addr addr, int msize)
{
struct server *serv = opt_malloc(sizeof(struct server));
in_addr_t a = ntohl(addr.s_addr);
in_addr_t a = ntohl(addr.s_addr);
char *p;
memset(serv, 0, sizeof(struct server));
@@ -949,10 +952,6 @@ static struct server *add_rev4(struct in_addr addr, int msize)
p += sprintf(p, "in-addr.arpa");
serv->flags = SERV_HAS_DOMAIN;
serv->next = daemon->servers;
daemon->servers = serv;
return serv;
}
@@ -973,10 +972,6 @@ static struct server *add_rev6(struct in6_addr *addr, int msize)
}
p += sprintf(p, "ip6.arpa");
serv->flags = SERV_HAS_DOMAIN;
serv->next = daemon->servers;
daemon->servers = serv;
return serv;
}
@@ -1664,16 +1659,6 @@ void reset_option_bool(unsigned int opt)
option_var(opt) &= ~(option_val(opt));
}
static void server_list_free(struct server *list)
{
while (list)
{
struct server *tmp = list;
list = list->next;
free(tmp);
}
}
static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only)
{
int i;
@@ -2309,15 +2294,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
if (!serv)
ret_err_free(_("bad prefix"), new);
serv->flags |= SERV_NO_ADDR;
serv->flags |= SERV_LITERAL_ADDRESS;
serv->next = daemon->local_domains;
daemon->local_domains = serv;
/* local=/<domain>/ */
serv = opt_malloc(sizeof(struct server));
memset(serv, 0, sizeof(struct server));
serv->domain = d;
serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR;
serv->next = daemon->servers;
daemon->servers = serv;
serv->flags = SERV_LITERAL_ADDRESS;
serv->next = daemon->local_domains;
daemon->local_domains = serv;
}
}
}
@@ -2352,15 +2339,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
/* generate the equivalent of
local=/xxx.yyy.zzz.ip6.arpa/ */
struct server *serv = add_rev6(&new->start6, msize);
serv->flags |= SERV_NO_ADDR;
serv->flags |= SERV_LITERAL_ADDRESS;
serv->next = daemon->local_domains;
daemon->local_domains = serv;
/* local=/<domain>/ */
serv = opt_malloc(sizeof(struct server));
memset(serv, 0, sizeof(struct server));
serv->domain = d;
serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR;
serv->next = daemon->servers;
daemon->servers = serv;
serv->flags = SERV_LITERAL_ADDRESS;
serv->next = daemon->local_domains;
daemon->local_domains = serv;
}
}
}
@@ -2615,98 +2604,166 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
} while (arg);
break;
case LOPT_NO_REBIND: /* --rebind-domain-ok */
{
struct server *new;
unhide_metas(arg);
if (*arg == '/')
arg++;
do {
comma = split_chr(arg, '/');
new = opt_malloc(sizeof(struct serv_local));
new->domain = opt_string_alloc(arg);
new->flags = strlen(arg);
new->next = daemon->no_rebind;
daemon->no_rebind = new;
arg = comma;
} while (arg && *arg);
break;
}
case 'S': /* --server */
case LOPT_LOCAL: /* --local */
case 'A': /* --address */
case LOPT_NO_REBIND: /* --rebind-domain-ok */
{
struct server *serv, *newlist = NULL;
struct server *new;
size_t size;
char *lastdomain = NULL, *domain = "";
char *alloc_domain;
int flags = 0;
char *err;
struct in_addr addr4;
struct in6_addr addr6;
unhide_metas(arg);
if (arg && (*arg == '/' || option == LOPT_NO_REBIND))
/* split the domain args, if any and skip to the end of them. */
if (arg && *arg == '/')
{
int rebind = !(*arg == '/');
char *end = NULL;
if (!rebind)
arg++;
while (rebind || (end = split_chr(arg, '/')))
char *last;
arg++;
domain = lastdomain = arg;
while ((last = split_chr(arg, '/')))
{
char *domain = NULL;
/* elide leading dots - they are implied in the search algorithm */
while (*arg == '.') arg++;
/* # matches everything and becomes a zero length domain string */
if (strcmp(arg, "#") == 0)
domain = "";
else if (strlen (arg) != 0 && !(domain = canonicalise_opt(arg)))
ret_err(gen_err);
serv = opt_malloc(sizeof(struct server));
memset(serv, 0, sizeof(struct server));
serv->next = newlist;
newlist = serv;
serv->domain = domain;
serv->flags = domain ? SERV_HAS_DOMAIN : SERV_FOR_NODOTS;
arg = end;
if (rebind)
break;
lastdomain = arg;
arg = last;
}
if (!newlist)
ret_err(gen_err);
}
else
{
newlist = opt_malloc(sizeof(struct server));
memset(newlist, 0, sizeof(struct server));
#ifdef HAVE_LOOP
newlist->uid = rand32();
#endif
}
if (servers_only && option == 'S')
newlist->flags |= SERV_FROM_FILE;
if (option == 'A')
{
newlist->flags |= SERV_LITERAL_ADDRESS;
if (!(newlist->flags & SERV_TYPE))
{
server_list_free(newlist);
ret_err(gen_err);
}
}
else if (option == LOPT_NO_REBIND)
newlist->flags |= SERV_NO_REBIND;
flags |= SERV_FROM_FILE;
if (!arg || !*arg)
flags = SERV_LITERAL_ADDRESS;
else if (option == 'A')
{
if (!(newlist->flags & SERV_NO_REBIND))
newlist->flags |= SERV_NO_ADDR; /* no server */
/* # as literal address means return zero address for 4 and 6 */
if (strcmp(arg, "#") == 0)
flags |= SERV_ALL_ZEROS | SERV_LITERAL_ADDRESS;
else if (inet_pton(AF_INET, arg, &addr4) > 0)
flags |= SERV_4ADDR | SERV_LITERAL_ADDRESS;
else if (inet_pton(AF_INET6, arg, &addr6) > 0)
flags |= SERV_6ADDR | SERV_LITERAL_ADDRESS;
else
ret_err(_("Bad address in --address"));
}
else if (strcmp(arg, "#") == 0)
newlist->flags |= SERV_USE_RESOLV; /* treat in ordinary way */
if (!(alloc_domain = canonicalise_opt(domain)))
ret_err(gen_err);
if (flags & SERV_LITERAL_ADDRESS)
{
if (flags & SERV_6ADDR)
{
size = sizeof(struct serv_addr6);
new = opt_malloc(sizeof(struct serv_addr6));
((struct serv_addr6*)new)->addr = addr6;
}
else if (flags & SERV_4ADDR)
{
size = sizeof(struct serv_addr4);
new = opt_malloc(sizeof(struct serv_addr4));
((struct serv_addr4*)new)->addr = addr4;
}
else
{
size = sizeof(struct serv_local);
new = opt_malloc(sizeof(struct serv_local));
}
new->next = daemon->local_domains;
daemon->local_domains = new;
}
else
{
char *err = parse_server(arg, &newlist->addr, &newlist->source_addr, newlist->interface, &newlist->flags);
if (err)
size = sizeof(struct server);
new = opt_malloc(sizeof(struct server));
#ifdef HAVE_LOOP
new->uid = rand32();
#endif
if ((err = parse_server(arg, &new->addr, &new->source_addr, new->interface, &flags)))
{
server_list_free(newlist);
ret_err(err);
free(new);
ret_err(err);
}
/* Since domains that use standard servers don't have the
network stuff, it's easier to treat them as local. */
if (flags & SERV_USE_RESOLV)
{
new->next = daemon->local_domains;
daemon->local_domains = new;
}
else
{
new->next = daemon->servers;
daemon->servers = new;
}
}
serv = newlist;
while (serv->next)
{
serv->next->flags |= serv->flags & ~(SERV_HAS_DOMAIN | SERV_FOR_NODOTS);
serv->next->addr = serv->addr;
serv->next->source_addr = serv->source_addr;
strcpy(serv->next->interface, serv->interface);
serv = serv->next;
}
serv->next = daemon->servers;
daemon->servers = newlist;
break;
new->domain = alloc_domain;
/* server=//1.2.3.4 is special. */
if (strlen(domain) == 0 && lastdomain)
flags |= SERV_FOR_NODOTS;
new->flags = flags;
/* If we have more than one domain, copy and iterate */
if (lastdomain)
while (domain != lastdomain)
{
struct server *last = new;
domain += strlen(domain) + 1;
if (!(alloc_domain = canonicalise_opt(domain)))
ret_err(gen_err);
new = opt_malloc(size);
memcpy(new, last, size);
new->domain = alloc_domain;
if (flags & (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS))
{
new->next = daemon->local_domains;
daemon->local_domains = new;
}
else
{
new->next = daemon->servers;
daemon->servers = new;
}
}
break;
}
case LOPT_REV_SERV: /* --rev-server */
@@ -2731,9 +2788,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
serv = add_rev4(addr4, size);
if (!serv)
ret_err(_("bad prefix"));
serv->next = daemon->servers;
daemon->servers = serv;
}
else if (inet_pton(AF_INET6, arg, &addr6))
serv = add_rev6(&addr6, size);
{
serv = add_rev6(&addr6, size);
serv->next = daemon->servers;
daemon->servers = serv;
}
else
ret_err(gen_err);

View File

@@ -894,6 +894,8 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
if (typep)
*typep = 0;
*name = 0; /* return empty name if no query found. */
if (ntohs(header->qdcount) != 1 || OPCODE(header) != QUERY)
return 0; /* must be exactly one query. */
@@ -926,14 +928,8 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
return F_QUERY;
}
size_t setup_reply(struct dns_header *header, size_t qlen,
union all_addr *addrp, unsigned int flags, unsigned long ttl)
void setup_reply(struct dns_header *header, unsigned int flags)
{
unsigned char *p;
if (!(p = skip_questions(header, qlen)))
return 0;
/* clear authoritative and truncated flags, set QR flag */
header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC )) | HB3_QR;
/* clear AD flag, set RA flag */
@@ -946,30 +942,10 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
SET_RCODE(header, NOERROR); /* empty domain */
else if (flags == F_NXDOMAIN)
SET_RCODE(header, NXDOMAIN);
else if (flags == F_SERVFAIL)
{
union all_addr a;
a.log.rcode = SERVFAIL;
log_query(F_CONFIG | F_RCODE, "error", &a, NULL);
SET_RCODE(header, SERVFAIL);
}
else if (flags & ( F_IPV4 | F_IPV6))
{
if (flags & F_IPV4)
{ /* we know the address */
SET_RCODE(header, NOERROR);
header->ancount = htons(1);
header->hb3 |= HB3_AA;
add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_A, C_IN, "4", addrp);
}
if (flags & F_IPV6)
{
SET_RCODE(header, NOERROR);
header->ancount = htons(ntohs(header->ancount) + 1);
header->hb3 |= HB3_AA;
add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_AAAA, C_IN, "6", addrp);
}
SET_RCODE(header, NOERROR);
header->hb3 |= HB3_AA;
}
else /* nowhere to forward to */
{
@@ -978,8 +954,6 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
log_query(F_CONFIG | F_RCODE, "error", &a, NULL);
SET_RCODE(header, REFUSED);
}
return p - (unsigned char *)header;
}
/* check if name matches local names ie from /etc/hosts or DHCP or local mx names. */
@@ -1553,43 +1527,17 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
anscount++;
}
}
else if (option_bool(OPT_BOGUSPRIV) && (
(is_arpa == F_IPV6 && private_net6(&addr.addr6)) ||
(is_arpa == F_IPV4 && private_net(addr.addr4, 1))))
else if (option_bool(OPT_BOGUSPRIV) &&
((is_arpa == F_IPV6 && private_net6(&addr.addr6)) || (is_arpa == F_IPV4 && private_net(addr.addr4, 1))) &&
!lookup_domain(name, F_DOMAINSRV, NULL, NULL))
{
struct server *serv;
unsigned int namelen = strlen(name);
char *nameend = name + namelen;
/* see if have rev-server set */
for (serv = daemon->servers; serv; serv = serv->next)
{
unsigned int domainlen;
char *matchstart;
if ((serv->flags & (SERV_HAS_DOMAIN | SERV_NO_ADDR)) != SERV_HAS_DOMAIN)
continue;
domainlen = strlen(serv->domain);
if (domainlen == 0 || domainlen > namelen)
continue;
matchstart = nameend - domainlen;
if (hostname_isequal(matchstart, serv->domain) &&
(namelen == domainlen || *(matchstart-1) == '.' ))
break;
}
/* if no configured server, not in cache, enabled and private IPV4 address, return NXDOMAIN */
if (!serv)
{
ans = 1;
sec_data = 0;
nxdomain = 1;
if (!dryrun)
log_query(F_CONFIG | F_REVERSE | is_arpa | F_NEG | F_NXDOMAIN,
name, &addr, NULL);
}
ans = 1;
sec_data = 0;
nxdomain = 1;
if (!dryrun)
log_query(F_CONFIG | F_REVERSE | is_arpa | F_NEG | F_NXDOMAIN,
name, &addr, NULL);
}
}