Compare commits

...
3 Commits
Author SHA1 Message Date
Simon Kelley 98189ff988 Fix problems with ipset or nftset and TCP DNS transport.
If DNS is happening over TCP, the query is handled by a forked
process. Of ipset ot nftset is configured, this might include
inserting addresses in the *sets. Before this update, that
was done by the forked process using handles inherited from the
parent "master" process.

This is inherently racy. If the master process or another
child process tries to do updates at the same time, the
updates can clash and fail.

To see this, you need a busy server doing lots of DNS
queries over TCP, and ipset or nftset configured.

Going forward, we use the already established pipe to send the
updates from the child back to the master process, which
serialises them.
2025-05-08 16:07:19 +01:00
Simon Kelley e86d53c438 Fix some edge cases wth domains and --address and --server.
Consider what happens when the same domain appears in
--address and --server.

This commit fixes the order, I think correctly like this:
highest to lowest priority.

--address with a IPv4 or IPv6 address (as long as the query matches the type)
--address with # for all-zeros, as long as the query is A or AAAA)
--address with no address, which returns NXDOMAIN or NOERROR for all types.
--server with address set to # to use the unqualified servers.
--server with matching domain.
--server without domain or from /etc/resolv.conf.

Note that the above is only valid when same domain appears.
The domain being matched is determined first, and has a higher
priority, so you can send google.com to a server and force com
to return NXDOMAIN and for google.com the server config will
override the address config, because there's a longer match.
2025-04-29 16:33:22 +01:00
Simon Kelley e127a972d1 Fix logging booboo. 2025-04-27 23:25:30 +01:00
9 changed files with 126 additions and 51 deletions

No files matched your search

