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.
This commit is contained in:
Simon Kelley
2022-09-09 18:18:46 +01:00
parent 3f56bb8ba1
commit c0e731d545
4 changed files with 64 additions and 15 deletions

View File

@@ -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. */

View File

@@ -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();

View File

@@ -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;

View File

@@ -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)