Add --port-limit option.

By default, when sending a query via random ports to multiple upstream servers or
retrying a query dnsmasq will use a single random port for all the tries/retries.
This option allows a larger number of ports to be used, which can increase robustness
in certain network configurations. Note that increasing this to more than
two or three can have security and resource implications and should only
be done with understanding of those.
This commit is contained in:
Simon Kelley
2022-08-17 15:33:15 +01:00
parent 4447d48bb9
commit 24c3b5b3d4
4 changed files with 64 additions and 11 deletions

View File

@@ -182,6 +182,14 @@ spoofing attacks but it may be faster and use less resources. Setting this opti
to zero makes dnsmasq use a single port allocated to it by the to zero makes dnsmasq use a single port allocated to it by the
OS: this was the default behaviour in versions prior to 2.43. OS: this was the default behaviour in versions prior to 2.43.
.TP .TP
.B --port-limit=<#ports>
By default, when sending a query via random ports to multiple upstream servers or
retrying a query dnsmasq will use a single random port for all the tries/retries.
This option allows a larger number of ports to be used, which can increase robustness
in certain network configurations. Note that increasing this to more than
two or three can have security and resource implications and should only
be done with understanding of those.
.TP
.B --min-port=<port> .B --min-port=<port>
Do not use ports less than that given as source for outbound DNS Do not use ports less than that given as source for outbound DNS
queries. Dnsmasq picks random ports as source for outbound queries: queries. Dnsmasq picks random ports as source for outbound queries:

View File

