mirror of
http://thekelleys.org.uk/git/dnsmasq.git
synced 2026-10-17 10:59:36 +00:00
776 lines
20 KiB
C
776 lines
20 KiB
C
/* dnsmasq is Copyright (c) 2000-2005 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.
|
|
|
|
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.
|
|
*/
|
|
|
|
/* Author's email: simon@thekelleys.org.uk */
|
|
|
|
#include "dnsmasq.h"
|
|
|
|
static char *compile_opts =
|
|
#ifndef HAVE_IPV6
|
|
"no-"
|
|
#endif
|
|
"IPv6 "
|
|
#ifndef HAVE_GETOPT_LONG
|
|
"no-"
|
|
#endif
|
|
"GNU-getopt "
|
|
#ifdef HAVE_BROKEN_RTC
|
|
"no-RTC "
|
|
#endif
|
|
#ifndef HAVE_ISC_READER
|
|
"no-"
|
|
#endif
|
|
"ISC-leasefile "
|
|
#ifndef HAVE_DBUS
|
|
"no-"
|
|
#endif
|
|
"DBus";
|
|
|
|
static volatile int sigterm, sighup, sigusr1, sigalarm, num_kids, in_child;
|
|
|
|
static int set_dns_listeners(struct daemon *daemon, fd_set *set, int maxfd);
|
|
static void check_dns_listeners(struct daemon *daemon, fd_set *set, time_t now);
|
|
static void sig_handler(int sig);
|
|
|
|
int main (int argc, char **argv)
|
|
{
|
|
struct daemon *daemon;
|
|
int first_loop = 1;
|
|
int bind_fallback = 0;
|
|
time_t now, last = 0;
|
|
struct sigaction sigact;
|
|
sigset_t sigmask;
|
|
struct iname *if_tmp;
|
|
|
|
sighup = 1; /* init cache the first time through */
|
|
sigusr1 = 0; /* but don't dump */
|
|
sigterm = 0; /* or die */
|
|
#ifdef HAVE_BROKEN_RTC
|
|
sigalarm = 1; /* need regular lease dumps */
|
|
#else
|
|
sigalarm = 0; /* or not */
|
|
#endif
|
|
num_kids = 0;
|
|
in_child = 0;
|
|
|
|
sigact.sa_handler = sig_handler;
|
|
sigact.sa_flags = 0;
|
|
sigemptyset(&sigact.sa_mask);
|
|
sigaction(SIGUSR1, &sigact, NULL);
|
|
sigaction(SIGHUP, &sigact, NULL);
|
|
sigaction(SIGTERM, &sigact, NULL);
|
|
sigaction(SIGALRM, &sigact, NULL);
|
|
sigaction(SIGCHLD, &sigact, NULL);
|
|
|
|
/* ignore SIGPIPE */
|
|
sigact.sa_handler = SIG_IGN;
|
|
sigaction(SIGPIPE, &sigact, NULL);
|
|
|
|
/* now block all the signals, they stay that way except
|
|
during the call to pselect */
|
|
sigaddset(&sigact.sa_mask, SIGUSR1);
|
|
sigaddset(&sigact.sa_mask, SIGTERM);
|
|
sigaddset(&sigact.sa_mask, SIGHUP);
|
|
sigaddset(&sigact.sa_mask, SIGALRM);
|
|
sigaddset(&sigact.sa_mask, SIGCHLD);
|
|
sigprocmask(SIG_BLOCK, &sigact.sa_mask, &sigmask);
|
|
|
|
daemon = read_opts(argc, argv, compile_opts);
|
|
|
|
if (daemon->edns_pktsz < PACKETSZ)
|
|
daemon->edns_pktsz = PACKETSZ;
|
|
daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ?
|
|
daemon->edns_pktsz : DNSMASQ_PACKETSZ;
|
|
daemon->packet = safe_malloc(daemon->packet_buff_sz);
|
|
|
|
if (!daemon->lease_file)
|
|
{
|
|
if (daemon->dhcp)
|
|
daemon->lease_file = LEASEFILE;
|
|
}
|
|
#ifndef HAVE_ISC_READER
|
|
else if (!daemon->dhcp)
|
|
die("ISC dhcpd integration not available: set HAVE_ISC_READER in src/config.h", NULL);
|
|
#endif
|
|
|
|
if (!enumerate_interfaces(daemon, &daemon->interfaces, NULL, NULL))
|
|
die("failed to find list of interfaces: %s", NULL);
|
|
|
|
if (!(daemon->options & OPT_NOWILD) &&
|
|
!(daemon->listeners = create_wildcard_listeners(daemon->port)))
|
|
{
|
|
bind_fallback = 1;
|
|
daemon->options |= OPT_NOWILD;
|
|
}
|
|
|
|
if (daemon->options & OPT_NOWILD)
|
|
{
|
|
daemon->listeners = create_bound_listeners(daemon->interfaces, daemon->port);
|
|
|
|
for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
|
|
if (if_tmp->name && !if_tmp->used)
|
|
die("unknown interface %s", if_tmp->name);
|
|
|
|
for (if_tmp = daemon->if_addrs; if_tmp; if_tmp = if_tmp->next)
|
|
if (!if_tmp->used)
|
|
{
|
|
prettyprint_addr(&if_tmp->addr, daemon->namebuff);
|
|
die("no interface with address %s", daemon->namebuff);
|
|
}
|
|
}
|
|
|
|
forward_init(1);
|
|
cache_init(daemon->cachesize, daemon->options & OPT_LOG);
|
|
|
|
#ifdef HAVE_BROKEN_RTC
|
|
if ((daemon->uptime_fd = open(UPTIME, O_RDONLY)) == -1)
|
|
die("cannot open " UPTIME ":%s", NULL);
|
|
#endif
|
|
|
|
now = dnsmasq_time(daemon->uptime_fd);
|
|
|
|
if (daemon->dhcp)
|
|
{
|
|
#if !defined(IP_PKTINFO) && !defined(IP_RECVIF)
|
|
int c;
|
|
struct iname *tmp;
|
|
for (c = 0, tmp = daemon->if_names; tmp; tmp = tmp->next)
|
|
if (!tmp->isloop)
|
|
c++;
|
|
if (c != 1)
|
|
die("must set exactly one interface on broken systems without IP_RECVIF", NULL);
|
|
#endif
|
|
dhcp_init(daemon);
|
|
lease_init(daemon, now);
|
|
}
|
|
|
|
if (daemon->options & OPT_DBUS)
|
|
#ifdef HAVE_DBUS
|
|
{
|
|
char *err;
|
|
daemon->dbus = NULL;
|
|
daemon->watches = NULL;
|
|
if ((err = dbus_init(daemon)))
|
|
die("DBus error: %s", err);
|
|
}
|
|
#else
|
|
if (daemon->options & OPT_DBUS)
|
|
die("DBus not available: set HAVE_DBUS in src/config.h", NULL);
|
|
#endif
|
|
|
|
/* If query_port is set then create a socket now, before dumping root
|
|
for use to access nameservers without more specific source addresses.
|
|
This allows query_port to be a low port */
|
|
if (daemon->query_port)
|
|
{
|
|
union mysockaddr 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
|
|
allocate_sfd(&addr, &daemon->sfds);
|
|
#ifdef HAVE_IPV6
|
|
addr.in6.sin6_family = AF_INET6;
|
|
addr.in6.sin6_addr = in6addr_any;
|
|
addr.in6.sin6_port = htons(daemon->query_port);
|
|
addr.in6.sin6_flowinfo = htonl(0);
|
|
#ifdef HAVE_SOCKADDR_SA_LEN
|
|
addr.in6.sin6_len = sizeof(struct sockaddr_in6);
|
|
#endif
|
|
allocate_sfd(&addr, &daemon->sfds);
|
|
#endif
|
|
}
|
|
|
|
setbuf(stdout, NULL);
|
|
|
|
if (!(daemon->options & OPT_DEBUG))
|
|
{
|
|
FILE *pidfile;
|
|
struct passwd *ent_pw;
|
|
fd_set test_set;
|
|
int maxfd, i;
|
|
|
|
FD_ZERO(&test_set);
|
|
maxfd = set_dns_listeners(daemon, &test_set, -1);
|
|
#ifdef HAVE_DBUS
|
|
maxfd = set_dbus_listeners(daemon, maxfd, &test_set, &test_set, &test_set);
|
|
#endif
|
|
|
|
/* The following code "daemonizes" the process.
|
|
See Stevens section 12.4 */
|
|
|
|
#ifndef NO_FORK
|
|
if (!(daemon->options & OPT_NO_FORK))
|
|
{
|
|
if (fork() != 0 )
|
|
exit(0);
|
|
|
|
setsid();
|
|
|
|
if (fork() != 0)
|
|
exit(0);
|
|
}
|
|
#endif
|
|
|
|
chdir("/");
|
|
umask(022); /* make pidfile 0644 */
|
|
|
|
/* write pidfile _after_ forking ! */
|
|
if (daemon->runfile && (pidfile = fopen(daemon->runfile, "w")))
|
|
{
|
|
fprintf(pidfile, "%d\n", (int) getpid());
|
|
fclose(pidfile);
|
|
}
|
|
|
|
umask(0);
|
|
|
|
for (i=0; i<64; i++)
|
|
{
|
|
#ifdef HAVE_BROKEN_RTC
|
|
if (i == daemon->uptime_fd)
|
|
continue;
|
|
#endif
|
|
|
|
if (daemon->dhcp &&
|
|
(i == daemon->lease_fd ||
|
|
i == daemon->dhcpfd ||
|
|
i == daemon->dhcp_raw_fd ||
|
|
i == daemon->dhcp_icmp_fd))
|
|
continue;
|
|
|
|
if (i <= maxfd && FD_ISSET(i, &test_set))
|
|
continue;
|
|
|
|
close(i);
|
|
}
|
|
|
|
/* Change uid and gid for security */
|
|
if (daemon->username && (ent_pw = getpwnam(daemon->username)))
|
|
{
|
|
gid_t dummy;
|
|
struct group *gp;
|
|
/* remove all supplimentary groups */
|
|
setgroups(0, &dummy);
|
|
/* change group for /etc/ppp/resolv.conf
|
|
otherwise get the group for "nobody" */
|
|
if ((daemon->groupname && (gp = getgrnam(daemon->groupname))) ||
|
|
(gp = getgrgid(ent_pw->pw_gid)))
|
|
setgid(gp->gr_gid);
|
|
/* finally drop root */
|
|
setuid(ent_pw->pw_uid);
|
|
}
|
|
}
|
|
|
|
openlog("dnsmasq",
|
|
DNSMASQ_LOG_OPT(daemon->options & OPT_DEBUG),
|
|
DNSMASQ_LOG_FAC(daemon->options & OPT_DEBUG));
|
|
|
|
if (daemon->cachesize != 0)
|
|
syslog(LOG_INFO, "started, version %s cachesize %d", VERSION, daemon->cachesize);
|
|
else
|
|
syslog(LOG_INFO, "started, version %s cache disabled", VERSION);
|
|
|
|
syslog(LOG_INFO, "compile time options: %s", compile_opts);
|
|
|
|
#ifdef HAVE_DBUS
|
|
if (daemon->options & OPT_DBUS)
|
|
{
|
|
if (daemon->dbus)
|
|
syslog(LOG_INFO, "DBus support enabled: connected to system bus");
|
|
else
|
|
syslog(LOG_INFO, "DBus support enabled: bus connection pending");
|
|
}
|
|
#endif
|
|
|
|
if (bind_fallback)
|
|
syslog(LOG_WARNING, "setting --bind-interfaces option because of OS limitations");
|
|
|
|
if (!(daemon->options & OPT_NOWILD))
|
|
for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
|
|
if (if_tmp->name && !if_tmp->used)
|
|
syslog(LOG_WARNING, "warning: interface %s does not currently exist", if_tmp->name);
|
|
|
|
if (daemon->dhcp)
|
|
{
|
|
struct dhcp_context *dhcp_tmp;
|
|
|
|
#ifdef HAVE_RTNETLINK
|
|
/* Must do this after daemonizing so that the pid is right */
|
|
daemon->netlinkfd = netlink_init();
|
|
#endif
|
|
|
|
for (dhcp_tmp = daemon->dhcp; dhcp_tmp; dhcp_tmp = dhcp_tmp->next)
|
|
{
|
|
prettyprint_time(daemon->dhcp_buff2, dhcp_tmp->lease_time);
|
|
strcpy(daemon->dhcp_buff, inet_ntoa(dhcp_tmp->start));
|
|
syslog(LOG_INFO,
|
|
(dhcp_tmp->flags & CONTEXT_STATIC) ?
|
|
"DHCP, static leases only on %.0s%s, lease time %s" :
|
|
"DHCP, IP range %s -- %s, lease time %s",
|
|
daemon->dhcp_buff, inet_ntoa(dhcp_tmp->end), daemon->dhcp_buff2);
|
|
}
|
|
|
|
#ifdef HAVE_BROKEN_RTC
|
|
daemon->min_leasetime = daemon->min_leasetime/3;
|
|
if (daemon->min_leasetime > (60 * 60 * 24))
|
|
daemon->min_leasetime = 60 * 60 * 24;
|
|
if (daemon->min_leasetime < 60)
|
|
daemon->min_leasetime = 60;
|
|
prettyprint_time(daemon->dhcp_buff2, daemon->min_leasetime);
|
|
syslog(LOG_INFO, "DHCP, %s will be written every %s", daemon->lease_file, daemon->dhcp_buff2);
|
|
#endif
|
|
}
|
|
|
|
if (!(daemon->options & OPT_DEBUG) && (getuid() == 0 || geteuid() == 0))
|
|
syslog(LOG_WARNING, "running as root");
|
|
|
|
check_servers(daemon);
|
|
|
|
while (sigterm == 0)
|
|
{
|
|
fd_set rset, wset, eset;
|
|
|
|
if (sighup)
|
|
{
|
|
clear_cache_and_reload(daemon, now);
|
|
if (daemon->resolv_files && (daemon->options & OPT_NO_POLL))
|
|
{
|
|
reload_servers(daemon->resolv_files->name, daemon);
|
|
check_servers(daemon);
|
|
}
|
|
sighup = 0;
|
|
}
|
|
|
|
if (sigusr1)
|
|
{
|
|
dump_cache(daemon);
|
|
sigusr1 = 0;
|
|
}
|
|
|
|
if (sigalarm)
|
|
{
|
|
if (daemon->dhcp)
|
|
{
|
|
lease_update_file(1, now);
|
|
#ifdef HAVE_BROKEN_RTC
|
|
alarm(daemon->min_leasetime);
|
|
#endif
|
|
}
|
|
sigalarm = 0;
|
|
}
|
|
|
|
FD_ZERO(&rset);
|
|
FD_ZERO(&wset);
|
|
FD_ZERO(&eset);
|
|
|
|
if (!first_loop)
|
|
{
|
|
int maxfd = set_dns_listeners(daemon, &rset, -1);
|
|
#ifdef HAVE_DBUS
|
|
maxfd = set_dbus_listeners(daemon, maxfd, &rset, &wset, &eset);
|
|
#endif
|
|
if (daemon->dhcp)
|
|
{
|
|
FD_SET(daemon->dhcpfd, &rset);
|
|
if (daemon->dhcpfd > maxfd)
|
|
maxfd = daemon->dhcpfd;
|
|
}
|
|
|
|
/* Whilst polling for the dbus, wake every quarter second */
|
|
#ifdef HAVE_PSELECT
|
|
{
|
|
struct timespec *tp = NULL;
|
|
#ifdef HAVE_DBUS
|
|
struct timespec t;
|
|
if ((daemon->options & OPT_DBUS) && !daemon->dbus)
|
|
{
|
|
tp = &t;
|
|
tp->tv_sec = 0;
|
|
tp->tv_nsec = 250000000;
|
|
}
|
|
#endif
|
|
if (pselect(maxfd+1, &rset, &wset, &eset, tp, &sigmask) < 0)
|
|
{
|
|
/* otherwise undefined after error */
|
|
FD_ZERO(&rset); FD_ZERO(&wset); FD_ZERO(&eset);
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
sigset_t save_mask;
|
|
struct timeval *tp = NULL;
|
|
#ifdef HAVE_DBUS
|
|
struct timeval t;
|
|
if ((daemon->options & OPT_DBUS) && !daemon->dbus)
|
|
{
|
|
tp = &t;
|
|
tp->tv_sec = 0;
|
|
tp->tv_usec = 250000;
|
|
}
|
|
#endif
|
|
sigprocmask(SIG_SETMASK, &sigmask, &save_mask);
|
|
if (select(maxfd+1, &rset, &wset, &eset, tp) < 0)
|
|
{
|
|
/* otherwise undefined after error */
|
|
FD_ZERO(&rset); FD_ZERO(&wset); FD_ZERO(&eset);
|
|
}
|
|
sigprocmask(SIG_SETMASK, &save_mask, NULL);
|
|
|
|
}
|
|
#endif
|
|
}
|
|
|
|
first_loop = 0;
|
|
now = dnsmasq_time(daemon->uptime_fd);
|
|
|
|
/* Check for changes to resolv files once per second max. */
|
|
/* Don't go silent for long periods if the clock goes backwards. */
|
|
if (last == 0 || difftime(now, last) > 1.0 || difftime(now, last) < 1.0)
|
|
{
|
|
last = now;
|
|
|
|
#ifdef HAVE_ISC_READER
|
|
if (daemon->lease_file && !daemon->dhcp)
|
|
load_dhcp(daemon, now);
|
|
#endif
|
|
|
|
if (!(daemon->options & OPT_NO_POLL))
|
|
{
|
|
struct resolvc *res = daemon->resolv_files, *latest = NULL;
|
|
struct stat statbuf;
|
|
time_t last_change = 0;
|
|
/* There may be more than one possible file.
|
|
Go through and find the one which changed _last_.
|
|
Warn of any which can't be read. */
|
|
while (res)
|
|
{
|
|
if (stat(res->name, &statbuf) == -1)
|
|
{
|
|
if (!res->logged)
|
|
syslog(LOG_WARNING, "failed to access %s: %m", res->name);
|
|
res->logged = 1;
|
|
}
|
|
else
|
|
{
|
|
res->logged = 0;
|
|
if (statbuf.st_mtime != res->mtime)
|
|
{
|
|
res->mtime = statbuf.st_mtime;
|
|
if (difftime(res->mtime, last_change) > 0.0)
|
|
{
|
|
last_change = res->mtime;
|
|
latest = res;
|
|
}
|
|
}
|
|
}
|
|
res = res->next;
|
|
}
|
|
|
|
if (latest)
|
|
{
|
|
reload_servers(latest->name, daemon);
|
|
check_servers(daemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_DBUS
|
|
/* if we didn't create a DBus connection, retry now. */
|
|
if ((daemon->options & OPT_DBUS) && !daemon->dbus)
|
|
{
|
|
char *err;
|
|
if ((err = dbus_init(daemon)))
|
|
syslog(LOG_WARNING, "DBus error: %s", err);
|
|
if (daemon->dbus)
|
|
syslog(LOG_INFO, "connected to system DBus");
|
|
}
|
|
check_dbus_listeners(daemon, &rset, &wset, &eset);
|
|
#endif
|
|
|
|
check_dns_listeners(daemon, &rset, now);
|
|
|
|
if (daemon->dhcp && FD_ISSET(daemon->dhcpfd, &rset))
|
|
dhcp_packet(daemon, now);
|
|
}
|
|
|
|
syslog(LOG_INFO, "exiting on receipt of SIGTERM");
|
|
|
|
if (daemon->dhcp)
|
|
{
|
|
#ifdef HAVE_BROKEN_RTC
|
|
lease_update_file(1, now);
|
|
#endif
|
|
close(daemon->lease_fd);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sig_handler(int sig)
|
|
{
|
|
if (sig == SIGTERM)
|
|
sigterm = 1;
|
|
else if (sig == SIGHUP)
|
|
sighup = 1;
|
|
else if (sig == SIGUSR1)
|
|
sigusr1 = 1;
|
|
else if (sig == SIGALRM)
|
|
{
|
|
/* alarm is used to kill children after a fixed time. */
|
|
if (in_child)
|
|
exit(0);
|
|
else
|
|
sigalarm = 1;
|
|
}
|
|
else if (sig == SIGCHLD)
|
|
{
|
|
/* See Stevens 5.10 */
|
|
|
|
while (waitpid(-1, NULL, WNOHANG) > 0)
|
|
num_kids--;
|
|
}
|
|
}
|
|
|
|
|
|
void clear_cache_and_reload(struct daemon *daemon, time_t now)
|
|
{
|
|
cache_reload(daemon->options, daemon->namebuff, daemon->domain_suffix, daemon->addn_hosts);
|
|
if (daemon->dhcp)
|
|
{
|
|
if (daemon->options & OPT_ETHERS)
|
|
dhcp_read_ethers(daemon);
|
|
dhcp_update_configs(daemon->dhcp_conf);
|
|
lease_update_from_configs(daemon->dhcp_conf, daemon->domain_suffix);
|
|
lease_update_file(0, now);
|
|
lease_update_dns(daemon);
|
|
}
|
|
}
|
|
|
|
static int set_dns_listeners(struct daemon *daemon, fd_set *set, int maxfd)
|
|
{
|
|
struct serverfd *serverfdp;
|
|
struct listener *listener;
|
|
|
|
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
|
|
{
|
|
FD_SET(serverfdp->fd, set);
|
|
if (serverfdp->fd > maxfd)
|
|
maxfd = serverfdp->fd;
|
|
}
|
|
|
|
for (listener = daemon->listeners; listener; listener = listener->next)
|
|
{
|
|
FD_SET(listener->fd, set);
|
|
if (listener->fd > maxfd)
|
|
maxfd = listener->fd;
|
|
FD_SET(listener->tcpfd, set);
|
|
if (listener->tcpfd > maxfd)
|
|
maxfd = listener->tcpfd;
|
|
}
|
|
|
|
return maxfd;
|
|
}
|
|
|
|
static void check_dns_listeners(struct daemon *daemon, fd_set *set, time_t now)
|
|
{
|
|
struct serverfd *serverfdp;
|
|
struct listener *listener;
|
|
|
|
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
|
|
if (FD_ISSET(serverfdp->fd, set))
|
|
reply_query(serverfdp, daemon, now);
|
|
|
|
for (listener = daemon->listeners; listener; listener = listener->next)
|
|
{
|
|
if (FD_ISSET(listener->fd, set))
|
|
receive_query(listener, daemon, now);
|
|
|
|
if (FD_ISSET(listener->tcpfd, set))
|
|
{
|
|
int confd;
|
|
struct in_addr netmask, dst_addr_4;
|
|
|
|
while((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR);
|
|
|
|
if (confd != -1)
|
|
{
|
|
union mysockaddr tcp_addr;
|
|
socklen_t tcp_len = sizeof(union mysockaddr);
|
|
|
|
/* Check for allowed interfaces when binding the wildcard address:
|
|
we do this by looking for an interface with the same address as
|
|
the local address of the TCP connection, then looking to see if that's
|
|
an allowed interface. As a side effect, we get the netmask of the
|
|
interface too, for localisation. */
|
|
|
|
if ((num_kids >= MAX_PROCS) ||
|
|
(!(daemon->options & OPT_NOWILD) &&
|
|
(getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1 ||
|
|
!enumerate_interfaces(daemon, NULL, &tcp_addr, &netmask))))
|
|
close(confd);
|
|
#ifndef NO_FORK
|
|
else if (!(daemon->options & OPT_DEBUG) && fork())
|
|
{
|
|
num_kids++;
|
|
close(confd);
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
unsigned char *buff;
|
|
struct server *s;
|
|
int flags;
|
|
|
|
/* Arrange for SIGALARM after CHILD_LIFETIME seconds to
|
|
terminate the process. */
|
|
if (!(daemon->options & OPT_DEBUG))
|
|
{
|
|
sigset_t mask;
|
|
sigemptyset(&mask);
|
|
sigaddset(&mask, SIGALRM);
|
|
sigprocmask(SIG_UNBLOCK, &mask, NULL);
|
|
alarm(CHILD_LIFETIME);
|
|
in_child = 1;
|
|
}
|
|
|
|
/* start with no upstream connections. */
|
|
for (s = daemon->servers; s; s = s->next)
|
|
s->tcpfd = -1;
|
|
|
|
/* The connected socket inherits non-blocking
|
|
attribute from the listening socket.
|
|
Reset that here. */
|
|
if ((flags = fcntl(confd, F_GETFL, 0)) != -1)
|
|
fcntl(confd, F_SETFL, flags & ~O_NONBLOCK);
|
|
|
|
if (listener->family == AF_INET)
|
|
{
|
|
if (daemon->options & OPT_NOWILD)
|
|
{
|
|
netmask = listener->iface->netmask;
|
|
dst_addr_4 = listener->iface->addr.in.sin_addr;
|
|
}
|
|
else
|
|
/* netmask already set by enumerate_interfaces */
|
|
dst_addr_4 = tcp_addr.in.sin_addr;
|
|
}
|
|
else
|
|
dst_addr_4.s_addr = 0;
|
|
|
|
buff = tcp_request(daemon, confd, now, dst_addr_4, netmask);
|
|
|
|
if (!(daemon->options & OPT_DEBUG))
|
|
exit(0);
|
|
|
|
close(confd);
|
|
if (buff)
|
|
free(buff);
|
|
for (s = daemon->servers; s; s = s->next)
|
|
if (s->tcpfd != -1)
|
|
close(s->tcpfd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int icmp_ping(struct daemon *daemon, struct in_addr addr)
|
|
{
|
|
/* Try and get an ICMP echo from a machine.
|
|
Note that we can't create the raw socket each time
|
|
we do this, since that needs root. Therefore the socket has to hang
|
|
around all the time. Since most of the time we won't read the
|
|
socket, it will accumulate buffers full of ICMP messages,
|
|
wasting memory. To avoid that we set the receive buffer
|
|
length to zero except when we're actively pinging. */
|
|
|
|
/* Note that whilst in the three second wait, we check for
|
|
(and service) events on the DNS sockets, (so doing that
|
|
better not use any resources our caller has in use...)
|
|
but we remain deaf to signals or further DHCP packets. */
|
|
|
|
struct sockaddr_in saddr;
|
|
struct {
|
|
struct ip ip;
|
|
struct icmp icmp;
|
|
} packet;
|
|
unsigned short id = rand16();
|
|
unsigned int i, j;
|
|
int opt = 2000, gotreply = 0;
|
|
time_t start, now;
|
|
|
|
saddr.sin_family = AF_INET;
|
|
saddr.sin_port = 0;
|
|
saddr.sin_addr = addr;
|
|
#ifdef HAVE_SOCKADDR_SA_LEN
|
|
saddr.sin_len = sizeof(struct sockaddr_in);
|
|
#endif
|
|
|
|
memset(&packet.icmp, 0, sizeof(packet.icmp));
|
|
packet.icmp.icmp_type = ICMP_ECHO;
|
|
packet.icmp.icmp_id = id;
|
|
for (j = 0, i = 0; i < sizeof(struct icmp) / 2; i++)
|
|
j += ((u16 *)&packet.icmp)[i];
|
|
while (j>>16)
|
|
j = (j & 0xffff) + (j >> 16);
|
|
packet.icmp.icmp_cksum = (j == 0xffff) ? j : ~j;
|
|
|
|
setsockopt(daemon->dhcp_icmp_fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt));
|
|
|
|
while (sendto(daemon->dhcp_icmp_fd, (char *)&packet.icmp, sizeof(struct icmp), 0,
|
|
(struct sockaddr *)&saddr, sizeof(saddr)) == -1 &&
|
|
retry_send());
|
|
|
|
for (now = start = dnsmasq_time(daemon->uptime_fd); difftime(now, start) < 3.0;)
|
|
{
|
|
struct timeval tv;
|
|
fd_set rset;
|
|
struct sockaddr_in faddr;
|
|
int maxfd;
|
|
socklen_t len = sizeof(faddr);
|
|
|
|
tv.tv_usec = 250000;
|
|
tv.tv_sec = 0;
|
|
|
|
FD_ZERO(&rset);
|
|
FD_SET(daemon->dhcp_icmp_fd, &rset);
|
|
maxfd = set_dns_listeners(daemon, &rset, daemon->dhcp_icmp_fd);
|
|
|
|
if (select(maxfd+1, &rset, NULL, NULL, &tv) < 0)
|
|
FD_ZERO(&rset);
|
|
|
|
now = dnsmasq_time(daemon->uptime_fd);
|
|
check_dns_listeners(daemon, &rset, now);
|
|
|
|
if (FD_ISSET(daemon->dhcp_icmp_fd, &rset) &&
|
|
recvfrom(daemon->dhcp_icmp_fd, &packet, sizeof(packet), 0,
|
|
(struct sockaddr *)&faddr, &len) == sizeof(packet) &&
|
|
saddr.sin_addr.s_addr == faddr.sin_addr.s_addr &&
|
|
packet.icmp.icmp_type == ICMP_ECHOREPLY &&
|
|
packet.icmp.icmp_seq == 0 &&
|
|
packet.icmp.icmp_id == id)
|
|
{
|
|
gotreply = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
opt = 1;
|
|
setsockopt(daemon->dhcp_icmp_fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt));
|
|
|
|
return gotreply;
|
|
}
|
|
|
|
|