diff options
Diffstat (limited to 'src/domain-match.c')
-rw-r--r-- | src/domain-match.c | 309 |
1 files changed, 180 insertions, 129 deletions
diff --git a/src/domain-match.c b/src/domain-match.c index f8e4796..fe8e25a 100644 --- a/src/domain-match.c +++ b/src/domain-match.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2022 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 @@ -207,16 +207,20 @@ int lookup_domain(char *domain, int flags, int *lowout, int *highout) } } - if (found) + if (found && filter_servers(try, flags, &nlow, &nhigh)) + /* 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 */ { /* We've matched a setting which says to use servers without a domain. - Continue the search with empty query */ - if (daemon->serverarray[try]->flags & SERV_USE_RESOLV) - crop_query = qlen; - else if (filter_servers(try, flags, &nlow, &nhigh)) - /* 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 */ + Continue the search with empty query. We set the F_SERVER flag + so that --address=/#/... doesn't match. */ + if (daemon->serverarray[nlow]->flags & SERV_USE_RESOLV) + { + crop_query = qlen; + flags |= F_SERVER; + } + else break; } } @@ -273,7 +277,7 @@ int filter_servers(int seed, int flags, int *lowout, int *highout) nlow--; while (nhigh < daemon->serverarraysz-1 && order_servers(daemon->serverarray[nhigh], daemon->serverarray[nhigh+1]) == 0) - nhigh++; + nhigh++; nhigh++; @@ -293,13 +297,13 @@ int filter_servers(int seed, int flags, int *lowout, int *highout) else { /* Now the servers are on order between low and high, in the order - IPv6 addr, IPv4 addr, return zero for both, send upstream, no-data return. + IPv6 addr, IPv4 addr, return zero for both, resolvconf servers, send upstream, no-data return. 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)) + if (!(flags & F_SERVER) && i != nlow && (flags & F_IPV6)) nhigh = i; else { @@ -307,7 +311,7 @@ int filter_servers(int seed, int flags, int *lowout, int *highout) for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_4ADDR); i++); - if (i != nlow && (flags & F_IPV4)) + if (!(flags & F_SERVER) && i != nlow && (flags & F_IPV4)) nhigh = i; else { @@ -315,38 +319,46 @@ int filter_servers(int seed, int flags, int *lowout, int *highout) for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_ALL_ZEROS); i++); - if (i != nlow && (flags & (F_IPV4 | F_IPV6))) + if (!(flags & F_SERVER) && i != nlow && (flags & (F_IPV4 | F_IPV6))) nhigh = i; else { nlow = i; - /* now look for a server */ - for (i = nlow; i < nhigh && !(daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++); - + /* Short to resolv.conf servers */ + for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_USE_RESOLV); i++); + if (i != nlow) - { - /* If we want a server that can do DNSSEC, and this one can't, - return nothing, similarly if were looking only for a server - for a particular domain. */ - if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC)) - nlow = nhigh; - else if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0) - nlow = nhigh; - else - nhigh = i; - } + nhigh = i; else { - /* --local=/domain/, only return if we don't need a server. */ - if (flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER)) - nhigh = i; + /* now look for a server */ + for (i = nlow; i < nhigh && !(daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++); + + if (i != nlow) + { + /* If we want a server that can do DNSSEC, and this one can't, + return nothing, similarly if were looking only for a server + for a particular domain. */ + if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC)) + nlow = nhigh; + else if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0) + nlow = nhigh; + else + nhigh = i; + } + else + { + /* --local=/domain/, only return if we don't need a server. */ + if (flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER)) + nhigh = i; + } } } } } } - + *lowout = nlow; *highout = nhigh; @@ -387,13 +399,13 @@ int is_local_answer(time_t now, int first, char *name) size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last, int ede) { - int trunc = 0; + int trunc = 0, anscount = 0; unsigned char *p; int start; union all_addr addr; if (flags & (F_NXDOMAIN | F_NOERR)) - log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL); + log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL, 0); setup_reply(header, flags, ede); @@ -410,9 +422,9 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header else addr.addr4 = srv->addr; - header->ancount = htons(ntohs(header->ancount) + 1); - add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr); - log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL); + if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr)) + anscount++; + log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL, 0); } if (flags & gotname & F_IPV6) @@ -425,14 +437,15 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header else addr.addr6 = srv->addr; - header->ancount = htons(ntohs(header->ancount) + 1); - add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr); - log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL); + if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr)) + anscount++; + log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL, 0); } if (trunc) header->hb3 |= HB3_TC; - + header->ancount = htons(anscount); + return p - (unsigned char *)header; } @@ -485,7 +498,7 @@ static int order(char *qdomain, size_t qlen, struct server *serv) if (qlen > dlen) return -1; - return strcmp(qdomain, serv->domain); + return hostname_order(qdomain, serv->domain); } static int order_servers(struct server *s1, struct server *s2) @@ -520,10 +533,10 @@ static int order_qsort(const void *a, const void *b) /* Sort all literal NODATA and local IPV4 or IPV6 responses together, in a very specific order. We flip the SERV_LITERAL_ADDRESS bit so the order is IPv6 literal, IPv4 literal, all-zero literal, - upstream server, NXDOMAIN literal. */ + unqualified servers, upstream server, NXDOMAIN literal. */ if (rc == 0) - rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS) - - ((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS); + rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_USE_RESOLV | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS) - + ((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_USE_RESOLV | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS); /* Finally, order by appearance in /etc/resolv.conf etc, for --strict-order */ if (rc == 0) @@ -533,22 +546,53 @@ static int order_qsort(const void *a, const void *b) return rc; } + +/* When loading large numbers of server=.... lines during startup, + there's no possibility that there will be server records that can be reused, but + searching a long list for each server added grows as O(n^2) and slows things down. + This flag is set only if is known there may be free server records that can be reused. + There's a call to mark_servers(0) in read_opts() to reset the flag before + main config read. */ + +static int maybe_free_servers = 0; + +/* Must be called before add_update_server() to set daemon->servers_tail */ void mark_servers(int flag) { - struct server *serv; + struct server *serv, *next, **up; + maybe_free_servers = !!flag; + + daemon->servers_tail = NULL; + /* mark everything with argument flag */ for (serv = daemon->servers; serv; serv = serv->next) - if (serv->flags & flag) - serv->flags |= SERV_MARK; - else - serv->flags &= ~SERV_MARK; + { + if (serv->flags & flag) + serv->flags |= SERV_MARK; + else + serv->flags &= ~SERV_MARK; - for (serv = daemon->local_domains; serv; serv = serv->next) - if (serv->flags & flag) - serv->flags |= SERV_MARK; - else - serv->flags &= ~SERV_MARK; + daemon->servers_tail = serv; + } + + /* --address etc is different: since they are expected to be + 1) numerous and 2) not reloaded often. We just delete + and recreate. */ + if (flag) + for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = next) + { + next = serv->next; + + if (serv->flags & flag) + { + *up = next; + free(serv->domain); + free(serv); + } + else + up = &serv->next; + } } void cleanup_servers(void) @@ -556,7 +600,7 @@ void cleanup_servers(void) struct server *serv, *tmp, **up; /* unlink and free anything still marked. */ - for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp) + for (serv = daemon->servers, up = &daemon->servers, daemon->servers_tail = NULL; serv; serv = tmp) { tmp = serv->next; if (serv->flags & SERV_MARK) @@ -567,21 +611,11 @@ void cleanup_servers(void) free(serv); } else - up = &serv->next; + { + up = &serv->next; + daemon->servers_tail = serv; + } } - - 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; - } } int add_update_server(int flags, @@ -609,82 +643,95 @@ int add_update_server(int flags, if (*domain == 0) alloc_domain = whine_malloc(1); - else if (!(alloc_domain = canonicalise((char *)domain, NULL))) + else + alloc_domain = canonicalise((char *)domain, NULL); + + if (!alloc_domain) return 0; - - /* See if there is a suitable candidate, and unmark - only do this for forwarding servers, not - address or local, to avoid delays on large numbers. */ + if (flags & SERV_IS_LOCAL) - for (serv = daemon->servers; serv; serv = serv->next) - if ((serv->flags & SERV_MARK) && - hostname_isequal(alloc_domain, serv->domain)) - break; - - if (serv) - { - free(alloc_domain); - alloc_domain = serv->domain; - } - else { size_t size; - - if (flags & SERV_LITERAL_ADDRESS) - { - if (flags & SERV_6ADDR) - size = sizeof(struct serv_addr6); - else if (flags & SERV_4ADDR) - size = sizeof(struct serv_addr4); - else - size = sizeof(struct serv_local); - } + + if (flags & SERV_6ADDR) + size = sizeof(struct serv_addr6); + else if (flags & SERV_4ADDR) + size = sizeof(struct serv_addr4); else - size = sizeof(struct server); + size = sizeof(struct serv_local); if (!(serv = whine_malloc(size))) - return 0; + { + free(alloc_domain); + return 0; + } + + serv->next = daemon->local_domains; + daemon->local_domains = serv; - if (flags & SERV_IS_LOCAL) + if (flags & SERV_4ADDR) + ((struct serv_addr4*)serv)->addr = local_addr->addr4; + + if (flags & SERV_6ADDR) + ((struct serv_addr6*)serv)->addr = local_addr->addr6; + } + else + { + /* Upstream servers. See if there is a suitable candidate, if so unmark + and move to the end of the list, for order. The entry found may already + be at the end. */ + struct server **up, *tmp; + + serv = NULL; + + if (maybe_free_servers) + for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp) + { + tmp = serv->next; + if ((serv->flags & SERV_MARK) && + hostname_isequal(alloc_domain, serv->domain)) + { + /* Need to move down? */ + if (serv->next) + { + *up = serv->next; + daemon->servers_tail->next = serv; + daemon->servers_tail = serv; + serv->next = NULL; + } + break; + } + else + up = &serv->next; + } + + if (serv) { - serv->next = daemon->local_domains; - daemon->local_domains = serv; + free(alloc_domain); + alloc_domain = serv->domain; } else { - struct server *s; - /* Add to the end of the chain, for order */ - if (!daemon->servers) - daemon->servers = serv; - else + if (!(serv = whine_malloc(sizeof(struct server)))) { - for (s = daemon->servers; s->next; s = s->next); - s->next = serv; + free(alloc_domain); + return 0; } - serv->next = NULL; + memset(serv, 0, sizeof(struct server)); + + /* Add to the end of the chain, for order */ + if (daemon->servers_tail) + daemon->servers_tail->next = serv; + else + daemon->servers = serv; + daemon->servers_tail = serv; } - } - - if (!(flags & SERV_IS_LOCAL)) - memset(serv, 0, sizeof(struct server)); - - serv->flags = flags; - serv->domain = alloc_domain; - serv->domain_len = strlen(alloc_domain); - - if (flags & SERV_4ADDR) - ((struct serv_addr4*)serv)->addr = local_addr->addr4; - - if (flags & SERV_6ADDR) - ((struct serv_addr6*)serv)->addr = local_addr->addr6; - - if (!(flags & SERV_IS_LOCAL)) - { + #ifdef HAVE_LOOP serv->uid = rand32(); #endif - + if (interface) safe_strncpy(serv->interface, interface, sizeof(serv->interface)); if (addr) @@ -692,7 +739,11 @@ int add_update_server(int flags, if (source_addr) serv->source_addr = *source_addr; } - + + serv->flags = flags; + serv->domain = alloc_domain; + serv->domain_len = strlen(alloc_domain); + return 1; } |