+45 -1
View File
@@ -864,6 +864,16 @@ void cache_update_hwm(void)
}
#endif
#if defined(HAVE_IPSET) || defined(HAVE_NFTSET)
void cache_send_ipset(unsigned char op, struct ipsets *sets, int flags, union all_addr *addr)
{
read_write(daemon->pipe_to_parent, &op, sizeof(op), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)&sets, sizeof(sets), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)addr, sizeof(*addr), RW_WRITE);
}
#endif
/* A marshalled cache entry arrives on fd, read, unmarshall and insert into cache of master process. */
int cache_recv_insert(time_t now, int fd)
{
@@ -1012,11 +1022,45 @@ int cache_recv_insert(time_t now, int fd)
#endif
crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags);
}
/* loop reading RRs, since we don't want to go back to the poll() loop
and start processing other queries which might pollute the insertion
chain. The child will never block between the first OP_RR and the OP_END */
continue;
#if defined(HAVE_IPSET) || defined(HAVE_NFTSET)
case PIPE_OP_IPSET:
case PIPE_OP_NFTSET:
{
struct ipsets *sets;
char **sets_cur;
if (!read_write(fd, (unsigned char *)&sets, sizeof(sets), RW_READ) ||
!read_write(fd, (unsigned char *)&flags, sizeof(flags), RW_READ) ||
!read_write(fd, (unsigned char *)&addr, sizeof(addr), RW_READ))
return 0;
for (sets_cur = sets->sets; *sets_cur; sets_cur++)
{
int rc = -1;
#ifdef HAVE_IPSET
if (op == PIPE_OP_IPSET)
rc = add_to_ipset(*sets_cur, &addr, flags, 0);
#endif
#ifdef HAVE_NFTSET
if (op == PIPE_OP_NFTSET)
rc = add_to_nftset(*sets_cur, &addr, flags, 0);
#endif
if (rc == 0)
log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, sets->domain, &addr, *sets_cur, op == PIPE_OP_IPSET);
}
return 1;
}
#endif
}
}
}
+7 -3
View File
@@ -153,6 +153,7 @@ NO_AUTH
NO_DUMPFILE
NO_LOOP
NO_INOTIFY
NO_IPSET
these are available to explicitly disable compile time options which would
otherwise be enabled automatically or which are enabled by default
in the distributed source tree. Building dnsmasq
@@ -287,7 +288,6 @@ HAVE_SOCKADDR_SA_LEN
#define HAVE_BSD_NETWORK
#define HAVE_GETOPT_LONG
#define HAVE_SOCKADDR_SA_LEN
#define NO_IPSET
/* Define before sys/socket.h is included so we get socklen_t */
#define _BSD_SOCKLEN_T_
/* Select the RFC_3542 version of the IPv6 socket API.
@@ -297,7 +297,6 @@ HAVE_SOCKADDR_SA_LEN
#ifndef SOL_TCP
# define SOL_TCP IPPROTO_TCP
#endif
#define NO_IPSET
#elif defined(__NetBSD__)
#define HAVE_BSD_NETWORK
@@ -347,6 +346,11 @@ HAVE_SOCKADDR_SA_LEN
#undef HAVE_AUTH
#endif
#if !defined(HAVE_LINUX_NETWORK)
#undef HAVE_IPSET
#undef HAVE_NFTSET
#endif
#if defined(NO_IPSET)
#undef HAVE_IPSET
#endif
@@ -460,4 +464,4 @@ static char *compile_opts =
#endif
"dumpfile";
#endif /* defined(HAVE_DHCP) */
#endif /* defined(DNSMASQ_COMPILE_OPTS) */
+10 -4
View File
@@ -539,8 +539,10 @@ struct crec {
#define PIPE_OP_RR 1 /* Resource record */
#define PIPE_OP_END 2 /* Cache entry complete: commit */
#define PIPE_OP_RESULT 3 /* validation result. */
#define PIPE_OP_RESULT 3 /* Validation result */
#define PIPE_OP_STATS 4 /* Update parent's stats */
#define PIPE_OP_IPSET 5 /* Update IPset */
#define PIPE_OP_NFTSET 6 /* Update NFTset */
/* struct sockaddr is not large enough to hold any address,
and specifically not big enough to hold an IPv6 address.
@@ -558,9 +560,9 @@ union mysockaddr {
/* The actual values here matter, since we sort on them to get records in the order
IPv6 addr, IPv4 addr, all zero return, resolvconf servers, upstream server, no-data return */
#define SERV_LITERAL_ADDRESS 1 /* addr is the answer, or NoDATA is the answer, depending on the next four flags */
#define SERV_USE_RESOLV 2 /* forward this domain in the normal way */
IPv6 addr, IPv4 addr, all zero return, no-data return, resolvconf servers, upstream server */
#define SERV_USE_RESOLV 1 /* forward this domain in the normal way */
#define SERV_LITERAL_ADDRESS 2 /* addr is the answer, or NoDATA is the answer, depending on the next four flags */
#define SERV_ALL_ZEROS 4 /* return all zeros for A and AAAA */
#define SERV_4ADDR 8 /* addr is IPv4 */
#define SERV_6ADDR 16 /* addr is IPv6 */
@@ -1361,6 +1363,10 @@ int cache_recv_insert(time_t now, int fd);
#ifdef HAVE_DNSSEC
void cache_update_hwm(void);
#endif
#if defined(HAVE_IPSET) || defined(HAVE_NFTSET)
void cache_send_ipset(unsigned char op, struct ipsets *sets,
int flags, union all_addr *addr);
#endif
struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class,
time_t now, unsigned long ttl, unsigned int flags);
void cache_reload(void);
+25 -22
View File
@@ -20,7 +20,7 @@ static int order(char *qdomain, 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);
/* If the server is USE_RESOLV or LITERAL_ADDRES, it lives on the local_domains chain. */
/* If the server is USE_RESOLV or LITERAL_ADDRESS, it lives on the local_domains chain. */
#define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS)
void build_server_array(void)
@@ -259,7 +259,6 @@ int lookup_domain(char *domain, int flags, int *lowout, int *highout)
return 1;
}
/* Return first server in group of equivalent servers; this is the "master" record. */
int server_samegroup(struct server *a, struct server *b)
{
return order_servers(a, b) == 0;
@@ -296,11 +295,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, resolvconf servers, send upstream, no-data return.
/* Now the matching server records are all between low and high.
order_qsort() ensures that they are in the order
IPv6 addr, IPv4 addr, return zero for both, no-data return,
"use resolvconf" servers, domain-specific upstream servers.
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 (!(flags & F_SERVER) && i != nlow && (flags & F_IPV6))
@@ -325,29 +326,27 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
{
nlow = i;
/* Short to resolv.conf servers */
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_USE_RESOLV); i++);
/* now look for a NXDOMAIN answer --local=/domain/ */
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
if (i != nlow)
if (!(flags & (F_DOMAINSRV | F_SERVER)) && i != nlow)
nhigh = i;
else
{
/* now look for a server */
for (i = nlow; i < nhigh && !(daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
nlow = i;
/* return "use resolv.conf servers" if they exist */
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_USE_RESOLV); i++);
if (i != nlow)
nhigh = i;
else
{
/* If we want a server for a particular domain, and this one isn't, return nothing. */
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_DOMAINSRV | F_SERVER))
nhigh = i;
nlow = i;
}
}
}
@@ -402,6 +401,8 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
setup_reply(header, flags, ede);
gotname &= ~F_QUERY;
if (flags & (F_NXDOMAIN | F_NOERR))
log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL, 0);
@@ -541,12 +542,14 @@ static int order_qsort(const void *a, const void *b)
rc = order_servers(s1, s2);
/* 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,
unqualified servers, upstream server, NXDOMAIN literal. */
in a very specific order IPv6 literal, IPv4 literal, all-zero literal,
NXDOMAIN literal. We also include SERV_USE_RESOLV in this, so that
use-standard servers sort before ordinary servers. (SERV_USR_RESOLV set
implies that none of SERV_LITERAL_ADDRESS,SERV_4ADDR,SERV_6ADDR,SERV_ALL_ZEROS
are set) */
if (rc == 0)
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);
rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS | SERV_USE_RESOLV))) -
((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS | SERV_USE_RESOLV)));
/* Finally, order by appearance in /etc/resolv.conf etc, for --strict-order */
if (rc == 0)
+12 -9
View File
@@ -704,16 +704,19 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
(void)ad_reqd;
(void)do_bit;
#ifdef HAVE_IPSET
if (daemon->ipsets && extract_name(header, n, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
ipsets = domain_find_sets(daemon->ipsets, daemon->namebuff);
#if defined(HAVE_IPSET) || defined(HAVE_NFTSET)
if ((daemon->ipsets || daemon->nftsets) && extract_name(header, n, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
{
# ifdef HAVE_IPSET
ipsets = domain_find_sets(daemon->ipsets, daemon->namebuff);
# endif
# ifdef HAVE_NFTSET
nftsets = domain_find_sets(daemon->nftsets, daemon->namebuff);
# endif
}
#endif
#ifdef HAVE_NFTSET
if (daemon->nftsets && extract_name(header, n, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
nftsets = domain_find_sets(daemon->nftsets, daemon->namebuff);
#endif
if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL)))
{
/* Get extended RCODE. */
+1 -1
View File
@@ -16,7 +16,7 @@
#include "dnsmasq.h"
#if defined(HAVE_IPSET) && defined(HAVE_LINUX_NETWORK)
#if defined(HAVE_IPSET)
#include <string.h>
#include <errno.h>
+1 -1
View File
@@ -1665,7 +1665,7 @@ void check_servers(int no_loop_check)
if (++locals <= LOCALS_LOGGED)
my_syslog(LOG_INFO, _("using only locally-known addresses for %s"), serv->domain);
}
else if (serv->flags & SERV_USE_RESOLV)
else if (serv->flags & SERV_USE_RESOLV && serv->domain_len != 0)
my_syslog(LOG_INFO, _("using standard nameservers for %s"), serv->domain);
}
+1 -1
View File
@@ -17,7 +17,7 @@
#include "dnsmasq.h"
#if defined (HAVE_NFTSET) && defined (HAVE_LINUX_NETWORK)
#if defined (HAVE_NFTSET)
#include <nftables/libnftables.h>
+24 -9
View File
@@ -1057,19 +1057,34 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
private_net6(&addr.addr6, !option_bool(OPT_LOCAL_REBIND)))
return 1;
}
if (flags & (F_IPV4 | F_IPV6))
{
/* If we're a child process, send this to the parent,
since the ipset and nfset access is not re-entrant. */
#ifdef HAVE_IPSET
if (ipsets && (flags & (F_IPV4 | F_IPV6)))
for (ipsets_cur = ipsets->sets; *ipsets_cur; ipsets_cur++)
if (add_to_ipset(*ipsets_cur, &addr, flags, 0) == 0)
log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, ipsets->domain, &addr, *ipsets_cur, 1);
if (ipsets)
{
if (daemon->pipe_to_parent != -1)
cache_send_ipset(PIPE_OP_IPSET, ipsets, flags, &addr);
else
for (ipsets_cur = ipsets->sets; *ipsets_cur; ipsets_cur++)
if (add_to_ipset(*ipsets_cur, &addr, flags, 0) == 0)
log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, ipsets->domain, &addr, *ipsets_cur, 1);
}
#endif
#ifdef HAVE_NFTSET
if (nftsets && (flags & (F_IPV4 | F_IPV6)))
for (nftsets_cur = nftsets->sets; *nftsets_cur; nftsets_cur++)
if (add_to_nftset(*nftsets_cur, &addr, flags, 0) == 0)
log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, nftsets->domain, &addr, *nftsets_cur, 0);
if (nftsets)
{
if (daemon->pipe_to_parent != -1)
cache_send_ipset(PIPE_OP_NFTSET, nftsets, flags, &addr);
else
for (nftsets_cur = nftsets->sets; *nftsets_cur; nftsets_cur++)
if (add_to_nftset(*nftsets_cur, &addr, flags, 0) == 0)
log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, nftsets->domain, &addr, *nftsets_cur, 0);
}
#endif
}
}
if (insert)