summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Kelley <simon@thekelleys.org.uk>2022-09-09 18:18:46 +0100
committerSimon Kelley <simon@thekelleys.org.uk>2022-09-09 23:15:50 +0100
commitc0e731d5456338c2756b975af81f7f3d445d1a0d (patch)
treee12b043812df225c2f51780aa34eb40d5bcdb83e
parent3f56bb8ba195f72a6bf1d572f8add07b4fc44ec0 (diff)
downloaddnsmasq-c0e731d5456338c2756b975af81f7f3d445d1a0d.tar.gz
Further optimisation of --port-limit.
No longer try and fail to open every port when the port range is in complete use; go straight to re-using an existing socket. Die at startup if port range is smaller than --port-limit, since the code behaves badly in this case.
-rw-r--r--src/config.h1
-rw-r--r--src/dnsmasq.c4
-rw-r--r--src/forward.c70
-rw-r--r--src/network.c4
4 files changed, 64 insertions, 15 deletions
diff --git a/src/config.h b/src/config.h
index cd37900..5746c0b 100644
--- a/src/config.h
+++ b/src/config.h
@@ -24,6 +24,7 @@
#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
#define DNSSEC_WORK 50 /* Max number of queries to validate one question */
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
+#define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */
#define FORWARD_TEST 50 /* try all servers every 50 queries */
#define FORWARD_TIME 20 /* or 20 seconds */
#define UDP_TEST_TIME 60 /* How often to reset our idea of max packet size. */
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 4742a97..bd3dcf5 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -265,6 +265,10 @@ int main (int argc, char **argv)
if (daemon->max_port < daemon->min_port)
die(_("max_port cannot be smaller than min_port"), NULL, EC_BADCONF);
+
+ if (daemon->max_port != 0 &&
+ daemon->max_port - daemon->min_port + 1 < daemon->randport_limit)
+ die(_("port_limit must not be larger than available port range"), NULL, EC_BADCONF);
now = dnsmasq_time();
diff --git a/src/forward.c b/src/forward.c
index 681ee90..f2dbbc1 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -2435,6 +2435,21 @@ static int random_sock(struct server *s)
if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1)
{
+ /* We need to set IPV6ONLY so we can use the same ports
+ for IPv4 and IPV6, otherwise, in restriced port situations,
+ we can end up with all our available ports in use for
+ one address family, and the other address family cannot be used. */
+ if (s->source_addr.sa.sa_family == AF_INET6)
+ {
+ int opt = 1;
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1)
+ {
+ close(fd);
+ return -1;
+ }
+ }
+
if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0))
return fd;
@@ -2479,9 +2494,22 @@ int allocate_rfd(struct randfd_list **fdlp, struct server *serv)
{
static int finger = 0;
int i, j = 0;
+ int ports_full = 0;
struct randfd_list **up, *rfl, *found, **found_link;
struct randfd *rfd = NULL;
int fd = 0;
+ int ports_avail = 0;
+
+ /* We can't have more randomsocks for this AF available than ports in our port range,
+ so check that here, to avoid trying and failing to bind every port
+ in local_bind(), called from random_sock(). The actual check is below when
+ ports_avail != 0 */
+ if (daemon->max_port != 0)
+ {
+ ports_avail = daemon->max_port - daemon->min_port + 1;
+ if (ports_avail >= SMALL_PORT_RANGE)
+ ports_avail = 0;
+ }
/* If server has a pre-allocated fd, use that. */
if (serv->sfd)
@@ -2507,22 +2535,38 @@ int allocate_rfd(struct randfd_list **fdlp, struct server *serv)
*fdlp = found;
return found->rfd->fd;
}
+
+ /* check for all available ports in use. */
+ if (ports_avail != 0)
+ {
+ int ports_inuse;
+
+ for (ports_inuse = 0, i = 0; i < daemon->numrrand; i++)
+ if (daemon->randomsocks[i].refcount != 0 &&
+ daemon->randomsocks[i].serv->source_addr.sa.sa_family == serv->source_addr.sa.sa_family &&
+ ++ports_inuse >= ports_avail)
+ {
+ ports_full = 1;
+ break;
+ }
+ }
/* limit the number of sockets we have open to avoid starvation of
(eg) TFTP. Once we have a reasonable number, randomness should be OK */
- for (i = 0; i < daemon->numrrand; i++)
- if (daemon->randomsocks[i].refcount == 0)
- {
- if ((fd = random_sock(serv)) != -1)
- {
- rfd = &daemon->randomsocks[i];
- rfd->serv = serv;
- rfd->fd = fd;
- rfd->refcount = 1;
- }
- break;
- }
-
+ if (!ports_full)
+ for (i = 0; i < daemon->numrrand; i++)
+ if (daemon->randomsocks[i].refcount == 0)
+ {
+ if ((fd = random_sock(serv)) != -1)
+ {
+ rfd = &daemon->randomsocks[i];
+ rfd->serv = serv;
+ rfd->fd = fd;
+ rfd->refcount = 1;
+ }
+ break;
+ }
+
/* No good existing. Need new link. */
if ((rfl = daemon->rfl_spare))
daemon->rfl_spare = rfl->next;
diff --git a/src/network.c b/src/network.c
index 9ecb9c0..3c53683 100644
--- a/src/network.c
+++ b/src/network.c
@@ -1371,7 +1371,7 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind
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) ? ports_avail : 100;
+ tries = (ports_avail < SMALL_PORT_RANGE) ? ports_avail : 100;
port = htons(daemon->min_port + (rand16() % ports_avail));
}
@@ -1401,7 +1401,7 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind
return 0;
/* For small ranges, do a systematic search, not a random one. */
- if (ports_avail < 30)
+ if (ports_avail < SMALL_PORT_RANGE)
{
unsigned short hport = ntohs(port);
if (hport++ == daemon->max_port)