@@ -1139,6 +1139,7 @@ extern struct daemon {
int log_fac; /* log facility */ int log_fac; /* log facility */
char *log_file; /* optional log file */ char *log_file; /* optional log file */
int max_logs; /* queue limit */ int max_logs; /* queue limit */
int randport_limit; /* Maximum number of source ports for query. */
int cachesize, ftabsize; int cachesize, ftabsize;
int port, query_port, min_port, max_port; int port, query_port, min_port, max_port;
unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl, dhcp_ttl, use_dhcp_ttl; unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl, dhcp_ttl, use_dhcp_ttl;

View File

@@ -2311,7 +2311,7 @@ int allocate_rfd(struct randfd_list **fdlp, struct server *serv)
{ {
static int finger = 0; static int finger = 0;
int i, j = 0; int i, j = 0;
struct randfd_list *rfl; struct randfd_list **up, *rfl, *found, **found_link;
struct randfd *rfd = NULL; struct randfd *rfd = NULL;
int fd = 0; int fd = 0;
@@ -2319,16 +2319,26 @@ int allocate_rfd(struct randfd_list **fdlp, struct server *serv)
if (serv->sfd) if (serv->sfd)
return serv->sfd->fd; return serv->sfd->fd;
/* existing suitable random port socket linked to this transaction? */ /* existing suitable random port socket linked to this transaction?
for (rfl = *fdlp; rfl; rfl = rfl->next) Find the last one in the list and count how many there are. */
for (found = NULL, found_link = NULL, i = 0, up = fdlp, rfl = *fdlp; rfl; up = &rfl->next, rfl = rfl->next)
if (server_isequal(serv, rfl->rfd->serv)) if (server_isequal(serv, rfl->rfd->serv))
return rfl->rfd->fd; {
i++;
found = rfl;
found_link = up;
}
/* No. need new link. */ /* We have the maximum number for this query already. Promote
if ((rfl = daemon->rfl_spare)) the last one on the list to the head, to circulate them,
daemon->rfl_spare = rfl->next; and return it. */
else if (!(rfl = whine_malloc(sizeof(struct randfd_list)))) if (found && i >= daemon->randport_limit)
return -1; {
*found_link = found->next;
found->next = *fdlp;
*fdlp = found;
return found->rfd->fd;
}
/* limit the number of sockets we have open to avoid starvation of /* limit the number of sockets we have open to avoid starvation of
(eg) TFTP. Once we have a reasonable number, randomness should be OK */ (eg) TFTP. Once we have a reasonable number, randomness should be OK */
@@ -2345,6 +2355,31 @@ int allocate_rfd(struct randfd_list **fdlp, struct server *serv)
break; break;
} }
/* We've hit the global limit on sockets before hitting the query limit,
use an exsiting socket as source in that case. */
if (!rfd && found)
{
*found_link = found->next;
found->next = *fdlp;
*fdlp = found;
return found->rfd->fd;
}
/* No good existing. Need new link. */
if ((rfl = daemon->rfl_spare))
daemon->rfl_spare = rfl->next;
else if (!(rfl = whine_malloc(sizeof(struct randfd_list))))
{
/* malloc failed, don't leak allocated sock */
if (rfd)
{
close(rfd->fd);
rfd->refcount = 0;
}
return -1;
}
/* No free ones or cannot get new socket, grab an existing one */ /* No free ones or cannot get new socket, grab an existing one */
if (!rfd) if (!rfd)
for (j = 0; j < daemon->numrrand; j++) for (j = 0; j < daemon->numrrand; j++)

View File

@@ -181,6 +181,7 @@ struct myoption {
#define LOPT_STRIP_MAC 372 #define LOPT_STRIP_MAC 372
#define LOPT_CONF_OPT 373 #define LOPT_CONF_OPT 373
#define LOPT_CONF_SCRIPT 374 #define LOPT_CONF_SCRIPT 374
#define LOPT_RANDPORT_LIM 375
#ifdef HAVE_GETOPT_LONG #ifdef HAVE_GETOPT_LONG
static const struct option opts[] = static const struct option opts[] =
@@ -366,6 +367,7 @@ static const struct myoption opts[] =
{ "log-debug", 0, 0, LOPT_LOG_DEBUG }, { "log-debug", 0, 0, LOPT_LOG_DEBUG },
{ "umbrella", 2, 0, LOPT_UMBRELLA }, { "umbrella", 2, 0, LOPT_UMBRELLA },
{ "quiet-tftp", 0, 0, LOPT_QUIET_TFTP }, { "quiet-tftp", 0, 0, LOPT_QUIET_TFTP },
{ "port-limit", 1, 0, LOPT_RANDPORT_LIM },
{ NULL, 0, 0, 0 } { NULL, 0, 0, 0 }
}; };
@@ -430,6 +432,7 @@ static struct {
{ 'P', ARG_ONE, "<integer>", gettext_noop("Maximum supported UDP packet size for EDNS.0 (defaults to %s)."), "*" }, { 'P', ARG_ONE, "<integer>", gettext_noop("Maximum supported UDP packet size for EDNS.0 (defaults to %s)."), "*" },
{ 'q', ARG_DUP, NULL, gettext_noop("Log DNS queries."), NULL }, { 'q', ARG_DUP, NULL, gettext_noop("Log DNS queries."), NULL },
{ 'Q', ARG_ONE, "<integer>", gettext_noop("Force the originating port for upstream DNS queries."), NULL }, { 'Q', ARG_ONE, "<integer>", gettext_noop("Force the originating port for upstream DNS queries."), NULL },
{ LOPT_RANDPORT_LIM, ARG_ONE, "#ports", gettext_noop("Set maximum number of random originating ports for a query."), NULL },
{ 'R', OPT_NO_RESOLV, NULL, gettext_noop("Do NOT read resolv.conf."), NULL }, { 'R', OPT_NO_RESOLV, NULL, gettext_noop("Do NOT read resolv.conf."), NULL },
{ 'r', ARG_DUP, "<path>", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE }, { 'r', ARG_DUP, "<path>", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE },
{ LOPT_SERVERS_FILE, ARG_ONE, "<path>", gettext_noop("Specify path to file with server= options"), NULL }, { LOPT_SERVERS_FILE, ARG_ONE, "<path>", gettext_noop("Specify path to file with server= options"), NULL },
@@ -3180,6 +3183,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
daemon->osport = 1; daemon->osport = 1;
break; break;
case LOPT_RANDPORT_LIM: /* --port-limit */
if (!atoi_check(arg, &daemon->randport_limit) || (daemon->randport_limit < 1))
ret_err(gen_err);
break;
case 'T': /* --local-ttl */ case 'T': /* --local-ttl */
case LOPT_NEGTTL: /* --neg-ttl */ case LOPT_NEGTTL: /* --neg-ttl */
case LOPT_MAXTTL: /* --max-ttl */ case LOPT_MAXTTL: /* --max-ttl */
@@ -5494,6 +5502,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->randport_limit = 1;
#ifndef NO_ID #ifndef NO_ID
add_txt("version.bind", "dnsmasq-" VERSION, 0 ); add_txt("version.bind", "dnsmasq-" VERSION, 0 );