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 114 additions and 39 deletions

No files matched your search

+44
View File
@@ -864,6 +864,16 @@ void cache_update_hwm(void)
} }
#endif #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. */ /* 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) int cache_recv_insert(time_t now, int fd)
{ {
@@ -1017,6 +1027,40 @@ int cache_recv_insert(time_t now, int fd)
and start processing other queries which might pollute the insertion 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 */ chain. The child will never block between the first OP_RR and the OP_END */
continue; 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_DUMPFILE
NO_LOOP NO_LOOP
NO_INOTIFY NO_INOTIFY
NO_IPSET
these are available to explicitly disable compile time options which would these are available to explicitly disable compile time options which would
otherwise be enabled automatically or which are enabled by default otherwise be enabled automatically or which are enabled by default
in the distributed source tree. Building dnsmasq in the distributed source tree. Building dnsmasq
@@ -287,7 +288,6 @@ HAVE_SOCKADDR_SA_LEN
#define HAVE_BSD_NETWORK #define HAVE_BSD_NETWORK
#define HAVE_GETOPT_LONG #define HAVE_GETOPT_LONG
#define HAVE_SOCKADDR_SA_LEN #define HAVE_SOCKADDR_SA_LEN
#define NO_IPSET
/* Define before sys/socket.h is included so we get socklen_t */ /* Define before sys/socket.h is included so we get socklen_t */
#define _BSD_SOCKLEN_T_ #define _BSD_SOCKLEN_T_
/* Select the RFC_3542 version of the IPv6 socket API. /* Select the RFC_3542 version of the IPv6 socket API.
@@ -297,7 +297,6 @@ HAVE_SOCKADDR_SA_LEN
#ifndef SOL_TCP #ifndef SOL_TCP
# define SOL_TCP IPPROTO_TCP # define SOL_TCP IPPROTO_TCP
#endif #endif
#define NO_IPSET
#elif defined(__NetBSD__) #elif defined(__NetBSD__)
#define HAVE_BSD_NETWORK #define HAVE_BSD_NETWORK
@@ -347,6 +346,11 @@ HAVE_SOCKADDR_SA_LEN
#undef HAVE_AUTH #undef HAVE_AUTH
#endif #endif
#if !defined(HAVE_LINUX_NETWORK)
#undef HAVE_IPSET
#undef HAVE_NFTSET
#endif
#if defined(NO_IPSET) #if defined(NO_IPSET)
#undef HAVE_IPSET #undef HAVE_IPSET
#endif #endif
@@ -460,4 +464,4 @@ static char *compile_opts =
#endif #endif
"dumpfile"; "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_RR 1 /* Resource record */
#define PIPE_OP_END 2 /* Cache entry complete: commit */ #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_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, /* struct sockaddr is not large enough to hold any address,
and specifically not big enough to hold an IPv6 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 /* 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 */ IPv6 addr, IPv4 addr, all zero return, no-data return, resolvconf servers, upstream server */
#define SERV_LITERAL_ADDRESS 1 /* addr is the answer, or NoDATA is the answer, depending on the next four flags */ #define SERV_USE_RESOLV 1 /* forward this domain in the normal way */
#define SERV_USE_RESOLV 2 /* 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_ALL_ZEROS 4 /* return all zeros for A and AAAA */
#define SERV_4ADDR 8 /* addr is IPv4 */ #define SERV_4ADDR 8 /* addr is IPv4 */
#define SERV_6ADDR 16 /* addr is IPv6 */ #define SERV_6ADDR 16 /* addr is IPv6 */
@@ -1361,6 +1363,10 @@ int cache_recv_insert(time_t now, int fd);
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
void cache_update_hwm(void); void cache_update_hwm(void);
#endif #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, struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class,
time_t now, unsigned long ttl, unsigned int flags); time_t now, unsigned long ttl, unsigned int flags);
void cache_reload(void); 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_qsort(const void *a, const void *b);
static int order_servers(struct server *s, struct server *s2); 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) #define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS)
void build_server_array(void) void build_server_array(void)
@@ -259,7 +259,6 @@ int lookup_domain(char *domain, int flags, int *lowout, int *highout)
return 1; return 1;
} }
/* Return first server in group of equivalent servers; this is the "master" record. */
int server_samegroup(struct server *a, struct server *b) int server_samegroup(struct server *a, struct server *b)
{ {
return order_servers(a, b) == 0; return order_servers(a, b) == 0;
@@ -296,8 +295,10 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
} }
else else
{ {
/* Now the servers are on order between low and high, in the order /* Now the matching server records are all between low and high.
IPv6 addr, IPv4 addr, return zero for both, resolvconf servers, send upstream, no-data return. 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) */ See which of those match our query in that priority order and narrow (low, high) */
@@ -325,29 +326,27 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
{ {
nlow = i; nlow = i;
/* Short to resolv.conf servers */ /* now look for a NXDOMAIN answer --local=/domain/ */
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
if (!(flags & (F_DOMAINSRV | F_SERVER)) && i != nlow)
nhigh = i;
else
{
nlow = i;
/* return "use resolv.conf servers" if they exist */
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_USE_RESOLV); i++); for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_USE_RESOLV); i++);
if (i != nlow) if (i != nlow)
nhigh = i; nhigh = i;
else else
{
/* 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 for a particular domain, and this one isn't, return nothing. */ /* 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) if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
nlow = nhigh; nlow = nhigh;
else else
nhigh = i; nlow = i;
}
else
{
/* --local=/domain/, only return if we don't need a server. */
if (flags & (F_DOMAINSRV | F_SERVER))
nhigh = 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); setup_reply(header, flags, ede);
gotname &= ~F_QUERY;
if (flags & (F_NXDOMAIN | F_NOERR)) if (flags & (F_NXDOMAIN | F_NOERR))
log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL, 0); 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); rc = order_servers(s1, s2);
/* Sort all literal NODATA and local IPV4 or IPV6 responses together, /* Sort all literal NODATA and local IPV4 or IPV6 responses together,
in a very specific order. We flip the SERV_LITERAL_ADDRESS bit in a very specific order IPv6 literal, IPv4 literal, all-zero literal,
so the order is IPv6 literal, IPv4 literal, all-zero literal, NXDOMAIN literal. We also include SERV_USE_RESOLV in this, so that
unqualified servers, upstream server, NXDOMAIN literal. */ 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) if (rc == 0)
rc = ((s2->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_USE_RESOLV | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS); ((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 */ /* Finally, order by appearance in /etc/resolv.conf etc, for --strict-order */
if (rc == 0) if (rc == 0)
+8 -5
View File
@@ -704,14 +704,17 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
(void)ad_reqd; (void)ad_reqd;
(void)do_bit; (void)do_bit;
#ifdef HAVE_IPSET #if defined(HAVE_IPSET) || defined(HAVE_NFTSET)
if (daemon->ipsets && extract_name(header, n, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0)) 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); ipsets = domain_find_sets(daemon->ipsets, daemon->namebuff);
#endif # endif
#ifdef HAVE_NFTSET # 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); nftsets = domain_find_sets(daemon->nftsets, daemon->namebuff);
# endif
}
#endif #endif
if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL))) if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL)))
+1 -1
View File
@@ -16,7 +16,7 @@
#include "dnsmasq.h" #include "dnsmasq.h"
#if defined(HAVE_IPSET) && defined(HAVE_LINUX_NETWORK) #if defined(HAVE_IPSET)
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
+1 -1
View File
@@ -1665,7 +1665,7 @@ void check_servers(int no_loop_check)
if (++locals <= LOCALS_LOGGED) if (++locals <= LOCALS_LOGGED)
my_syslog(LOG_INFO, _("using only locally-known addresses for %s"), serv->domain); 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); my_syslog(LOG_INFO, _("using standard nameservers for %s"), serv->domain);
} }
+1 -1
View File
@@ -17,7 +17,7 @@
#include "dnsmasq.h" #include "dnsmasq.h"
#if defined (HAVE_NFTSET) && defined (HAVE_LINUX_NETWORK) #if defined (HAVE_NFTSET)
#include <nftables/libnftables.h> #include <nftables/libnftables.h>
+17 -2
View File
@@ -1058,19 +1058,34 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
return 1; 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 #ifdef HAVE_IPSET
if (ipsets && (flags & (F_IPV4 | F_IPV6))) 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++) for (ipsets_cur = ipsets->sets; *ipsets_cur; ipsets_cur++)
if (add_to_ipset(*ipsets_cur, &addr, flags, 0) == 0) 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); log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, ipsets->domain, &addr, *ipsets_cur, 1);
}
#endif #endif
#ifdef HAVE_NFTSET #ifdef HAVE_NFTSET
if (nftsets && (flags & (F_IPV4 | F_IPV6))) 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++) for (nftsets_cur = nftsets->sets; *nftsets_cur; nftsets_cur++)
if (add_to_nftset(*nftsets_cur, &addr, flags, 0) == 0) 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); log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, nftsets->domain, &addr, *nftsets_cur, 0);
}
#endif #endif
} }
}
if (insert) if (insert)
{ {