Merge branch 'random-port'

This commit is contained in:
Simon Kelley
2021-03-17 20:42:21 +00:00
10 changed files with 348 additions and 276 deletions

View File

@@ -27,6 +27,28 @@ version 2.85
Teach --bogus-nxdomain and --ignore-address to take an IPv4 subnet. Teach --bogus-nxdomain and --ignore-address to take an IPv4 subnet.
Use random source ports where possible if source
addresses/interfaces in use.
CVE-2021-3448 applies. Thanks to Petr Menšík for spotting this.
It's possible to specify the source address or interface to be
used when contacting upstream nameservers: server=8.8.8.8@1.2.3.4
or server=8.8.8.8@1.2.3.4#66 or server=8.8.8.8@eth0, and all of
these have, until now, used a single socket, bound to a fixed
port. This was originally done to allow an error (non-existent
interface, or non-local address) to be detected at start-up. This
means that any upstream servers specified in such a way don't use
random source ports, and are more susceptible to cache-poisoning
attacks.
We now use random ports where possible, even when the
source is specified, so server=8.8.8.8@1.2.3.4 or
server=8.8.8.8@eth0 will use random source
ports. server=8.8.8.8@1.2.3.4#66 or any use of --query-port will
use the explicitly configured port, and should only be done with
understanding of the security implications.
Note that this change changes non-existing interface, or non-local
source address errors from fatal to run-time. The error will be
logged and communiction with the server not possible.
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

View File

