Change the method of allocation of random source ports for DNS.

Previously, without min-port or max-port configured, dnsmasq would
default to the compiled in defaults for those, which are 1024 and
65535. Now, when neither are configured, it defaults instead to
the kernel's ephemeral port range, which is typically
32768 to 60999 on Linux systems. This change eliminates the
possibility that dnsmasq may be using a registered port > 1024
when a long-running daemon starts up and wishes to claim it.

This change does likely slighly reduce the number of random ports
and therefore the protection from reply spoofing. The older
behaviour can be restored using the min-port and max-port config
switches should that be a concern.
This commit is contained in:
Simon Kelley
2021-03-26 21:19:39 +00:00
parent ffa4628faa
commit 4a8c098840
4 changed files with 51 additions and 24 deletions

View File

@@ -49,7 +49,20 @@ version 2.85
source address errors from fatal to run-time. The error will be source address errors from fatal to run-time. The error will be
logged and communiction with the server not possible. logged and communiction with the server not possible.
Change the method of allocation of random source ports for DNS.
Previously, without min-port or max-port configured, dnsmasq would
default to the compiled in defaults for those, which are 1024 and
65535. Now, when neither are configured, it defaults instead to
the kernel's ephemeral port range, which is typically
32768 to 60999 on Linux systems. This change eliminates the
possibility that dnsmasq may be using a registered port > 1024
when a long-running daemon starts up and wishes to claim it.
This change does likely slighly reduce the number of random ports
and therefore the protection from reply spoofing. The older
behaviour can be restored using the min-port and max-port config
switches should that be a concern.
version 2.84 version 2.84
Fix a problem, introduced in 2.83, which could see DNS replies Fix a problem, introduced in 2.83, which could see DNS replies
being sent via the wrong socket. On machines running both being sent via the wrong socket. On machines running both

View File

@@ -237,9 +237,16 @@ int main (int argc, char **argv)
die(_("Ubus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF); die(_("Ubus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF);
#endif #endif
/* Handle only one of min_port/max_port being set. */
if (daemon->min_port != 0 && daemon->max_port == 0)
daemon->max_port = MAX_PORT;
if (daemon->max_port != 0 && daemon->min_port == 0)
daemon->min_port = MIN_PORT;
if (daemon->max_port < daemon->min_port) if (daemon->max_port < daemon->min_port)
die(_("max_port cannot be smaller than min_port"), NULL, EC_BADCONF); die(_("max_port cannot be smaller than min_port"), NULL, EC_BADCONF);
now = dnsmasq_time(); now = dnsmasq_time();
if (daemon->auth_zones) if (daemon->auth_zones)

View File

@@ -1310,9 +1310,9 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind
{ {
union mysockaddr addr_copy = *addr; union mysockaddr addr_copy = *addr;
unsigned short port; unsigned short port;
int tries = 1, done = 0; int tries = 1;
unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1; unsigned short ports_avail = 1;
if (addr_copy.sa.sa_family == AF_INET) if (addr_copy.sa.sa_family == AF_INET)
port = addr_copy.in.sin_port; port = addr_copy.in.sin_port;
else else
@@ -1321,34 +1321,43 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind
/* cannot set source _port_ for TCP connections. */ /* cannot set source _port_ for TCP connections. */
if (is_tcp) if (is_tcp)
port = 0; port = 0;
else if (port == 0) else if (port == 0 && daemon->max_port != 0)
{ {
/* Bind a random port within the range given by min-port and max-port */ /* Bind a random port within the range given by min-port and max-port if either
or both are set. Otherwise use the OS's random ephemeral port allocation by
leaving port == 0 and tries == 1 */
ports_avail = daemon->max_port - daemon->min_port + 1;
tries = ports_avail < 30 ? 3 * ports_avail : 100; tries = ports_avail < 30 ? 3 * ports_avail : 100;
port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); port = htons(daemon->min_port + (rand16() % ports_avail));
} }
while (tries--) while (1)
{ {
/* elide bind() call if it's to port 0, address 0 */
if (addr_copy.sa.sa_family == AF_INET) if (addr_copy.sa.sa_family == AF_INET)
addr_copy.in.sin_port = port;
else
addr_copy.in6.sin6_port = port;
if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) != -1)
{ {
done = 1; if (port == 0 && addr_copy.in.sin_addr.s_addr == 0)
break; break;
addr_copy.in.sin_port = port;
}
else
{
if (port == 0 && IN6_IS_ADDR_UNSPECIFIED(&addr_copy.in6.sin6_addr))
break;
addr_copy.in6.sin6_port = port;
} }
if (errno != EADDRINUSE && errno != EACCES) if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) != -1)
return 0; break;
port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); if (errno != EADDRINUSE && errno != EACCES)
} return 0;
if (!done) if (--tries == 0)
return 0; return 0;
port = htons(daemon->min_port + (rand16() % ports_avail));
}
if (!is_tcp && ifindex > 0) if (!is_tcp && ifindex > 0)
{ {

View File

@@ -5076,9 +5076,7 @@ void read_opts(int argc, char **argv, char *compile_opts)
daemon->soa_refresh = SOA_REFRESH; daemon->soa_refresh = SOA_REFRESH;
daemon->soa_retry = SOA_RETRY; daemon->soa_retry = SOA_RETRY;
daemon->soa_expiry = SOA_EXPIRY; daemon->soa_expiry = SOA_EXPIRY;
daemon->max_port = MAX_PORT;
daemon->min_port = MIN_PORT;
#ifndef NO_ID #ifndef NO_ID
add_txt("version.bind", "dnsmasq-" VERSION, 0 ); add_txt("version.bind", "dnsmasq-" VERSION, 0 );
add_txt("authors.bind", "Simon Kelley", 0); add_txt("authors.bind", "Simon Kelley", 0);