/* dnsmasq is Copyright (c) 2000-2022 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 dated June, 1991, or
(at your option) version 3 dated 29 June, 2007.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dnsmasq.h"
#ifdef HAVE_LINUX_NETWORK
int indextoname(int fd, int index, char *name)
{
struct ifreq ifr;
if (index == 0)
return 0;
ifr.ifr_ifindex = index;
if (ioctl(fd, SIOCGIFNAME, &ifr) == -1)
return 0;
safe_strncpy(name, ifr.ifr_name, IF_NAMESIZE);
return 1;
}
#elif defined(HAVE_SOLARIS_NETWORK)
#include <zone.h>
#include <alloca.h>
#ifndef LIFC_UNDER_IPMP
# define LIFC_UNDER_IPMP 0
#endif
int indextoname(int fd, int index, char *name)
{
int64_t lifc_flags;
struct lifnum lifn;
int numifs, bufsize, i;
struct lifconf lifc;
struct lifreq *lifrp;
if (index == 0)
return 0;
if (getzoneid() == GLOBAL_ZONEID)
{
if (!if_indextoname(index, name))
return 0;
return 1;
}
lifc_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES | LIFC_UNDER_IPMP;
lifn.lifn_family = AF_UNSPEC;
lifn.lifn_flags = lifc_flags;
if (ioctl(fd, SIOCGLIFNUM, &lifn) < 0)
return 0;
numifs = lifn.lifn_count;
bufsize = numifs * sizeof(struct lifreq);
lifc.lifc_family = AF_UNSPEC;
lifc.lifc_flags = lifc_flags;
lifc.lifc_len = bufsize;
lifc.lifc_buf = alloca(bufsize);
if (ioctl(fd, SIOCGLIFCONF, &lifc) < 0)
return 0;
lifrp = lifc.lifc_req;
for (i = lifc.lifc_len / sizeof(struct lifreq); i; i--, lifrp++)
{
struct lifreq lifr;
safe_strncpy(lifr.lifr_name, lifrp->lifr_name, IF_NAMESIZE);
if (ioctl(fd, SIOCGLIFINDEX, &lifr) < 0)
return 0;
if (lifr.lifr_index == index) {
safe_strncpy(name, lifr.lifr_name, IF_NAMESIZE);
return 1;
}
}
return 0;
}
#else
int indextoname(int fd, int index, char *name)
{
(void)fd;
if (index == 0 || !if_indextoname(index, name))
return 0;
return 1;
}
#endif
int iface_check(int family, union all_addr *addr, char *name, int *auth)
{
struct iname *tmp;
int ret = 1, match_addr = 0;
/* Note: have to check all and not bail out early, so that we set the "used" flags.
May be called with family == AF_LOCAL to check interface by name only. */
if (daemon->if_names || daemon->if_addrs)
{
ret = 0;
for (tmp = daemon->if_names; tmp; tmp = tmp->next)
if (tmp->name && wildcard_match(tmp->name, name))
ret = tmp->used = 1;
if (addr)
for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
if (tmp->addr.sa.sa_family == family)
{
if (family == AF_INET &&
tmp->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
ret = match_addr = tmp->used = 1;
else if (family == AF_INET6 &&
IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr,
&addr->addr6))
ret = match_addr = tmp->used = 1;
}
}
if (!match_addr)
for (tmp = daemon->if_except; tmp; tmp = tmp->next)
if (tmp->name && wildcard_match(tmp->name, name))
ret = 0;
if (auth)
{
*auth = 0;
for (tmp = daemon->authinterface; tmp; tmp = tmp->next)
if (tmp->name)
{
if (strcmp(tmp->name, name) == 0 &&
(tmp->addr.sa.sa_family == 0 || tmp->addr.sa.sa_family == family))
break;
}
else if (addr && tmp->addr.sa.sa_family == AF_INET && family == AF_INET &&
tmp->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
break;
else if (addr && tmp->addr.sa.sa_family == AF_INET6 && family == AF_INET6 &&
IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, &addr->addr6))
break;
if (tmp)
{
*auth = 1;
ret = 1;
}
}
return ret;
}
/* Fix for problem that the kernel sometimes reports the loopback interface as the
arrival interface when a packet originates locally, even when sent to address of
an interface other than the loopback. Accept packet if it arrived via a loopback
interface, even when we're not accepting packets that way, as long as the destination
address is one we're believing. Interface list must be up-to-date before calling. */
int loopback_exception(int fd, int family, union all_addr *addr, char *name)
{
struct ifreq ifr;
struct irec *iface;
safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE);
if (ioctl(fd, SIOCGIFFLAGS, &ifr) != -1 &&
ifr.ifr_flags & IFF_LOOPBACK)
{
for (iface = daemon->interfaces; iface; iface = iface->next)
if (iface->addr.sa.sa_family == family)
{
if (family == AF_INET)
{
if (iface->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
return 1;
}
else if (IN6_ARE_ADDR_EQUAL(&iface->addr.in6.sin6_addr, &addr->addr6))
return 1;
}
}
return 0;
}
/* If we're configured with something like --interface=eth0:0 then we'll listen correctly
on the relevant address, but the name of the arrival interface, derived from the
index won't match the config. Check that we found an interface address for the arrival
interface: daemon->interfaces must be up-to-date. */
int label_exception(int index, int family, union all_addr *addr)
{
struct irec *iface;
/* labels only supported on IPv4 addresses. */
if (family != AF_INET)
return 0;
for (iface = daemon->interfaces; iface; iface = iface->next)
if (iface->index == index && iface->addr.sa.sa_family == AF_INET &&
iface->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
return 1;
return 0;
}
struct iface_param {
struct addrlist *spare;
int fd;
};
static int iface_allowed(struct iface_param *param, int if_index, char *label,
union mysockaddr *addr, struct in_addr netmask, int prefixlen, int iface_flags)
{
struct irec *iface;
struct cond_domain *cond;
int loopback;
struct ifreq ifr;
int tftp_ok = !!option_bool(OPT_TFTP);
int dhcp_ok = 1;
int auth_dns = 0;
int is_label = 0;
#if defined(HAVE_DHCP) || defined(HAVE_TFTP)
struct iname *tmp;
#endif
(void)prefixlen;
if (!indextoname(param->fd, if_index, ifr.ifr_name) ||
ioctl(param->fd, SIOCGIFFLAGS, &ifr) == -1)
return 0;
loopback = ifr.ifr_flags & IFF_LOOPBACK;
if (loopback)
dhcp_ok = 0;
if (!label)
label = ifr.ifr_name;
else
is_label = strcmp(label, ifr.ifr_name);
/* maintain a list of all addresses on all interfaces for --local-service option */
if (option_bool(OPT_LOCAL_SERVICE))
{
struct addrlist *al;
if (param->spare)
{
al = param->spare;
param->spare = al->next;
}
else
al = whine_malloc(sizeof(struct addrlist));
if (al)
{
al->next = daemon->interface_addrs;
daemon->interface_addrs = al;
al->prefixlen = prefixlen;
if (addr->sa.sa_family == AF_INET)
{
al->addr.addr4 = addr->in.sin_addr;
al->flags = 0;
}
else
{
al->addr.addr6 = addr->in6.sin6_addr;
al->flags = ADDRLIST_IPV6;
}
}
}
if (addr->sa.sa_family != AF_INET6 || !IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr))
{
struct interface_name *int_name;
struct addrlist *al;
#ifdef HAVE_AUTH
struct auth_zone *zone;
struct auth_name_list *name;
/* Find subnets in auth_zones */
for (zone = daemon->auth_zones; zone; zone = zone->next)
for (name = zone->interface_names; name; name = name->next)
if (wildcard_match(name->name, label))
{
if (addr->sa.sa_family == AF_INET && (name->flags & AUTH4))
{
if (param->spare)
{
al = param->spare;
param->spare = al->next;
}
else
al = whine_malloc(sizeof(struct addrlist));
if (al)
{
al->next = zone->subnet;
zone->subnet = al;
al->prefixlen = prefixlen;
al->addr.addr4 = addr->in.sin_addr;
al->flags = 0;
}
}
if (addr->sa.sa_family == AF_INET6 && (name->flags & AUTH6))
{
if (param->spare)
{
al = param->spare;
param->spare = al->next;
}
else
al = whine_malloc(sizeof(struct addrlist));
if (al)
{
al->next = zone->subnet;
zone->subnet = al;
al->prefixlen = prefixlen;
al->addr.addr6 = addr->in6.sin6_addr;
al->flags = ADDRLIST_IPV6;
}
}
}
#endif
/* Update addresses from interface_names. These are a set independent
of the set we're listening on. */
for (int_name = daemon->int_names; int_name; int_name = int_name->next)
if (strncmp(label, int_name->intr, IF_NAMESIZE) == 0)
{
struct addrlist *lp;
al = NULL;
if (addr->sa.sa_family == AF_INET && (int_name->flags & (IN4 | INP4)))
{
struct in_addr newaddr = addr->in.sin_addr;
if (int_name->flags & INP4)
{
if (netmask.s_addr == 0xffffffff)
continue;
newaddr.s_addr = (addr->in.sin_addr.s_addr & netmask.s_addr) |
(int_name->proto4.s_addr & ~netmask.s_addr);
}
/* check for duplicates. */
for (lp = int_name->addr; lp; lp = lp->next)
if (lp->flags == 0 && lp->addr.addr4.s_addr == newaddr.s_addr)
break;
if (!lp)
{
if (param->spare)
{
al = param->spare;
param->spare = al->next;
}
else
al = whine_malloc(sizeof(struct addrlist));
if (al)
{
al->flags = 0;
al->addr.addr4 = newaddr;
}
}
}
if (addr->sa.sa_family == AF_INET6 && (int_name->flags & (IN6 | INP6)))
{
struct in6_addr newaddr = addr->in6.sin6_addr;
if (int_name->flags & INP6)
{
int i;
/* No sense in doing /128. */
if (prefixlen == 128)
continue;
for (i = 0; i < 16; i++)
{
int bits = ((i+1)*8) - prefixlen;
if (bits >= 8)
newaddr.s6_addr[i] = int_name->proto6.s6_addr[i];
else if (bits >= 0)
{
unsigned char mask = 0xff << bits;
newaddr.s6_addr[i] =
(addr->in6.sin6_addr.s6_addr[i] & mask) |
(int_name->proto6.s6_addr[i] & ~mask);
}
}
}
/* check for duplicates. */
for (lp = int_name->addr; lp; lp = lp->next)
if ((lp->flags & ADDRLIST_IPV6) &&
IN6_ARE_ADDR_EQUAL(&lp->addr.addr6, &newaddr))
break;
if (!lp)
{
if (param->spare)
{
al = param->spare;
param->spare = al->next;
}
else
al = whine_malloc(sizeof(struct addrlist));
if (al)
{
al->flags = ADDRLIST_IPV6;
al->addr.addr6 = newaddr;
/* Privacy addresses and addresses still undergoing DAD and deprecated addresses
don't appear in forward queries, but will in reverse ones. */
if (!(iface_flags & IFACE_PERMANENT) || (iface_flags & (IFACE_DEPRECATED | IFACE_TENTATIVE)))
al->flags |= ADDRLIST_REVONLY;
}
}
}
if (al)
{
al->next = int_name->addr;
int_name->addr = al;
}
}
}
/* Update addresses for domain=<domain>,<interface> */
for (cond = daemon->cond_domain; cond; cond = cond->next)
if (cond->interface && strncmp(label, cond->interface, IF_NAMESIZE) == 0)
{
struct addrlist *al;
if (param->spare)
{
al = param->spare;
param->spare = al->next;
}
else
al = whine_malloc(sizeof(struct addrlist));
if (addr->sa.sa_family == AF_INET)
{
al->addr.addr4 = addr->in.sin_addr;
al->flags = 0;
}
else
{
al->addr.addr6 = addr->in6.sin6_addr;
al->flags = ADDRLIST_IPV6;
}
al->prefixlen = prefixlen;
al->next = cond->al;
cond->al = al;
}
/* check whether the interface IP has been added already
we call this routine multiple times. */
for (iface = daemon->interfaces; iface; iface = iface->next)
if (sockaddr_isequal(&iface->addr, addr) && iface->index == if_index)
{
iface->dad = !!(iface_flags & IFACE_TENTATIVE);
iface->found = 1; /* for garbage collection */
iface->netmask = netmask;
return 1;
}
/* If we are restricting the set of interfaces to use, make
sure that loopback interfaces are in that set. */
if (daemon->if_names && loopback)
{
struct iname *lo;
for (lo = daemon->if_names; lo; lo = lo->next)
if (lo->name && strcmp(lo->name, ifr.ifr_name) == 0)
break;
if (!lo && (lo = whine_malloc(sizeof(struct iname))))
{
if ((lo->name = whine_malloc(strlen(ifr.ifr_name)+1)))
{
strcpy(lo->name, ifr.ifr_name);
lo->used = 1;
lo->next = daemon->if_names;
daemon->if_names = lo;
}
else
free(lo);
}
}
if (addr->sa.sa_family == AF_INET &&
!iface_check(AF_INET, (union all_addr *)&addr->in.sin_addr, label, &auth_dns))
return 1;
if (addr->sa.sa_family == AF_INET6 &&
!iface_check(AF_INET6, (union all_addr *)&addr->in6.sin6_addr, label, &auth_dns))
return 1;
#ifdef HAVE_DHCP
/* No DHCP where we're doing auth DNS. */
if (auth_dns)
{
tftp_ok = 0;
dhcp_ok = 0;
}
else
for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
{
tftp_ok = 0;
dhcp_ok = 0;
}
#endif
#ifdef HAVE_TFTP
if (daemon->tftp_interfaces)
{
/* dedicated tftp interface list */
tftp_ok = 0;
for (tmp = daemon->tftp_interfaces; tmp; tmp = tmp->next)
if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
tftp_ok = 1;
}
#endif
/* add to list */
if ((iface = whine_malloc(sizeof(struct irec))))
{
int mtu = 0;
if (ioctl(param->fd, SIOCGIFMTU, &ifr) != -1)
mtu = ifr.ifr_mtu;
iface->addr = *addr;
iface->netmask = netmask;
iface->tftp_ok = tftp_ok;
iface->dhcp_ok = dhcp_ok;
iface->dns_auth = auth_dns;
iface->mtu = mtu;
iface->dad = !!(iface_flags & IFACE_TENTATIVE);
iface->found = 1;
iface->done = iface->multicast_done = iface->warned = 0;
iface->index = if_index;
iface->label = is_label;
if ((iface->name = whine_malloc(strlen(ifr.ifr_name)+1)))
{
strcpy(iface->name, ifr.ifr_name);
iface->next = daemon->interfaces;
daemon->interfaces = iface;
return 1;
}
free(iface);
}
errno = ENOMEM;
return 0;
}
static int iface_allowed_v6(struct in6_addr *local, int prefix,
int scope, int if_index, int flags,
int preferred, int valid, void *vparam)
{
union mysockaddr addr;
struct in_addr netmask; /* dummy */
netmask.s_addr = 0;
(void)scope; /* warning */
(void)preferred;
(void)valid;
memset(&addr, 0, sizeof(addr));
#ifdef HAVE_SOCKADDR_SA_LEN
addr.in6.sin6_len = sizeof(addr.in6);
#endif
addr.in6.sin6_family = AF_INET6;
addr.in6.sin6_addr = *local;
addr.in6.sin6_port = htons(daemon->port);
/* FreeBSD insists this is zero for non-linklocal addresses */
if (IN6_IS_ADDR_LINKLOCAL(local))
addr.in6.sin6_scope_id = if_index;
else
addr.in6.sin6_scope_id = 0;
return iface_allowed((struct iface_param *)vparam, if_index, NULL, &addr, netmask, prefix, flags);
}
static int iface_allowed_v4(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam)
{
union mysockaddr addr;
int prefix, bit;
(void)broadcast; /* warning */
memset(&addr, 0, sizeof(addr));
#ifdef HAVE_SOCKADDR_SA_LEN
addr.in.sin_len = sizeof(addr.in);
#endif
addr.in.sin_family = AF_INET;
addr.in.sin_addr = local;
addr.in.sin_port = htons(daemon->port);
/* determine prefix length from netmask */
for (prefix = 32, bit = 1; (bit & ntohl(netmask.s_addr)) == 0 && prefix != 0; bit = bit << 1, prefix--);
return iface_allowed((struct iface_param *)vparam, if_index, label, &addr, netmask, prefix, 0);
}
/*
* Clean old interfaces no longer found.
*/
static void clean_interfaces()
{
struct irec *iface;
struct irec **up = &daemon->interfaces;
for (iface = *up; iface; iface = *up)
{
if (!iface->found && !iface->done)
{
*up = iface->next;
free(iface->name);
free(iface);
}
else
{
up = &iface->next;
}
}
}
/** Release listener if no other interface needs it.
*
* @return 1 if released, 0 if still required
*/
static int release_listener(struct listener *l)
{
if (l->used > 1)
{
struct irec *iface;
for (iface = daemon->interfaces; iface; iface = iface->next)
if (iface->done && sockaddr_isequal(&l->addr, &iface->addr))
{
if (iface->found)
{
/* update listener to point to active interface instead */
if (!l->iface->found)
l->iface = iface;
}
else
{
l->used--;
iface->done = 0;
}
}
/* Someone is still using this listener, skip its deletion */
if (l->used > 0)
return 0;
}
if (l->iface->done)
{
int port;
port = prettyprint_addr(&l->iface->addr, daemon->addrbuff);
my_syslog(LOG_DEBUG|MS_DEBUG, _("stopped listening on %s(#%d): %s port %d"),
l->iface->name, l->iface->index, daemon->addrbuff, port);
/* In case it ever returns */
l->iface->done = 0;
}
if (l->fd != -1)
close(l->fd);
if (l->tcpfd != -1)
close(l->tcpfd);
if (l->tftpfd != -1)
close(l->tftpfd);
free(l);
return 1;
}
int enumerate_interfaces(int reset)
{
static struct addrlist *spare = NULL;
static int done = 0;
struct iface_param param;
int errsave, ret = 1;
struct addrlist *addr, *tmp;
struct interface_name *intname;
struct cond_domain *cond;
struct irec *iface;
#ifdef HAVE_AUTH
struct auth_zone *zone;
#endif
struct server *serv;
/* Do this max once per select cycle - also inhibits netlink socket use
in TCP child processes. */
if (reset)
{
done = 0;
return 1;
}
if (done)
return 1;
done = 1;
if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
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 (serv->interface[0] != 0)
{
#ifdef HAVE_LINUX_NETWORK
struct ifreq ifr;
safe_strncpy(ifr.ifr_name, serv->interface, IF_NAMESIZE);
if (ioctl(param.fd, SIOCGIFINDEX, &ifr) != -1)
serv->ifindex = ifr.ifr_ifindex;
#else
serv->ifindex = if_nametoindex(serv->interface);
#endif
}
again:
/* Mark interfaces for garbage collection */
for (iface = daemon->interfaces; iface; iface = iface->next)
iface->found = 0;
/* remove addresses stored against interface_names */
for (intname = daemon->int_names; intname; intname = intname->next)
{
for (addr = intname->addr; addr; addr = tmp)
{
tmp = addr->next;
addr->next = spare;
spare = addr;
}
intname->addr = NULL;
}
/* remove addresses stored against cond-domains. */
for (cond = daemon->cond_domain; cond; cond = cond->next)
{
for (addr = cond->al; addr; addr = tmp)
{
tmp = addr->next;
addr->next = spare;
spare = addr;
}
cond->al = NULL;
}
/* Remove list of addresses of local interfaces */
for (addr = daemon->interface_addrs; addr; addr = tmp)
{
tmp = addr->next;
addr->next = spare;
spare = addr;
}
daemon->interface_addrs = NULL;
#ifdef HAVE_AUTH
/* remove addresses stored against auth_zone subnets, but not
ones configured as address literals */
for (zone = daemon->auth_zones; zone; zone = zone->next)
if (zone->interface_names)
{
struct addrlist **up;
for (up = &zone->subnet, addr = zone->subnet; addr; addr = tmp)
{
tmp = addr->next;
if (addr->flags & ADDRLIST_LITERAL)
up = &addr->next;
else
{
*up = addr->next;
addr->next = spare;
spare = addr;
}
}
}
#endif
param.spare = spare;
ret = iface_enumerate(AF_INET6, ¶m, iface_allowed_v6);
if (ret < 0)
goto again;
else if (ret)
{
ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4);
if (ret < 0)
goto again;
}
errsave = errno;
close(param.fd);
if (option_bool(OPT_CLEVERBIND))
{
/* Garbage-collect listeners listening on addresses that no longer exist.
Does nothing when not binding interfaces or for listeners on localhost,
since the ->iface field is NULL. Note that this needs the protections
against reentrancy, hence it's here. It also means there's a possibility,
in OPT_CLEVERBIND mode, that at listener will just disappear after
a call to enumerate_interfaces, this is checked OK on all calls. */
struct listener *l, *tmp, **up;
int freed = 0;
for (up = &daemon->listeners, l = daemon->listeners; l; l = tmp)
{
tmp = l->next;
if (!l->iface || l->iface->found)
up = &l->next;
else if (release_listener(l))
{
*up = tmp;
freed = 1;
}
}
if (freed)
clean_interfaces();
}
errno = errsave;
spare = param.spare;
return ret;
}
/* set NONBLOCK bit on fd: See Stevens 16.6 */
int fix_fd(int fd)
{
int flags;
if ((flags = fcntl(fd, F_GETFL)) == -1 ||
fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
return 0;
return 1;
}
static int make_sock(union mysockaddr *addr, int type, int dienow)
{
int family = addr->sa.sa_family;
int fd, rc, opt = 1;
if ((fd = socket(family, type, 0)) == -1)
{
int port, errsave;
char *s;
/* No error if the kernel just doesn't support this IP flavour */
if (errno == EPROTONOSUPPORT ||
errno == EAFNOSUPPORT ||
errno == EINVAL)
return -1;
err:
errsave = errno;
port = prettyprint_addr(addr, daemon->addrbuff);
if (!option_bool(OPT_NOWILD) && !option_bool(OPT_CLEVERBIND))
sprintf(daemon->addrbuff, "port %d", port);
s = _("failed to create listening socket for %s: %s");
if (fd != -1)
close (fd);
errno = errsave;
if (dienow)
{
/* failure to bind addresses given by --listen-address at this point
is OK if we're doing bind-dynamic */
if (!option_bool(OPT_CLEVERBIND))
die(s, daemon->addrbuff, EC_BADNET);
}
else
my_syslog(LOG_WARNING, s, daemon->addrbuff, strerror(errno));
return -1;
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || !fix_fd(fd))
goto err;
if (family == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1)
goto err;
if ((rc = bind(fd, (struct sockaddr *)addr, sa_len(addr))) == -1)
goto err;
if (type == SOCK_STREAM)
{
#ifdef TCP_FASTOPEN
int qlen = 5;
setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen));
#endif
if (listen(fd, TCP_BACKLOG) == -1)
goto err;
}
else if (family == AF_INET)
{
if (!option_bool(OPT_NOWILD))
{
#if defined(HAVE_LINUX_NETWORK)
if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) == -1)
goto err;
#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt)) == -1 ||
setsockopt(fd, IPPROTO_IP, IP_RECVIF, &opt, sizeof(opt)) == -1)
goto err;
#endif
}
}
else if (!set_ipv6pktinfo(fd))
goto err;
return fd;
}
int set_ipv6pktinfo(int fd)
{
int opt = 1;
/* The API changed around Linux 2.6.14 but the old ABI is still supported:
handle all combinations of headers and kernel.
OpenWrt note that this fixes the problem addressed by your very broken patch. */
daemon->v6pktinfo = IPV6_PKTINFO;
#ifdef IPV6_RECVPKTINFO
if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &opt, sizeof(opt)) != -1)
return 1;
# ifdef IPV6_2292PKTINFO
else if (errno == ENOPROTOOPT && setsockopt(fd, IPPROTO_IPV6, IPV6_2292PKTINFO, &opt, sizeof(opt)) != -1)
{
daemon->v6pktinfo = IPV6_2292PKTINFO;
return 1;
}
# endif
#else
if (setsockopt(fd, IPPROTO_IPV6, IPV6_PKTINFO, &opt, sizeof(opt)) != -1)
return 1;
#endif
return 0;
}
/* Find the interface on which a TCP connection arrived, if possible, or zero otherwise. */
int tcp_interface(int fd, int af)
{
(void)fd; /* suppress potential unused warning */
(void)af; /* suppress potential unused warning */
int if_index = 0;
#ifdef HAVE_LINUX_NETWORK
int opt = 1;
struct cmsghdr *cmptr;
struct msghdr msg;
socklen_t len;
/* use mshdr so that the CMSDG_* macros are available */
msg.msg_control = daemon->packet;
msg.msg_controllen = len = daemon->packet_buff_sz;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
if (af == AF_INET)
{
if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 &&
getsockopt(fd, IPPROTO_IP, IP_PKTOPTIONS, msg.msg_control, &len) != -1)
{
msg.msg_controllen = len;
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
{
union {
unsigned char *c;
struct in_pktinfo *p;
} p;
p.c = CMSG_DATA(cmptr);
if_index = p.p->ipi_ifindex;
}
}
}
else
{
/* Only the RFC-2292 API has the ability to find the interface for TCP connections,
it was removed in RFC-3542 !!!!
Fortunately, Linux kept the 2292 ABI when it moved to 3542. The following code always
uses the old ABI, and should work with pre- and post-3542 kernel headers */
#ifdef IPV6_2292PKTOPTIONS
# define PKTOPTIONS IPV6_2292PKTOPTIONS
#else
# define PKTOPTIONS IPV6_PKTOPTIONS
#endif
if (set_ipv6pktinfo(fd) &&
getsockopt(fd, IPPROTO_IPV6, PKTOPTIONS, msg.msg_control, &len) != -1)
{
msg.msg_controllen = len;
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
{
union {
unsigned char *c;
struct in6_pktinfo *p;
} p;
p.c = CMSG_DATA(cmptr);
if_index = p.p->ipi6_ifindex;
}
}
}
#endif /* Linux */
return if_index;
}
static struct listener *create_listeners(union mysockaddr *addr, int do_tftp, int dienow)
{
struct listener *l = NULL;
int fd = -1, tcpfd = -1, tftpfd = -1;
(void)do_tftp;
if (daemon->port != 0)
{
fd = make_sock(addr, SOCK_DGRAM, dienow);
tcpfd = make_sock(addr, SOCK_STREAM, dienow);
}
#ifdef HAVE_TFTP
if (do_tftp)
{
if (addr->sa.sa_family == AF_INET)
{
/* port must be restored to DNS port for TCP code */
short save = addr->in.sin_port;
addr->in.sin_port = htons(TFTP_PORT);
tftpfd = make_sock(addr, SOCK_DGRAM, dienow);
addr->in.sin_port = save;
}
else
{
short save = addr->in6.sin6_port;
addr->in6.sin6_port = htons(TFTP_PORT);
tftpfd = make_sock(addr, SOCK_DGRAM, dienow);
addr->in6.sin6_port = save;
}
}
#endif
if (fd != -1 || tcpfd != -1 || tftpfd != -1)
{
l = safe_malloc(sizeof(struct listener));
l->next = NULL;
l->fd = fd;
l->tcpfd = tcpfd;
l->tftpfd = tftpfd;
l->addr = *addr;
l->used = 1;
l->iface = NULL;
}
return l;
}
void create_wildcard_listeners(void)
{
union mysockaddr addr;
struct listener *l, *l6;
memset(&addr, 0, sizeof(addr));
#ifdef HAVE_SOCKADDR_SA_LEN
addr.in.sin_len = sizeof(addr.in);
#endif
addr.in.sin_family = AF_INET;
addr.in.sin_addr.s_addr = INADDR_ANY;
addr.in.sin_port = htons(daemon->port);
l = create_listeners(&addr, !!option_bool(OPT_TFTP), 1);
memset(&addr, 0, sizeof(addr));
#ifdef HAVE_SOCKADDR_SA_LEN
addr.in6.sin6_len = sizeof(addr.in6);
#endif
addr.in6.sin6_family = AF_INET6;
addr.in6.sin6_addr = in6addr_any;
addr.in6.sin6_port = htons(daemon->port);
l6 = create_listeners(&addr, !!option_bool(OPT_TFTP), 1);
if (l)
l->next = l6;
else
l = l6;
daemon->listeners = l;
}
static struct listener *find_listener(union mysockaddr *addr)
{
struct listener *l;
for (l = daemon->listeners; l; l = l->next)
if (sockaddr_isequal(&l->addr, addr))
return l;
return NULL;
}
void create_bound_listeners(int dienow)
{
struct listener *new;
struct irec *iface;
struct iname *if_tmp;
struct listener *existing;
for (iface = daemon->interfaces; iface; iface = iface->next)
if (!iface->done && !iface->dad && iface->found)
{
existing = find_listener(&iface->addr);
if (existing)
{
iface->done = 1;
existing->used++; /* increase usage counter */
}
else if ((new = create_listeners(&iface->addr, iface->tftp_ok, dienow)))
{
new->iface = iface;
new->next = daemon->listeners;
daemon->listeners = new;
iface->done = 1;
/* Don't log the initial set of listen addresses created
at startup, since this is happening before the logging
system is initialised and the sign-on printed. */
if (!dienow)
{
int port = prettyprint_addr(&iface->addr, daemon->addrbuff);
my_syslog(LOG_DEBUG|MS_DEBUG, _("listening on %s(#%d): %s port %d"),
iface->name, iface->index, daemon->addrbuff, port);
}
}
}
/* Check for --listen-address options that haven't been used because there's
no interface with a matching address. These may be valid: eg it's possible
to listen on 127.0.1.1 even if the loopback interface is 127.0.0.1
If the address isn't valid the bind() will fail and we'll die()
(except in bind-dynamic mode, when we'll complain but keep trying.)
The resulting listeners have the ->iface field NULL, and this has to be
handled by the DNS and TFTP code. It disables --localise-queries processing
(no netmask) and some MTU login the tftp code. */
for (if_tmp = daemon->if_addrs; if_tmp; if_tmp = if_tmp->next)
if (!if_tmp->used &&
(new = create_listeners(&if_tmp->addr, !!option_bool(OPT_TFTP), dienow)))
{
new->next = daemon->listeners;
daemon->listeners = new;
if (!dienow)
{
int port = prettyprint_addr(&if_tmp->addr, daemon->addrbuff);
my_syslog(LOG_DEBUG|MS_DEBUG, _("listening on %s port %d"), daemon->addrbuff, port);
}
}
}
/* In --bind-interfaces, the only access control is the addresses we're listening on.
There's nothing to avoid a query to the address of an internal interface arriving via
an external interface where we don't want to accept queries, except that in the usual
case the addresses of internal interfaces are RFC1918. When bind-interfaces in use,
and we listen on an address that looks like it's probably globally routeable, shout.
The fix is to use --bind-dynamic, which actually checks the arrival interface too.
Tough if your platform doesn't support this.
Note that checking the arrival interface is supported in the standard IPv6 API and
always done, so we don't warn about any IPv6 addresses here.
*/
void warn_bound_listeners(void)
{
struct irec *iface;
int advice = 0;
for (iface = daemon->interfaces; iface; iface = iface->next)
if (!iface->dns_auth)
{
if (iface->addr.sa.sa_family == AF_INET)
{
if (!private_net(iface->addr.in.sin_addr, 1))
{
inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
iface->warned = advice = 1;
my_syslog(LOG_WARNING,
_("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"),
daemon->addrbuff, iface->name);
}
}
}
if (advice)
my_syslog(LOG_WARNING, _("LOUD WARNING: use --bind-dynamic rather than --bind-interfaces to avoid DNS amplification attacks via these interface(s)"));
}
void warn_wild_labels(void)
{
struct irec *iface;
for (iface = daemon->interfaces; iface; iface = iface->next)
if (iface->found && iface->name && iface->label)
my_syslog(LOG_WARNING, _("warning: using interface %s instead"), iface->name);
}
void warn_int_names(void)
{
struct interface_name *intname;
for (intname = daemon->int_names; intname; intname = intname->next)
if (!intname->addr)
my_syslog(LOG_WARNING, _("warning: no addresses found for interface %s"), intname->intr);
}
int is_dad_listeners(void)
{
struct irec *iface;
if (option_bool(OPT_NOWILD))
for (iface = daemon->interfaces; iface; iface = iface->next)
if (iface->dad && !iface->done)
return 1;
return 0;
}
#ifdef HAVE_DHCP6
void join_multicast(int dienow)
{
struct irec *iface, *tmp;
for (iface = daemon->interfaces; iface; iface = iface->next)
if (iface->addr.sa.sa_family == AF_INET6 && iface->dhcp_ok && !iface->multicast_done)
{
/* There's an irec per address but we only want to join for multicast
once per interface. Weed out duplicates. */
for (tmp = daemon->interfaces; tmp; tmp = tmp->next)
if (tmp->multicast_done && tmp->index == iface->index)
break;
iface->multicast_done = 1;
if (!tmp)
{
struct ipv6_mreq mreq;
int err = 0;
mreq.ipv6mr_interface = iface->index;
inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &mreq.ipv6mr_multiaddr);
if ((daemon->doing_dhcp6 || daemon->relay6) &&
setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
err = errno;
inet_pton(AF_INET6, ALL_SERVERS, &mreq.ipv6mr_multiaddr);
if (daemon->doing_dhcp6 &&
setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
err = errno;
inet_pton(AF_INET6, ALL_ROUTERS, &mreq.ipv6mr_multiaddr);
if (daemon->doing_ra &&
setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
err = errno;
if (err)
{
char *s = _("interface %s failed to join DHCPv6 multicast group: %s");
errno = err;
#ifdef HAVE_LINUX_NETWORK
if (errno == ENOMEM)
my_syslog(LOG_ERR, _("try increasing /proc/sys/net/core/optmem_max"));
#endif
if (dienow)
die(s, iface->name, EC_BADNET);
else
my_syslog(LOG_ERR, s, iface->name, strerror(errno));
}
}
}
}
#endif
int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp)
{
union mysockaddr addr_copy = *addr;
unsigned short port;
int tries = 1;
unsigned short ports_avail = 1;
if (addr_copy.sa.sa_family == AF_INET)
port = addr_copy.in.sin_port;
else
port = addr_copy.in6.sin6_port;
/* cannot set source _port_ for TCP connections. */
if (is_tcp)
port = 0;
else if (port == 0 && daemon->max_port != 0)
{
/* Bind a random port within the range given by min-port and max-port if either
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 < SMALL_PORT_RANGE) ? ports_avail : 100;
port = htons(daemon->min_port + (rand16() % ports_avail));
}
while (1)
{
/* elide bind() call if it's to port 0, address 0 */
if (addr_copy.sa.sa_family == AF_INET)
{
if (port == 0 && addr_copy.in.sin_addr.s_addr == 0)
break;
addr_copy.in.sin_port = port;
}
else
{
if (port == 0 && IN6_IS_ADDR_UNSPECIFIED(&addr_copy.in6.sin6_addr))
break;
addr_copy.in6.sin6_port = port;
}
if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) != -1)
break;
if (errno != EADDRINUSE && errno != EACCES)
return 0;
if (--tries == 0)
return 0;
/* For small ranges, do a systematic search, not a random one. */
if (ports_avail < SMALL_PORT_RANGE)
{
unsigned short hport = ntohs(port);
if (hport++ == daemon->max_port)
hport = daemon->min_port;
port = htons(hport);
}
else
port = htons(daemon->min_port + (rand16() % ports_avail));
}
if (!is_tcp && ifindex > 0)
{
#if defined(IP_UNICAST_IF)
if (addr_copy.sa.sa_family == AF_INET)
{
uint32_t ifindex_opt = htonl(ifindex);
return setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex_opt, sizeof(ifindex_opt)) == 0;
}
#endif
#if defined (IPV6_UNICAST_IF)
if (addr_copy.sa.sa_family == AF_INET6)
{
uint32_t ifindex_opt = htonl(ifindex);
return setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex_opt, sizeof(ifindex_opt)) == 0;
}
#endif
}
(void)intname; /* suppress potential unused warning */
#if defined(SO_BINDTODEVICE)
if (intname[0] != 0 &&
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE) == -1)
return 0;
#endif
return 1;
}
static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsigned int ifindex)
{
struct serverfd *sfd;
int errsave;
int opt = 1;
/* when using random ports, servers which would otherwise use
the INADDR_ANY/port0 socket have sfd set to NULL, this is
anything without an explictly set source port. */
if (!daemon->osport)
{
errno = 0;
if (addr->sa.sa_family == AF_INET &&
addr->in.sin_port == htons(0))
return NULL;
if (addr->sa.sa_family == AF_INET6 &&
addr->in6.sin6_port == htons(0))
return NULL;
}
/* may have a suitable one already */
for (sfd = daemon->sfds; sfd; sfd = sfd->next )
if (ifindex == sfd->ifindex &&
sockaddr_isequal(&sfd->source_addr, addr) &&
strcmp(intname, sfd->interface) == 0)
return sfd;
/* need to make a new one. */
errno = ENOMEM; /* in case malloc fails. */
if (!(sfd = whine_malloc(sizeof(struct serverfd))))
return NULL;
if ((sfd->fd = socket(addr->sa.sa_family, SOCK_DGRAM, 0)) == -1)
{
free(sfd);
return NULL;
}
if ((addr->sa.sa_family == AF_INET6 && setsockopt(sfd->fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1) ||
!local_bind(sfd->fd, addr, intname, ifindex, 0) || !fix_fd(sfd->fd))
{
errsave = errno; /* save error from bind/setsockopt. */
close(sfd->fd);
free(sfd);
errno = errsave;
return NULL;
}
safe_strncpy(sfd->interface, intname, sizeof(sfd->interface));
sfd->source_addr = *addr;
sfd->next = daemon->sfds;
sfd->ifindex = ifindex;
sfd->preallocated = 0;
daemon->sfds = sfd;
return sfd;
}
/* create upstream sockets during startup, before root is dropped which may be needed
this allows query_port to be a low port and interface binding */
void pre_allocate_sfds(void)
{
struct server *srv;
struct serverfd *sfd;
if (daemon->query_port != 0)
{
union mysockaddr addr;
memset(&addr, 0, sizeof(addr));
addr.in.sin_family = AF_INET;
addr.in.sin_addr.s_addr = INADDR_ANY;
addr.in.sin_port = htons(daemon->query_port);
#ifdef HAVE_SOCKADDR_SA_LEN
addr.in.sin_len = sizeof(struct sockaddr_in);
#endif
if ((sfd = allocate_sfd(&addr, "", 0)))
sfd->preallocated = 1;
memset(&addr, 0, sizeof(addr));
addr.in6.sin6_family = AF_INET6;
addr.in6.sin6_addr = in6addr_any;
addr.in6.sin6_port = htons(daemon->query_port);
#ifdef HAVE_SOCKADDR_SA_LEN
addr.in6.sin6_len = sizeof(struct sockaddr_in6);
#endif
if ((sfd = allocate_sfd(&addr, "", 0)))
sfd->preallocated = 1;
}
for (srv = daemon->servers; srv; srv = srv->next)
if (!allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) &&
errno != 0 &&
option_bool(OPT_NOWILD))
{
(void)prettyprint_addr(&srv->source_addr, daemon->namebuff);
if (srv->interface[0] != 0)
{
strcat(daemon->namebuff, " ");
strcat(daemon->namebuff, srv->interface);
}
die(_("failed to bind server socket for %s: %s"),
daemon->namebuff, EC_BADNET);
}
}
void check_servers(int no_loop_check)
{
struct irec *iface;
struct server *serv;
struct serverfd *sfd, *tmp, **up;
int port = 0, count;
int locals = 0;
#ifdef HAVE_LOOP
if (!no_loop_check)
loop_send_probes();
#endif
/* clear all marks. */
mark_servers(0);
/* interface may be new since startup */
if (!option_bool(OPT_NOWILD))
enumerate_interfaces(0);
/* don't garbage collect pre-allocated sfds. */
for (sfd = daemon->sfds; sfd; sfd = sfd->next)
sfd->used = sfd->preallocated;
for (count = 0, serv = daemon->servers; serv; serv = serv->next)
{
/* Init edns_pktsz for newly created server records. */
if (serv->edns_pktsz == 0)
serv->edns_pktsz = daemon->edns_pktsz;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
{
if (!(serv->flags & SERV_FOR_NODOTS))
serv->flags |= SERV_DO_DNSSEC;
/* Disable DNSSEC validation when using server=/domain/.... servers
unless there's a configured trust anchor. */
if (strlen(serv->domain) != 0)
{
struct ds_config *ds;
char *domain = serv->domain;
/* .example.com is valid */
while (*domain == '.')
domain++;
for (ds = daemon->ds; ds; ds = ds->next)
if (ds->name[0] != 0 && hostname_isequal(domain, ds->name))
break;
if (!ds)
serv->flags &= ~SERV_DO_DNSSEC;
}
}
#endif
port = prettyprint_addr(&serv->addr, daemon->namebuff);
/* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */
if (serv->addr.sa.sa_family == AF_INET &&
serv->addr.in.sin_addr.s_addr == 0)
{
serv->flags |= SERV_MARK;
continue;
}
for (iface = daemon->interfaces; iface; iface = iface->next)
if (sockaddr_isequal(&serv->addr, &iface->addr))
break;
if (iface)
{
my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff);
serv->flags |= SERV_MARK;
continue;
}
/* Do we need a socket set? */
if (!serv->sfd &&
!(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) &&
errno != 0)
{
my_syslog(LOG_WARNING,
_("ignoring nameserver %s - cannot make/bind socket: %s"),
daemon->namebuff, strerror(errno));
serv->flags |= SERV_MARK;
continue;
}
if (serv->sfd)
serv->sfd->used = 1;
if (count == SERVERS_LOGGED)
my_syslog(LOG_INFO, _("more servers are defined but not logged"));
if (++count > SERVERS_LOGGED)
continue;
if (strlen(serv->domain) != 0 || (serv->flags & SERV_FOR_NODOTS))
{
char *s1, *s2, *s3 = "", *s4 = "";
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC))
s3 = _("(no DNSSEC)");
#endif
if (serv->flags & SERV_FOR_NODOTS)
s1 = _("unqualified"), s2 = _("names");
else if (strlen(serv->domain) == 0)
s1 = _("default"), s2 = "";
else
s1 = _("domain"), s2 = serv->domain, s4 = (serv->flags & SERV_WILDCARD) ? "*" : "";
my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s%s %s"), daemon->namebuff, port, s1, s4, s2, s3);
}
#ifdef HAVE_LOOP
else if (serv->flags & SERV_LOOP)
my_syslog(LOG_INFO, _("NOT using nameserver %s#%d - query loop detected"), daemon->namebuff, port);
#endif
else if (serv->interface[0] != 0)
my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface);
else
my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port);
}
for (count = 0, serv = daemon->local_domains; serv; serv = serv->next)
{
if (++count > SERVERS_LOGGED)
continue;
if ((serv->flags & SERV_LITERAL_ADDRESS) &&
!(serv->flags & (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS)) &&
strlen(serv->domain))
{
count--;
if (++locals <= LOCALS_LOGGED)
my_syslog(LOG_INFO, _("using only locally-known addresses for %s"), serv->domain);
}
else if (serv->flags & SERV_USE_RESOLV)
my_syslog(LOG_INFO, _("using standard nameservers for %s"), serv->domain);
}
if (locals > LOCALS_LOGGED)
my_syslog(LOG_INFO, _("using %d more local addresses"), locals - LOCALS_LOGGED);
if (count - 1 > SERVERS_LOGGED)
my_syslog(LOG_INFO, _("using %d more nameservers"), count - SERVERS_LOGGED - 1);
/* Remove unused sfds */
for (sfd = daemon->sfds, up = &daemon->sfds; sfd; sfd = tmp)
{
tmp = sfd->next;
if (!sfd->used)
{
*up = sfd->next;
close(sfd->fd);
free(sfd);
}
else
up = &sfd->next;
}
cleanup_servers(); /* remove servers we just deleted. */
build_server_array();
}
/* Return zero if no servers found, in that case we keep polling.
This is a protection against an update-time/write race on resolv.conf */
int reload_servers(char *fname)
{
FILE *f;
char *line;
int gotone = 0;
/* buff happens to be MAXDNAME long... */
if (!(f = fopen(fname, "r")))
{
my_syslog(LOG_ERR, _("failed to read %s: %s"), fname, strerror(errno));
return 0;
}
mark_servers(SERV_FROM_RESOLV);
while ((line = fgets(daemon->namebuff, MAXDNAME, f)))
{
union mysockaddr addr, source_addr;
char *token = strtok(line, " \t\n\r");
if (!token)
continue;
if (strcmp(token, "nameserver") != 0 && strcmp(token, "server") != 0)
continue;
if (!(token = strtok(NULL, " \t\n\r")))
continue;
memset(&addr, 0, sizeof(addr));
memset(&source_addr, 0, sizeof(source_addr));
if (inet_pton(AF_INET, token, &addr.in.sin_addr) > 0)
{
#ifdef HAVE_SOCKADDR_SA_LEN
source_addr.in.sin_len = addr.in.sin_len = sizeof(source_addr.in);
#endif
source_addr.in.sin_family = addr.in.sin_family = AF_INET;
addr.in.sin_port = htons(NAMESERVER_PORT);
source_addr.in.sin_addr.s_addr = INADDR_ANY;
source_addr.in.sin_port = htons(daemon->query_port);
}
else
{
int scope_index = 0;
char *scope_id = strchr(token, '%');
if (scope_id)
{
*(scope_id++) = 0;
scope_index = if_nametoindex(scope_id);
}
if (inet_pton(AF_INET6, token, &addr.in6.sin6_addr) > 0)
{
#ifdef HAVE_SOCKADDR_SA_LEN
source_addr.in6.sin6_len = addr.in6.sin6_len = sizeof(source_addr.in6);
#endif
source_addr.in6.sin6_family = addr.in6.sin6_family = AF_INET6;
source_addr.in6.sin6_flowinfo = addr.in6.sin6_flowinfo = 0;
addr.in6.sin6_port = htons(NAMESERVER_PORT);
addr.in6.sin6_scope_id = scope_index;
source_addr.in6.sin6_addr = in6addr_any;
source_addr.in6.sin6_port = htons(daemon->query_port);
source_addr.in6.sin6_scope_id = 0;
}
else
continue;
}
add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL, NULL);
gotone = 1;
}
fclose(f);
cleanup_servers();
return gotone;
}
/* Called when addresses are added or deleted from an interface */
void newaddress(time_t now)
{
struct dhcp_relay *relay;
(void)now;
if (option_bool(OPT_CLEVERBIND) || option_bool(OPT_LOCAL_SERVICE) ||
daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
enumerate_interfaces(0);
if (option_bool(OPT_CLEVERBIND))
create_bound_listeners(0);
#ifdef HAVE_DHCP
/* clear cache of subnet->relay index */
for (relay = daemon->relay4; relay; relay = relay->next)
relay->iface_index = 0;
#endif
#ifdef HAVE_DHCP6
if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
join_multicast(0);
if (daemon->doing_dhcp6 || daemon->doing_ra)
dhcp_construct_contexts(now);
if (daemon->doing_dhcp6)
lease_find_interfaces(now);
for (relay = daemon->relay6; relay; relay = relay->next)
relay->iface_index = 0;
#endif
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>