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

@@ -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);