mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 02:08:24 +00:00
Fix failure to add client MAC address to queries in TCP mode.
The options which cause dnsmasq to decorate a DNS query with the MAC address on the originating client can fail when the query is sent using TCP. In TCP mode, dnsmasq spawns a new process to handle each TCP connection. These child processes do not have an open netlink socket, which is needed to read the kernel ARP table, so the process of adding the client's MAC address as an EDNS0 option silently fails. This is fixed by this patch by updating dnsmasq's ARP cache in the main process just before forking the TCP-handler child process. This ensures that the copy of the ARP cache inherited by the TCP-handler contains the information required without the need to read the kernel ARP table. Thanks to Bruno Ravara for spotting and characterising this bug.
This commit is contained in:
@@ -70,6 +70,12 @@ version 2.92
|
|||||||
is functional when client and server networks aren't mutually
|
is functional when client and server networks aren't mutually
|
||||||
route-able.
|
route-able.
|
||||||
|
|
||||||
|
Fix failure to add client MAC address to queries in TCP mode.
|
||||||
|
The options which cause dnsmasq to decorate a DNS query with the MAC
|
||||||
|
address on the originating client can fail when the query is sent
|
||||||
|
using TCP. Thanks to Bruno Ravara for spotting and
|
||||||
|
characterising this bug.
|
||||||
|
|
||||||
|
|
||||||
version 2.91
|
version 2.91
|
||||||
Fix spurious "resource limit exceeded messages". Thanks to
|
Fix spurious "resource limit exceeded messages". Thanks to
|
||||||
|
|||||||
18
src/arp.c
18
src/arp.c
@@ -111,22 +111,26 @@ int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now)
|
|||||||
|
|
||||||
again:
|
again:
|
||||||
|
|
||||||
/* If the database is less then INTERVAL old, look in there */
|
/* If the database is less then INTERVAL old, look in there.
|
||||||
if (difftime(now, last) < INTERVAL)
|
|
||||||
|
If we're a child process, we always rely on the existing cache we
|
||||||
|
inherited from the parent, since we don't have a netlink socket.
|
||||||
|
*/
|
||||||
|
if (difftime(now, last) < INTERVAL || daemon->pipe_to_parent != -1)
|
||||||
{
|
{
|
||||||
/* addr == NULL -> just make cache up-to-date */
|
/* addr == NULL -> just make cache up-to-date */
|
||||||
if (!addr)
|
if (!addr)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
for (arp = arps; arp; arp = arp->next)
|
for (arp = arps; arp; arp = arp->next)
|
||||||
{
|
{
|
||||||
if (addr->sa.sa_family != arp->family)
|
if (addr->sa.sa_family != arp->family)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (arp->family == AF_INET &&
|
if (arp->family == AF_INET &&
|
||||||
arp->addr.addr4.s_addr != addr->in.sin_addr.s_addr)
|
arp->addr.addr4.s_addr != addr->in.sin_addr.s_addr)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (arp->family == AF_INET6 &&
|
if (arp->family == AF_INET6 &&
|
||||||
!IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, &addr->in6.sin6_addr))
|
!IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, &addr->in6.sin6_addr))
|
||||||
continue;
|
continue;
|
||||||
@@ -141,6 +145,10 @@ int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Not in cache in child, no go. */
|
||||||
|
if (daemon->pipe_to_parent != -1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
/* Not found, try the kernel */
|
/* Not found, try the kernel */
|
||||||
if (!updated)
|
if (!updated)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2033,9 +2033,24 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
|
|||||||
|
|
||||||
if (!option_bool(OPT_DEBUG))
|
if (!option_bool(OPT_DEBUG))
|
||||||
{
|
{
|
||||||
|
/* The code in edns0.c qthat decorates queries with the source MAC address depends
|
||||||
|
on the code in arp.c, which populates a cache with the contents of the ARP table
|
||||||
|
using netlink. Since the child process can't use netlink, we pre-populate
|
||||||
|
the cache with the ARP table entry for our source here, including a negative entry
|
||||||
|
if there is nothing for our address in the ARP table.
|
||||||
|
|
||||||
|
When the edns0 code calls find_mac() in the child process, it will
|
||||||
|
get the correct answer from the cache inherited from the parent
|
||||||
|
without having to use netlink to consult the kernel ARP table.
|
||||||
|
|
||||||
|
edns0_needs_mac() simply calls find_mac if any EDNS0 options
|
||||||
|
which need a MAC address are enabled. */
|
||||||
|
|
||||||
|
edns0_needs_mac(&tcp_addr, now);
|
||||||
|
|
||||||
if (pipe(pipefd) == -1)
|
if (pipe(pipefd) == -1)
|
||||||
goto closeconandreturn; /* pipe failed */
|
goto closeconandreturn; /* pipe failed */
|
||||||
|
|
||||||
if ((p = fork()) == -1)
|
if ((p = fork()) == -1)
|
||||||
{
|
{
|
||||||
/* fork failed */
|
/* fork failed */
|
||||||
|
|||||||
@@ -1918,6 +1918,7 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
|
|||||||
size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
|
size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
|
||||||
int optno, unsigned char *opt, size_t optlen, int set_do, int replace);
|
int optno, unsigned char *opt, size_t optlen, int set_do, int replace);
|
||||||
size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit);
|
size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit);
|
||||||
|
void edns0_needs_mac(union mysockaddr *addr, time_t now);
|
||||||
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
|
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
|
||||||
union mysockaddr *source, time_t now, int *cacheable);
|
union mysockaddr *source, time_t now, int *cacheable);
|
||||||
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
|
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
|
||||||
|
|||||||
10
src/edns0.c
10
src/edns0.c
@@ -265,6 +265,15 @@ static void encoder(unsigned char *in, char *out)
|
|||||||
out[3] = char64(in[2]);
|
out[3] = char64(in[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This function needs to call find_mac if any option which requires a MAC address is enabled
|
||||||
|
and used below. If you add a new MAC consumer, modify this, otherwise your
|
||||||
|
new EDNS0 option won't work in TCP mode. */
|
||||||
|
void edns0_needs_mac(union mysockaddr *addr, time_t now)
|
||||||
|
{
|
||||||
|
if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX) || option_bool(OPT_ADD_MAC))
|
||||||
|
find_mac(addr, NULL, 0, now);
|
||||||
|
}
|
||||||
|
|
||||||
/* OPT_ADD_MAC = MAC is added (if available)
|
/* OPT_ADD_MAC = MAC is added (if available)
|
||||||
OPT_ADD_MAC + OPT_STRIP_MAC = MAC is replaced, if not available, it is only removed
|
OPT_ADD_MAC + OPT_STRIP_MAC = MAC is replaced, if not available, it is only removed
|
||||||
OPT_STRIP_MAC = MAC is removed */
|
OPT_STRIP_MAC = MAC is removed */
|
||||||
@@ -562,3 +571,4 @@ size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *l
|
|||||||
|
|
||||||
return plen;
|
return plen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user