@@ -431,7 +431,7 @@ Tells dnsmasq to never forward A or AAAA queries for plain names, without dots
or domain parts, to upstream nameservers. If the name is not known or domain parts, to upstream nameservers. If the name is not known
from /etc/hosts or DHCP then a "not found" answer is returned. from /etc/hosts or DHCP then a "not found" answer is returned.
.TP .TP
.B \-S, --local, --server=[/[<domain>]/[domain/]][<ipaddr>[#<port>]][@<source-ip>|<interface>[#<port>]] .B \-S, --local, --server=[/[<domain>]/[domain/]][<ipaddr>[#<port>]][@<interface>][@<source-ip>[#<port>]]
Specify IP address of upstream servers directly. Setting this flag does Specify IP address of upstream servers directly. Setting this flag does
not suppress reading of /etc/resolv.conf, use \fB--no-resolv\fP to do that. If one or more not suppress reading of /etc/resolv.conf, use \fB--no-resolv\fP to do that. If one or more
optional domains are given, that server is used only for those domains optional domains are given, that server is used only for those domains
@@ -492,7 +492,7 @@ source address specified but the port may be specified directly as
part of the source address. Forcing queries to an interface is not part of the source address. Forcing queries to an interface is not
implemented on all platforms supported by dnsmasq. implemented on all platforms supported by dnsmasq.
.TP .TP
.B --rev-server=<ip-address>/<prefix-len>[,<ipaddr>][#<port>][@<source-ip>|<interface>[#<port>]] .B --rev-server=<ip-address>/<prefix-len>[,<ipaddr>][#<port>][@<interface>][@<source-ip>[#<port>]]
This is functionally the same as This is functionally the same as
.B --server, .B --server,
but provides some syntactic sugar to make specifying address-to-name queries easier. For example but provides some syntactic sugar to make specifying address-to-name queries easier. For example

View File

@@ -1672,6 +1672,7 @@ static int set_dns_listeners(time_t now)
{ {
struct serverfd *serverfdp; struct serverfd *serverfdp;
struct listener *listener; struct listener *listener;
struct randfd_list *rfl;
int wait = 0, i; int wait = 0, i;
#ifdef HAVE_TFTP #ifdef HAVE_TFTP
@@ -1692,10 +1693,13 @@ static int set_dns_listeners(time_t now)
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
poll_listen(serverfdp->fd, POLLIN); poll_listen(serverfdp->fd, POLLIN);
if (daemon->port != 0 && !daemon->osport) for (i = 0; i < RANDOM_SOCKS; i++)
for (i = 0; i < RANDOM_SOCKS; i++) if (daemon->randomsocks[i].refcount != 0)
if (daemon->randomsocks[i].refcount != 0) poll_listen(daemon->randomsocks[i].fd, POLLIN);
poll_listen(daemon->randomsocks[i].fd, POLLIN);
/* Check overflow random sockets too. */
for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
poll_listen(rfl->rfd->fd, POLLIN);
for (listener = daemon->listeners; listener; listener = listener->next) for (listener = daemon->listeners; listener; listener = listener->next)
{ {
@@ -1733,18 +1737,23 @@ static void check_dns_listeners(time_t now)
{ {
struct serverfd *serverfdp; struct serverfd *serverfdp;
struct listener *listener; struct listener *listener;
struct randfd_list *rfl;
int i; int i;
int pipefd[2]; int pipefd[2];
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
if (poll_check(serverfdp->fd, POLLIN)) if (poll_check(serverfdp->fd, POLLIN))
reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now); reply_query(serverfdp->fd, now);
if (daemon->port != 0 && !daemon->osport) for (i = 0; i < RANDOM_SOCKS; i++)
for (i = 0; i < RANDOM_SOCKS; i++) if (daemon->randomsocks[i].refcount != 0 &&
if (daemon->randomsocks[i].refcount != 0 && poll_check(daemon->randomsocks[i].fd, POLLIN))
poll_check(daemon->randomsocks[i].fd, POLLIN)) reply_query(daemon->randomsocks[i].fd, now);
reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now);
/* Check overflow random sockets too. */
for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
if (poll_check(rfl->rfd->fd, POLLIN))
reply_query(rfl->rfd->fd, now);
/* Races. The child process can die before we read all of the data from the /* Races. The child process can die before we read all of the data from the
pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the

View File

@@ -549,13 +549,20 @@ struct serverfd {
}; };
struct randfd { struct randfd {
struct server *serv;
int fd; int fd;
unsigned short refcount, family; unsigned short refcount; /* refcount == 0xffff means overflow record. */
};
struct randfd_list {
struct randfd *rfd;
struct randfd_list *next;
}; };
struct server { struct server {
union mysockaddr addr, source_addr; union mysockaddr addr, source_addr;
char interface[IF_NAMESIZE+1]; char interface[IF_NAMESIZE+1];
unsigned int ifindex; /* corresponding to interface, above */
struct serverfd *sfd; struct serverfd *sfd;
char *domain; /* set if this server only handles a domain. */ char *domain; /* set if this server only handles a domain. */
int flags, tcpfd, edns_pktsz; int flags, tcpfd, edns_pktsz;
@@ -679,8 +686,7 @@ struct frec {
struct frec_src *next; struct frec_src *next;
} frec_src; } frec_src;
struct server *sentto; /* NULL means free */ struct server *sentto; /* NULL means free */
struct randfd *rfd4; struct randfd_list *rfds;
struct randfd *rfd6;
unsigned short new_id; unsigned short new_id;
int forwardall, flags; int forwardall, flags;
time_t time; time_t time;
@@ -1120,11 +1126,12 @@ extern struct daemon {
int forwardcount; int forwardcount;
struct server *srv_save; /* Used for resend on DoD */ struct server *srv_save; /* Used for resend on DoD */
size_t packet_len; /* " " */ size_t packet_len; /* " " */
struct randfd *rfd_save; /* " " */ int fd_save; /* " " */
pid_t tcp_pids[MAX_PROCS]; pid_t tcp_pids[MAX_PROCS];
int tcp_pipes[MAX_PROCS]; int tcp_pipes[MAX_PROCS];
int pipe_to_parent; int pipe_to_parent;
struct randfd randomsocks[RANDOM_SOCKS]; struct randfd randomsocks[RANDOM_SOCKS];
struct randfd_list *rfl_spare, *rfl_poll;
int v6pktinfo; int v6pktinfo;
struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */ struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */
int log_id, log_display_id; /* ids of transactions for logging */ int log_id, log_display_id; /* ids of transactions for logging */
@@ -1296,7 +1303,7 @@ void safe_strncpy(char *dest, const char *src, size_t size);
void safe_pipe(int *fd, int read_noblock); void safe_pipe(int *fd, int read_noblock);
void *whine_malloc(size_t size); void *whine_malloc(size_t size);
int sa_len(union mysockaddr *addr); int sa_len(union mysockaddr *addr);
int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2); int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2);
int hostname_isequal(const char *a, const char *b); int hostname_isequal(const char *a, const char *b);
int hostname_issubdomain(char *a, char *b); int hostname_issubdomain(char *a, char *b);
time_t dnsmasq_time(void); time_t dnsmasq_time(void);
@@ -1347,7 +1354,7 @@ char *parse_server(char *arg, union mysockaddr *addr,
int option_read_dynfile(char *file, int flags); int option_read_dynfile(char *file, int flags);
/* forward.c */ /* forward.c */
void reply_query(int fd, int family, time_t now); void reply_query(int fd, time_t now);
void receive_query(struct listener *listen, time_t now); void receive_query(struct listener *listen, time_t now);
unsigned char *tcp_request(int confd, time_t now, unsigned char *tcp_request(int confd, time_t now,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns); union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
@@ -1357,13 +1364,12 @@ int send_from(int fd, int nowild, char *packet, size_t len,
union mysockaddr *to, union all_addr *source, union mysockaddr *to, union all_addr *source,
unsigned int iface); unsigned int iface);
void resend_query(void); void resend_query(void);
struct randfd *allocate_rfd(int family); int allocate_rfd(struct randfd_list **fdlp, struct server *serv);
void free_rfd(struct randfd *rfd); void free_rfds(struct randfd_list **fdlp);
/* network.c */ /* network.c */
int indextoname(int fd, int index, char *name); int indextoname(int fd, int index, char *name);
int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp); int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp);
int random_sock(int family);
void pre_allocate_sfds(void); void pre_allocate_sfds(void);
int reload_servers(char *fname); int reload_servers(char *fname);
void mark_servers(int flag); void mark_servers(int flag);

View File

@@ -16,7 +16,7 @@
#include "dnsmasq.h" #include "dnsmasq.h"
static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash); static struct frec *lookup_frec(unsigned short id, int fd, void *hash);
static struct frec *lookup_frec_by_query(void *hash, unsigned int flags); static struct frec *lookup_frec_by_query(void *hash, unsigned int flags);
static unsigned short get_id(void); static unsigned short get_id(void);
@@ -344,25 +344,17 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign)
PUTSHORT(SAFE_PKTSZ, pheader); PUTSHORT(SAFE_PKTSZ, pheader);
if (forward->sentto->addr.sa.sa_family == AF_INET) if ((fd = allocate_rfd(&forward->rfds, forward->sentto)) != -1)
log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec");
else
log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec");
if (forward->sentto->sfd)
fd = forward->sentto->sfd->fd;
else
{ {
if (forward->sentto->addr.sa.sa_family == AF_INET6) if (forward->sentto->addr.sa.sa_family == AF_INET)
fd = forward->rfd6->fd; log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec");
else else
fd = forward->rfd4->fd; log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec");
}
while (retry_send(sendto(fd, (char *)header, plen, 0, while (retry_send(sendto(fd, (char *)header, plen, 0,
&forward->sentto->addr.sa, &forward->sentto->addr.sa,
sa_len(&forward->sentto->addr)))); sa_len(&forward->sentto->addr))));
}
return 1; return 1;
} }
@@ -508,48 +500,27 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
while (1) while (1)
{ {
int fd;
/* only send to servers dealing with our domain. /* only send to servers dealing with our domain.
domain may be NULL, in which case server->domain domain may be NULL, in which case server->domain
must be NULL also. */ must be NULL also. */
if (type == (start->flags & SERV_TYPE) && if (type == (start->flags & SERV_TYPE) &&
(type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) &&
!(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) &&
((fd = allocate_rfd(&forward->rfds, start)) != -1))
{ {
int fd;
/* find server socket to use, may need to get random one. */
if (start->sfd)
fd = start->sfd->fd;
else
{
if (start->addr.sa.sa_family == AF_INET6)
{
if (!forward->rfd6 &&
!(forward->rfd6 = allocate_rfd(AF_INET6)))
break;
daemon->rfd_save = forward->rfd6;
fd = forward->rfd6->fd;
}
else
{
if (!forward->rfd4 &&
!(forward->rfd4 = allocate_rfd(AF_INET)))
break;
daemon->rfd_save = forward->rfd4;
fd = forward->rfd4->fd;
}
#ifdef HAVE_CONNTRACK #ifdef HAVE_CONNTRACK
/* Copy connection mark of incoming query to outgoing connection. */ /* Copy connection mark of incoming query to outgoing connection. */
if (option_bool(OPT_CONNTRACK)) if (option_bool(OPT_CONNTRACK))
{ {
unsigned int mark; unsigned int mark;
if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark)) if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark))
setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
}
#endif
} }
#endif
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER)) if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER))
@@ -581,6 +552,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
/* Keep info in case we want to re-send this packet */ /* Keep info in case we want to re-send this packet */
daemon->srv_save = start; daemon->srv_save = start;
daemon->packet_len = plen; daemon->packet_len = plen;
daemon->fd_save = fd;
if (!gotname) if (!gotname)
strcpy(daemon->namebuff, "query"); strcpy(daemon->namebuff, "query");
@@ -812,7 +784,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
} }
/* sets new last_server */ /* sets new last_server */
void reply_query(int fd, int family, time_t now) void reply_query(int fd, time_t now)
{ {
/* packet from peer server, extract data for cache, and send to /* packet from peer server, extract data for cache, and send to
original requester */ original requester */
@@ -829,7 +801,7 @@ void reply_query(int fd, int family, time_t now)
daemon->srv_save = NULL; daemon->srv_save = NULL;
/* Determine the address of the server replying so that we can mark that as good */ /* Determine the address of the server replying so that we can mark that as good */
if ((serveraddr.sa.sa_family = family) == AF_INET6) if (serveraddr.sa.sa_family == AF_INET6)
serveraddr.in6.sin6_flowinfo = 0; serveraddr.in6.sin6_flowinfo = 0;
header = (struct dns_header *)daemon->packet; header = (struct dns_header *)daemon->packet;
@@ -852,7 +824,7 @@ void reply_query(int fd, int family, time_t now)
hash = hash_questions(header, n, daemon->namebuff); hash = hash_questions(header, n, daemon->namebuff);
if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash))) if (!(forward = lookup_frec(ntohs(header->id), fd, hash)))
return; return;
#ifdef HAVE_DUMPFILE #ifdef HAVE_DUMPFILE
@@ -906,28 +878,7 @@ void reply_query(int fd, int family, time_t now)
} }
fd = -1; if ((fd = allocate_rfd(&forward->rfds, start)) == -1)
if (start->sfd)
fd = start->sfd->fd;
else
{
if (start->addr.sa.sa_family == AF_INET6)
{
/* may have changed family */
if (forward->rfd6 || (forward->rfd6 = allocate_rfd(AF_INET6)))
fd = forward->rfd6->fd;
}
else
{
/* may have changed family */
if (forward->rfd4 || (forward->rfd4 = allocate_rfd(AF_INET)))
fd = forward->rfd4->fd;
}
}
/* Can't get socket. */
if (fd == -1)
return; return;
#ifdef HAVE_DUMPFILE #ifdef HAVE_DUMPFILE
@@ -1136,8 +1087,7 @@ void reply_query(int fd, int family, time_t now)
} }
new->sentto = server; new->sentto = server;
new->rfd4 = NULL; new->rfds = NULL;
new->rfd6 = NULL;
new->frec_src.next = NULL; new->frec_src.next = NULL;
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA); new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA);
new->forwardall = 0; new->forwardall = 0;
@@ -1176,24 +1126,7 @@ void reply_query(int fd, int family, time_t now)
/* Don't resend this. */ /* Don't resend this. */
daemon->srv_save = NULL; daemon->srv_save = NULL;
if (server->sfd) if ((fd = allocate_rfd(&new->rfds, server)) != -1)
fd = server->sfd->fd;
else
{
fd = -1;
if (server->addr.sa.sa_family == AF_INET6)
{
if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6)))
fd = new->rfd6->fd;
}
else
{
if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET)))
fd = new->rfd4->fd;
}
}
if (fd != -1)
{ {
#ifdef HAVE_CONNTRACK #ifdef HAVE_CONNTRACK
/* Copy connection mark of incoming query to outgoing connection. */ /* Copy connection mark of incoming query to outgoing connection. */
@@ -2228,9 +2161,8 @@ static struct frec *allocate_frec(time_t now)
f->next = daemon->frec_list; f->next = daemon->frec_list;
f->time = now; f->time = now;
f->sentto = NULL; f->sentto = NULL;
f->rfd4 = NULL; f->rfds = NULL;
f->flags = 0; f->flags = 0;
f->rfd6 = NULL;
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
f->dependent = NULL; f->dependent = NULL;
f->blocking_query = NULL; f->blocking_query = NULL;
@@ -2242,46 +2174,192 @@ static struct frec *allocate_frec(time_t now)
return f; return f;
} }
struct randfd *allocate_rfd(int family) /* return a UDP socket bound to a random port, have to cope with straying into
occupied port nos and reserved ones. */
static int random_sock(struct server *s)
{
int fd;
if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1)
{
if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0))
return fd;
if (s->interface[0] == 0)
(void)prettyprint_addr(&s->source_addr, daemon->namebuff);
else
strcpy(daemon->namebuff, s->interface);
my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"),
daemon->namebuff, strerror(errno));
close(fd);
}
return -1;
}
/* compare source addresses and interface, serv2 can be null. */
static int server_isequal(const struct server *serv1,
const struct server *serv2)
{
return (serv2 &&
serv2->ifindex == serv1->ifindex &&
sockaddr_isequal(&serv2->source_addr, &serv1->source_addr) &&
strncmp(serv2->interface, serv1->interface, IF_NAMESIZE) == 0);
}
/* fdlp points to chain of randomfds already in use by transaction.
If there's already a suitable one, return it, else allocate a
new one and add it to the list.
Not leaking any resources in the face of allocation failures
is rather convoluted here.
Note that rfd->serv may be NULL, when a server goes away.
*/
int allocate_rfd(struct randfd_list **fdlp, struct server *serv)
{ {
static int finger = 0; static int finger = 0;
int i; int i, j = 0;
struct randfd_list *rfl;
struct randfd *rfd = NULL;
int fd = 0;
/* If server has a pre-allocated fd, use that. */
if (serv->sfd)
return serv->sfd->fd;
/* existing suitable random port socket linked to this transaction? */
for (rfl = *fdlp; rfl; rfl = rfl->next)
if (server_isequal(serv, rfl->rfd->serv))
return rfl->rfd->fd;
/* No. need new link. */
if ((rfl = daemon->rfl_spare))
daemon->rfl_spare = rfl->next;
else if (!(rfl = whine_malloc(sizeof(struct randfd_list))))
return -1;
/* 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 */
for (i = 0; i < RANDOM_SOCKS; i++) for (i = 0; i < RANDOM_SOCKS; i++)
if (daemon->randomsocks[i].refcount == 0) if (daemon->randomsocks[i].refcount == 0)
{ {
if ((daemon->randomsocks[i].fd = random_sock(family)) == -1) if ((fd = random_sock(serv)) != -1)
break; {
rfd = &daemon->randomsocks[i];
daemon->randomsocks[i].refcount = 1; rfd->serv = serv;
daemon->randomsocks[i].family = family; rfd->fd = fd;
return &daemon->randomsocks[i]; rfd->refcount = 1;
}
break;
} }
/* No free ones or cannot get new socket, grab an existing one */ /* No free ones or cannot get new socket, grab an existing one */
for (i = 0; i < RANDOM_SOCKS; i++) if (!rfd)
for (j = 0; j < RANDOM_SOCKS; j++)
{
i = (j + finger) % RANDOM_SOCKS;
if (daemon->randomsocks[i].refcount != 0 &&
server_isequal(serv, daemon->randomsocks[i].serv) &&
daemon->randomsocks[i].refcount != 0xfffe)
{
finger = i + 1;
rfd = &daemon->randomsocks[i];
rfd->refcount++;
break;
}
}
if (j == RANDOM_SOCKS)
{ {
int j = (i+finger) % RANDOM_SOCKS; struct randfd_list *rfl_poll;
if (daemon->randomsocks[j].refcount != 0 &&
daemon->randomsocks[j].family == family && /* there are no free slots, and non with the same parameters we can piggy-back on.
daemon->randomsocks[j].refcount != 0xffff) We're going to have to allocate a new temporary record, distinguished by
refcount == 0xffff. This will exist in the frec randfd list, never be shared,
and be freed when no longer in use. It will also be held on
the daemon->rfl_poll list so the poll system can find it. */
if ((rfl_poll = daemon->rfl_spare))
daemon->rfl_spare = rfl_poll->next;
else
rfl_poll = whine_malloc(sizeof(struct randfd_list));
if (!rfl_poll ||
!(rfd = whine_malloc(sizeof(struct randfd))) ||
(fd = random_sock(serv)) == -1)
{ {
finger = j;
daemon->randomsocks[j].refcount++; /* Don't leak anything we may already have */
return &daemon->randomsocks[j]; rfl->next = daemon->rfl_spare;
daemon->rfl_spare = rfl;
if (rfl_poll)
{
rfl_poll->next = daemon->rfl_spare;
daemon->rfl_spare = rfl_poll;
}
if (rfd)
free(rfd);
return -1; /* doom */
} }
/* Note rfd->serv not set here, since it's not reused */
rfd->fd = fd;
rfd->refcount = 0xffff; /* marker for temp record */
rfl_poll->rfd = rfd;
rfl_poll->next = daemon->rfl_poll;
daemon->rfl_poll = rfl_poll;
} }
return NULL; /* doom */ rfl->rfd = rfd;
rfl->next = *fdlp;
*fdlp = rfl;
return rfl->rfd->fd;
} }
void free_rfd(struct randfd *rfd) void free_rfds(struct randfd_list **fdlp)
{ {
if (rfd && --(rfd->refcount) == 0) struct randfd_list *tmp, *rfl, *poll, *next, **up;
close(rfd->fd);
for (rfl = *fdlp; rfl; rfl = tmp)
{
if (rfl->rfd->refcount == 0xffff || --(rfl->rfd->refcount) == 0)
close(rfl->rfd->fd);
/* temporary overflow record */
if (rfl->rfd->refcount == 0xffff)
{
free(rfl->rfd);
/* go through the link of all these by steam to delete.
This list is expected to be almost always empty. */
for (poll = daemon->rfl_poll, up = &daemon->rfl_poll; poll; poll = next)
{
next = poll->next;
if (poll->rfd == rfl->rfd)
{
*up = poll->next;
poll->next = daemon->rfl_spare;
daemon->rfl_spare = poll;
}
else
up = &poll->next;
}
}
tmp = rfl->next;
rfl->next = daemon->rfl_spare;
daemon->rfl_spare = rfl;
}
*fdlp = NULL;
} }
static void free_frec(struct frec *f) static void free_frec(struct frec *f)
@@ -2297,12 +2375,9 @@ static void free_frec(struct frec *f)
} }
f->frec_src.next = NULL; f->frec_src.next = NULL;
free_rfd(f->rfd4); free_rfds(&f->rfds);
f->rfd4 = NULL;
f->sentto = NULL; f->sentto = NULL;
f->flags = 0; f->flags = 0;
free_rfd(f->rfd6);
f->rfd6 = NULL;
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (f->stash) if (f->stash)
@@ -2409,24 +2484,37 @@ struct frec *get_new_frec(time_t now, int *wait, struct frec *force)
return f; /* OK if malloc fails and this is NULL */ return f; /* OK if malloc fails and this is NULL */
} }
static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash) static struct frec *lookup_frec(unsigned short id, int fd, void *hash)
{ {
struct frec *f; struct frec *f;
struct server *s;
int type;
struct randfd_list *fdl;
for(f = daemon->frec_list; f; f = f->next) for(f = daemon->frec_list; f; f = f->next)
if (f->sentto && f->new_id == id && if (f->sentto && f->new_id == id &&
(memcmp(hash, f->hash, HASH_SIZE) == 0)) (memcmp(hash, f->hash, HASH_SIZE) == 0))
{ {
/* sent from random port */ /* sent from random port */
if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd) for (fdl = f->rfds; fdl; fdl = fdl->next)
if (fdl->rfd->fd == fd)
return f; return f;
if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd) /* Sent to upstream from socket associated with a server.
return f; Note we have to iterate over all the possible servers, since they may
have different bound sockets. */
type = f->sentto->flags & SERV_TYPE;
s = f->sentto;
do {
if ((type == (s->flags & SERV_TYPE)) &&
(type != SERV_HAS_DOMAIN ||
(s->domain && hostname_isequal(f->sentto->domain, s->domain))) &&
!(s->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) &&
s->sfd && s->sfd->fd == fd)
return f;
/* sent to upstream from bound socket. */ s = s->next ? s->next : daemon->servers;
if (f->sentto->sfd && f->sentto->sfd->fd == fd) } while (s != f->sentto);
return f;
} }
return NULL; return NULL;
@@ -2458,31 +2546,27 @@ static struct frec *lookup_frec_by_query(void *hash, unsigned int flags)
void resend_query() void resend_query()
{ {
if (daemon->srv_save) if (daemon->srv_save)
{ while(retry_send(sendto(daemon->fd_save, daemon->packet, daemon->packet_len, 0,
int fd; &daemon->srv_save->addr.sa,
sa_len(&daemon->srv_save->addr))));
if (daemon->srv_save->sfd)
fd = daemon->srv_save->sfd->fd;
else if (daemon->rfd_save && daemon->rfd_save->refcount != 0)
fd = daemon->rfd_save->fd;
else
return;
while(retry_send(sendto(fd, daemon->packet, daemon->packet_len, 0,
&daemon->srv_save->addr.sa,
sa_len(&daemon->srv_save->addr))));
}
} }
/* A server record is going away, remove references to it */ /* A server record is going away, remove references to it */
void server_gone(struct server *server) void server_gone(struct server *server)
{ {
struct frec *f; struct frec *f;
int i;
for (f = daemon->frec_list; f; f = f->next) for (f = daemon->frec_list; f; f = f->next)
if (f->sentto && f->sentto == server) if (f->sentto && f->sentto == server)
free_frec(f); free_frec(f);
/* If any random socket refers to this server, NULL the reference.
No more references to the socket will be created in the future. */
for (i = 0; i < RANDOM_SOCKS; i++)
if (daemon->randomsocks[i].refcount != 0 && daemon->randomsocks[i].serv == server)
daemon->randomsocks[i].serv = NULL;
if (daemon->last_server == server) if (daemon->last_server == server)
daemon->last_server = NULL; daemon->last_server = NULL;

View File

@@ -22,6 +22,7 @@ static ssize_t loop_make_probe(u32 uid);
void loop_send_probes() void loop_send_probes()
{ {
struct server *serv; struct server *serv;
struct randfd_list *rfds = NULL;
if (!option_bool(OPT_LOOP_DETECT)) if (!option_bool(OPT_LOOP_DETECT))
return; return;
@@ -34,22 +35,15 @@ void loop_send_probes()
{ {
ssize_t len = loop_make_probe(serv->uid); ssize_t len = loop_make_probe(serv->uid);
int fd; int fd;
struct randfd *rfd = NULL;
if (serv->sfd) if ((fd = allocate_rfd(&rfds, serv)) == -1)
fd = serv->sfd->fd; continue;
else
{
if (!(rfd = allocate_rfd(serv->addr.sa.sa_family)))
continue;
fd = rfd->fd;
}
while (retry_send(sendto(fd, daemon->packet, len, 0, while (retry_send(sendto(fd, daemon->packet, len, 0,
&serv->addr.sa, sa_len(&serv->addr)))); &serv->addr.sa, sa_len(&serv->addr))));
free_rfd(rfd);
} }
free_rfds(&rfds);
} }
static ssize_t loop_make_probe(u32 uid) static ssize_t loop_make_probe(u32 uid)

View File

@@ -696,6 +696,7 @@ int enumerate_interfaces(int reset)
#ifdef HAVE_AUTH #ifdef HAVE_AUTH
struct auth_zone *zone; struct auth_zone *zone;
#endif #endif
struct server *serv;
/* Do this max once per select cycle - also inhibits netlink socket use /* Do this max once per select cycle - also inhibits netlink socket use
in TCP child processes. */ in TCP child processes. */
@@ -714,6 +715,20 @@ int enumerate_interfaces(int reset)
if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
return 0; return 0;
/* iface indexes can change when interfaces are created/destroyed.
We use them in the main forwarding control path, when the path
to a server is specified by an interface, so cache them.
Update the cache here. */
for (serv = daemon->servers; serv; serv = serv->next)
if (strlen(serv->interface) != 0)
{
struct ifreq ifr;
safe_strncpy(ifr.ifr_name, serv->interface, IF_NAMESIZE);
if (ioctl(param.fd, SIOCGIFINDEX, &ifr) != -1)
serv->ifindex = ifr.ifr_ifindex;
}
again: again:
/* Mark interfaces for garbage collection */ /* Mark interfaces for garbage collection */
for (iface = daemon->interfaces; iface; iface = iface->next) for (iface = daemon->interfaces; iface; iface = iface->next)
@@ -1287,59 +1302,6 @@ void join_multicast(int dienow)
} }
#endif #endif
/* return a UDP socket bound to a random port, have to cope with straying into
occupied port nos and reserved ones. */
int random_sock(int family)
{
int fd;
if ((fd = socket(family, SOCK_DGRAM, 0)) != -1)
{
union mysockaddr addr;
unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1;
int tries = ports_avail < 30 ? 3 * ports_avail : 100;
memset(&addr, 0, sizeof(addr));
addr.sa.sa_family = family;
/* don't loop forever if all ports in use. */
if (fix_fd(fd))
while(tries--)
{
unsigned short port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail)));
if (family == AF_INET)
{
addr.in.sin_addr.s_addr = INADDR_ANY;
addr.in.sin_port = port;
#ifdef HAVE_SOCKADDR_SA_LEN
addr.in.sin_len = sizeof(struct sockaddr_in);
#endif
}
else
{
addr.in6.sin6_addr = in6addr_any;
addr.in6.sin6_port = port;
#ifdef HAVE_SOCKADDR_SA_LEN
addr.in6.sin6_len = sizeof(struct sockaddr_in6);
#endif
}
if (bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == 0)
return fd;
if (errno != EADDRINUSE && errno != EACCES)
break;
}
close(fd);
}
return -1;
}
int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp) int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp)
{ {
union mysockaddr addr_copy = *addr; union mysockaddr addr_copy = *addr;
@@ -1355,10 +1317,9 @@ 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)
/* Bind a random port within the range given by min-port and max-port */
if (port == 0)
{ {
/* Bind a random port within the range given by min-port and max-port */
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() % ((unsigned short)ports_avail)));
} }
@@ -1413,38 +1374,33 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind
return 1; return 1;
} }
static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsigned int ifindex)
{ {
struct serverfd *sfd; struct serverfd *sfd;
unsigned int ifindex = 0;
int errsave; int errsave;
int opt = 1; int opt = 1;
/* when using random ports, servers which would otherwise use /* when using random ports, servers which would otherwise use
the INADDR_ANY/port0 socket have sfd set to NULL */ the INADDR_ANY/port0 socket have sfd set to NULL, this is
if (!daemon->osport && intname[0] == 0) anything without an explictly set source port. */
if (!daemon->osport)
{ {
errno = 0; errno = 0;
if (addr->sa.sa_family == AF_INET && if (addr->sa.sa_family == AF_INET &&
addr->in.sin_addr.s_addr == INADDR_ANY &&
addr->in.sin_port == htons(0)) addr->in.sin_port == htons(0))
return NULL; return NULL;
if (addr->sa.sa_family == AF_INET6 && if (addr->sa.sa_family == AF_INET6 &&
memcmp(&addr->in6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 &&
addr->in6.sin6_port == htons(0)) addr->in6.sin6_port == htons(0))
return NULL; return NULL;
} }
if (intname && strlen(intname) != 0)
ifindex = if_nametoindex(intname); /* index == 0 when not binding to an interface */
/* may have a suitable one already */ /* may have a suitable one already */
for (sfd = daemon->sfds; sfd; sfd = sfd->next ) for (sfd = daemon->sfds; sfd; sfd = sfd->next )
if (sockaddr_isequal(&sfd->source_addr, addr) && if (ifindex == sfd->ifindex &&
strcmp(intname, sfd->interface) == 0 && sockaddr_isequal(&sfd->source_addr, addr) &&
ifindex == sfd->ifindex) strcmp(intname, sfd->interface) == 0)
return sfd; return sfd;
/* need to make a new one. */ /* need to make a new one. */
@@ -1495,7 +1451,7 @@ void pre_allocate_sfds(void)
#ifdef HAVE_SOCKADDR_SA_LEN #ifdef HAVE_SOCKADDR_SA_LEN
addr.in.sin_len = sizeof(struct sockaddr_in); addr.in.sin_len = sizeof(struct sockaddr_in);
#endif #endif
if ((sfd = allocate_sfd(&addr, ""))) if ((sfd = allocate_sfd(&addr, "", 0)))
sfd->preallocated = 1; sfd->preallocated = 1;
memset(&addr, 0, sizeof(addr)); memset(&addr, 0, sizeof(addr));
@@ -1505,13 +1461,13 @@ void pre_allocate_sfds(void)
#ifdef HAVE_SOCKADDR_SA_LEN #ifdef HAVE_SOCKADDR_SA_LEN
addr.in6.sin6_len = sizeof(struct sockaddr_in6); addr.in6.sin6_len = sizeof(struct sockaddr_in6);
#endif #endif
if ((sfd = allocate_sfd(&addr, ""))) if ((sfd = allocate_sfd(&addr, "", 0)))
sfd->preallocated = 1; sfd->preallocated = 1;
} }
for (srv = daemon->servers; srv; srv = srv->next) for (srv = daemon->servers; srv; srv = srv->next)
if (!(srv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)) && if (!(srv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)) &&
!allocate_sfd(&srv->source_addr, srv->interface) && !allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) &&
errno != 0 && errno != 0 &&
option_bool(OPT_NOWILD)) option_bool(OPT_NOWILD))
{ {
@@ -1720,7 +1676,7 @@ void check_servers(void)
/* Do we need a socket set? */ /* Do we need a socket set? */
if (!serv->sfd && if (!serv->sfd &&
!(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface)) && !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) &&
errno != 0) errno != 0)
{ {
my_syslog(LOG_WARNING, my_syslog(LOG_WARNING,

View File

@@ -819,7 +819,8 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
if (interface_opt) if (interface_opt)
{ {
#if defined(SO_BINDTODEVICE) #if defined(SO_BINDTODEVICE)
safe_strncpy(interface, interface_opt, IF_NAMESIZE); safe_strncpy(interface, source, IF_NAMESIZE);
source = interface_opt;
#else #else
return _("interface binding not supported"); return _("interface binding not supported");
#endif #endif

View File

@@ -316,7 +316,7 @@ void *whine_malloc(size_t size)
return ret; return ret;
} }
int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2) int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2)
{ {
if (s1->sa.sa_family == s2->sa.sa_family) if (s1->sa.sa_family == s2->sa.sa_family)
{ {