diff options
author | Simon Kelley <simon@thekelleys.org.uk> | 2022-09-09 18:18:46 +0100 |
---|---|---|
committer | Simon Kelley <simon@thekelleys.org.uk> | 2022-09-09 23:15:50 +0100 |
commit | c0e731d5456338c2756b975af81f7f3d445d1a0d (patch) | |
tree | e12b043812df225c2f51780aa34eb40d5bcdb83e | |
parent | 3f56bb8ba195f72a6bf1d572f8add07b4fc44ec0 (diff) | |
download | dnsmasq-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.h | 1 | ||||
-rw-r--r-- | src/dnsmasq.c | 4 | ||||
-rw-r--r-- | src/forward.c | 70 | ||||
-rw-r--r-- | src/network.c | 4 |
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) |