Add --bind-dynamic

This commit is contained in:
Simon Kelley
2012-06-20 11:23:38 +01:00
parent 4ce4f3779b
commit 54dd393f39
8 changed files with 131 additions and 77 deletions

View File

@@ -7,6 +7,10 @@ version 2.63
Allow more than one --tfp-root flag. The per-interface Allow more than one --tfp-root flag. The per-interface
stuff is pointless without that. stuff is pointless without that.
Add --bind-dynamic. A hybrid mode between the default and
--bind-interfaces which copes with dynamically created
interfaces.
version 2.62 version 2.62
Update German translation. Thanks to Conrad Kostecki. Update German translation. Thanks to Conrad Kostecki.

View File

@@ -204,6 +204,17 @@ running another nameserver (or another instance of dnsmasq) on the
same machine. Setting this option also enables multiple instances of same machine. Setting this option also enables multiple instances of
dnsmasq which provide DHCP service to run in the same machine. dnsmasq which provide DHCP service to run in the same machine.
.TP .TP
.B --bind-dynamic
Enable a network mode which is a hybrid between
.B --bind-interfaces
and the default. Dnsmasq binds the address of indivdual interfaces,
allowing multiple dnsmasq instances, but if new interfaces or
addresses appear, it automatically listens on those (subject to any
access-control configuration). This makes dynamically created
interfaces work in the same way as the default. Implementing this
option requires non-standard networking APIs and it is only availble
under Linux.
.TP
.B \-y, --localise-queries .B \-y, --localise-queries
Return answers to DNS queries from /etc/hosts which depend on the interface over which the query was Return answers to DNS queries from /etc/hosts which depend on the interface over which the query was
received. If a name in /etc/hosts has more than one address associated with received. If a name in /etc/hosts has more than one address associated with

View File

@@ -66,7 +66,7 @@ static int make_fd(int port)
/* When bind-interfaces is set, there might be more than one dnmsasq /* When bind-interfaces is set, there might be more than one dnmsasq
instance binding port 67. That's OK if they serve different networks. instance binding port 67. That's OK if they serve different networks.
Need to set REUSEADDR to make this posible, or REUSEPORT on *BSD. */ Need to set REUSEADDR to make this posible, or REUSEPORT on *BSD. */
if (option_bool(OPT_NOWILD)) if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
{ {
#ifdef SO_REUSEPORT #ifdef SO_REUSEPORT
int rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt)); int rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt));

View File

@@ -114,6 +114,9 @@ int main (int argc, char **argv)
set_option_bool(OPT_NOWILD); set_option_bool(OPT_NOWILD);
} }
# endif # endif
if (option_bool(OPT_CLEVERBIND))
die(_("--bind-dynamic not available on this platform"), NULL, EC_BADCONF);
#endif #endif
#ifndef HAVE_TFTP #ifndef HAVE_TFTP
@@ -185,6 +188,9 @@ int main (int argc, char **argv)
#ifdef HAVE_LINUX_NETWORK #ifdef HAVE_LINUX_NETWORK
/* After lease_init */ /* After lease_init */
netlink_init(); netlink_init();
if (option_bool(OPT_NOWILD) && option_bool(OPT_CLEVERBIND))
die(_("cannot set --bind-interfaces and --bind-dynamic"), NULL, EC_BADCONF);
#endif #endif
#ifdef HAVE_DHCP6 #ifdef HAVE_DHCP6
@@ -202,13 +208,14 @@ int main (int argc, char **argv)
if (!enumerate_interfaces()) if (!enumerate_interfaces())
die(_("failed to find list of interfaces: %s"), NULL, EC_MISC); die(_("failed to find list of interfaces: %s"), NULL, EC_MISC);
if (option_bool(OPT_NOWILD)) if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
{ {
create_bound_listeners(1); create_bound_listeners(1);
for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next) if (!option_bool(OPT_CLEVERBIND))
if (if_tmp->name && !if_tmp->used) for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
die(_("unknown interface %s"), if_tmp->name, EC_BADNET); if (if_tmp->name && !if_tmp->used)
die(_("unknown interface %s"), if_tmp->name, EC_BADNET);
#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP) #if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP)
/* after enumerate_interfaces() */ /* after enumerate_interfaces() */
@@ -425,8 +432,9 @@ int main (int argc, char **argv)
#if defined(HAVE_LINUX_NETWORK) #if defined(HAVE_LINUX_NETWORK)
/* On linux, we keep CAP_NETADMIN (for ARP-injection) and /* On linux, we keep CAP_NETADMIN (for ARP-injection) and
CAP_NET_RAW (for icmp) if we're doing dhcp. If we have yet to bind CAP_NET_RAW (for icmp) if we're doing dhcp. If we have yet to bind
ports because of DAD, we need CAP_NET_BIND_SERVICE too. */ ports because of DAD, or we're doing it dynamically,
if (is_dad_listeners()) we need CAP_NET_BIND_SERVICE too. */
if (is_dad_listeners() || option_bool(OPT_CLEVERBIND))
data->effective = data->permitted = data->inheritable = data->effective = data->permitted = data->inheritable =
(1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) |
(1 << CAP_SETUID) | (1 << CAP_NET_BIND_SERVICE); (1 << CAP_SETUID) | (1 << CAP_NET_BIND_SERVICE);
@@ -474,7 +482,7 @@ int main (int argc, char **argv)
} }
#ifdef HAVE_LINUX_NETWORK #ifdef HAVE_LINUX_NETWORK
if (is_dad_listeners()) if (is_dad_listeners() || option_bool(OPT_CLEVERBIND))
data->effective = data->permitted = data->effective = data->permitted =
(1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_NET_BIND_SERVICE); (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_NET_BIND_SERVICE);
else else
@@ -1352,7 +1360,7 @@ static void check_dns_listeners(fd_set *set, time_t now)
getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1) getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
continue; continue;
if (option_bool(OPT_NOWILD)) if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
iface = listener->iface; /* May be NULL */ iface = listener->iface; /* May be NULL */
else else
{ {
@@ -1369,7 +1377,7 @@ static void check_dns_listeners(fd_set *set, time_t now)
break; break;
} }
if (!iface && !option_bool(OPT_NOWILD)) if (!iface && !(option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)))
{ {
shutdown(confd, SHUT_RDWR); shutdown(confd, SHUT_RDWR);
close(confd); close(confd);

View File

@@ -219,7 +219,8 @@ struct event_desc {
#define OPT_FQDN_UPDATE 36 #define OPT_FQDN_UPDATE 36
#define OPT_RA 37 #define OPT_RA 37
#define OPT_TFTP_LC 38 #define OPT_TFTP_LC 38
#define OPT_LAST 39 #define OPT_CLEVERBIND 39
#define OPT_LAST 40
/* extra flags for my_syslog, we use a couple of facilities since they are known /* extra flags for my_syslog, we use a couple of facilities since they are known
not to occupy the same bits as priorities, no matter how syslog.h is set up. */ not to occupy the same bits as priorities, no matter how syslog.h is set up. */

View File

@@ -436,7 +436,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (udpfd != -1) if (udpfd != -1)
{ {
plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl); plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl);
send_from(udpfd, option_bool(OPT_NOWILD), (char *)header, plen, udpaddr, dst_addr, dst_iface); send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface);
} }
return 0; return 0;
@@ -624,7 +624,7 @@ void reply_query(int fd, int family, time_t now)
{ {
header->id = htons(forward->orig_id); header->id = htons(forward->orig_id);
header->hb4 |= HB4_RA; /* recursion if available */ header->hb4 |= HB4_RA; /* recursion if available */
send_from(forward->fd, option_bool(OPT_NOWILD), daemon->packet, nn, send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
&forward->source, &forward->dest, forward->iface); &forward->source, &forward->dest, forward->iface);
} }
free_frec(forward); /* cancel */ free_frec(forward); /* cancel */
@@ -819,8 +819,8 @@ void receive_query(struct listener *listen, time_t now)
dst_addr_4, netmask, now); dst_addr_4, netmask, now);
if (m >= 1) if (m >= 1)
{ {
send_from(listen->fd, option_bool(OPT_NOWILD), (char *)header, send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
m, &source_addr, &dst_addr, if_index); (char *)header, m, &source_addr, &dst_addr, if_index);
daemon->local_answer++; daemon->local_answer++;
} }
else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index, else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index,

View File

@@ -38,7 +38,7 @@
static struct iovec iov; static struct iovec iov;
static u32 netlink_pid; static u32 netlink_pid;
static void nl_err(struct nlmsghdr *h); static int nl_async(struct nlmsghdr *h);
static void nl_routechange(struct nlmsghdr *h); static void nl_routechange(struct nlmsghdr *h);
void netlink_init(void) void netlink_init(void)
@@ -49,10 +49,13 @@ void netlink_init(void)
addr.nl_family = AF_NETLINK; addr.nl_family = AF_NETLINK;
addr.nl_pad = 0; addr.nl_pad = 0;
addr.nl_pid = 0; /* autobind */ addr.nl_pid = 0; /* autobind */
#ifdef HAVE_IPV6
addr.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE;
#else
addr.nl_groups = RTMGRP_IPV4_ROUTE; addr.nl_groups = RTMGRP_IPV4_ROUTE;
if (option_bool(OPT_CLEVERBIND))
addr.nl_groups |= RTMGRP_IPV4_IFADDR;
#ifdef HAVE_IPV6
addr.nl_groups |= RTMGRP_IPV6_ROUTE;
if (daemon->ra_contexts || option_bool(OPT_CLEVERBIND))
addr.nl_groups |= RTMGRP_IPV6_IFADDR;
#endif #endif
/* May not be able to have permission to set multicast groups don't die in that case */ /* May not be able to have permission to set multicast groups don't die in that case */
@@ -136,7 +139,7 @@ int iface_enumerate(int family, void *parm, int (*callback)())
struct nlmsghdr *h; struct nlmsghdr *h;
ssize_t len; ssize_t len;
static unsigned int seq = 0; static unsigned int seq = 0;
int callback_ok = 1; int callback_ok = 1, newaddr = 0;
struct { struct {
struct nlmsghdr nlh; struct nlmsghdr nlh;
@@ -182,12 +185,24 @@ int iface_enumerate(int family, void *parm, int (*callback)())
} }
for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len))
if (h->nlmsg_seq != seq || h->nlmsg_pid != netlink_pid) if (h->nlmsg_seq != seq || h->nlmsg_pid != netlink_pid || h->nlmsg_type == NLMSG_ERROR)
nl_routechange(h); /* May be multicast arriving async */ {
else if (h->nlmsg_type == NLMSG_ERROR) /* May be multicast arriving async */
nl_err(h); if (nl_async(h))
newaddr = 1;
}
else if (h->nlmsg_type == NLMSG_DONE) else if (h->nlmsg_type == NLMSG_DONE)
return callback_ok; {
/* handle async new interface address arrivals, these have to be done
after we complete as we're not re-entrant */
if (newaddr)
{
enumerate_interfaces();
create_bound_listeners(0);
}
return callback_ok;
}
else if (h->nlmsg_type == RTM_NEWADDR && family != AF_UNSPEC && family != AF_LOCAL) else if (h->nlmsg_type == RTM_NEWADDR && family != AF_UNSPEC && family != AF_LOCAL)
{ {
struct ifaddrmsg *ifa = NLMSG_DATA(h); struct ifaddrmsg *ifa = NLMSG_DATA(h);
@@ -295,7 +310,7 @@ void netlink_multicast(void)
{ {
ssize_t len; ssize_t len;
struct nlmsghdr *h; struct nlmsghdr *h;
int flags; int flags, newaddr = 0;
/* don't risk blocking reading netlink messages here. */ /* don't risk blocking reading netlink messages here. */
if ((flags = fcntl(daemon->netlinkfd, F_GETFL)) == -1 || if ((flags = fcntl(daemon->netlinkfd, F_GETFL)) == -1 ||
@@ -303,71 +318,83 @@ void netlink_multicast(void)
return; return;
if ((len = netlink_recv()) != -1) if ((len = netlink_recv()) != -1)
{ for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len))
for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) if (nl_async(h))
if (h->nlmsg_type == NLMSG_ERROR) newaddr = 1;
nl_err(h);
else
nl_routechange(h);
}
/* restore non-blocking status */
fcntl(daemon->netlinkfd, F_SETFL, flags);
}
static void nl_err(struct nlmsghdr *h)
{
struct nlmsgerr *err = NLMSG_DATA(h);
if (err->error != 0) /* restore non-blocking status */
my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error))); fcntl(daemon->netlinkfd, F_SETFL, flags);
if (newaddr)
{
enumerate_interfaces();
create_bound_listeners(0);
}
} }
static int nl_async(struct nlmsghdr *h)
{
if (h->nlmsg_type == NLMSG_ERROR)
{
struct nlmsgerr *err = NLMSG_DATA(h);
my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error)));
return 0;
}
else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE)
{
nl_routechange(h);
return 0;
}
else if (h->nlmsg_type == RTM_NEWADDR)
{
#ifdef HAVE_DHCP6
/* force RAs to sync new network and pick up new interfaces. */
if (daemon->ra_contexts)
{
schedule_subnet_map();
ra_start_unsolicted(dnsmasq_time(), NULL);
/* cause lease_update_file to run after we return, in case we were called from
iface_enumerate and can't re-enter it now */
send_alarm(0, 0);
}
return !!option_bool(OPT_CLEVERBIND); /* clever bind mode - rescan */
}
#endif
return 0;
}
/* We arrange to receive netlink multicast messages whenever the network route is added. /* We arrange to receive netlink multicast messages whenever the network route is added.
If this happens and we still have a DNS packet in the buffer, we re-send it. If this happens and we still have a DNS packet in the buffer, we re-send it.
This helps on DoD links, where frequently the packet which triggers dialling is This helps on DoD links, where frequently the packet which triggers dialling is
a DNS query, which then gets lost. By re-sending, we can avoid the lookup a DNS query, which then gets lost. By re-sending, we can avoid the lookup
failing. Note that we only accept these messages from the kernel (pid == 0) */ failing. */
static void nl_routechange(struct nlmsghdr *h) static void nl_routechange(struct nlmsghdr *h)
{ {
if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE) struct rtmsg *rtm = NLMSG_DATA(h);
int fd;
if (rtm->rtm_type != RTN_UNICAST || rtm->rtm_scope != RT_SCOPE_LINK)
return;
/* Force re-reading resolv file right now, for luck. */
daemon->last_resolv = 0;
if (daemon->srv_save)
{ {
struct rtmsg *rtm = NLMSG_DATA(h); if (daemon->srv_save->sfd)
int fd; fd = daemon->srv_save->sfd->fd;
else if (daemon->rfd_save && daemon->rfd_save->refcount != 0)
if (rtm->rtm_type != RTN_UNICAST || rtm->rtm_scope != RT_SCOPE_LINK) fd = daemon->rfd_save->fd;
else
return; return;
/* Force re-reading resolv file right now, for luck. */
daemon->last_resolv = 0;
#ifdef HAVE_DHCP6 while(sendto(fd, daemon->packet, daemon->packet_len, 0,
/* force RAs to sync new network and pick up new interfaces. */ &daemon->srv_save->addr.sa, sa_len(&daemon->srv_save->addr)) == -1 && retry_send());
if (daemon->ra_contexts)
{
schedule_subnet_map();
ra_start_unsolicted(dnsmasq_time(), NULL);
/* cause lease_update_file to run after we return, in case we were called from
iface_enumerate and can't re-enter it now */
send_alarm(0, 0);
}
#endif
if (daemon->srv_save)
{
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(sendto(fd, daemon->packet, daemon->packet_len, 0,
&daemon->srv_save->addr.sa, sa_len(&daemon->srv_save->addr)) == -1 && retry_send());
}
} }
} }
#endif #endif

View File

@@ -119,6 +119,7 @@ struct myoption {
#define LOPT_HOST_REC 308 #define LOPT_HOST_REC 308
#define LOPT_TFTP_LC 309 #define LOPT_TFTP_LC 309
#define LOPT_RR 310 #define LOPT_RR 310
#define LOPT_CLVERBIND 311
#ifdef HAVE_GETOPT_LONG #ifdef HAVE_GETOPT_LONG
static const struct option opts[] = static const struct option opts[] =
@@ -243,6 +244,7 @@ static const struct myoption opts[] =
{ "enable-ra", 0, 0, LOPT_RA }, { "enable-ra", 0, 0, LOPT_RA },
{ "dhcp-duid", 1, 0, LOPT_DUID }, { "dhcp-duid", 1, 0, LOPT_DUID },
{ "host-record", 1, 0, LOPT_HOST_REC }, { "host-record", 1, 0, LOPT_HOST_REC },
{ "bind-dynamic", 0, 0, LOPT_CLVERBIND },
{ NULL, 0, 0, 0 } { NULL, 0, 0, 0 }
}; };
@@ -374,6 +376,7 @@ static struct {
{ LOPT_DUID, ARG_ONE, "<enterprise>,<duid>", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL }, { LOPT_DUID, ARG_ONE, "<enterprise>,<duid>", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL },
{ LOPT_HOST_REC, ARG_DUP, "<name>,<address>", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, { LOPT_HOST_REC, ARG_DUP, "<name>,<address>", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL },
{ LOPT_RR, ARG_DUP, "<name>,<RR-number>,[<data>]", gettext_noop("Specify arbitrary DNS resource record"), NULL }, { LOPT_RR, ARG_DUP, "<name>,<RR-number>,[<data>]", gettext_noop("Specify arbitrary DNS resource record"), NULL },
{ LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL},
{ 0, 0, NULL, NULL, NULL } { 0, 0, NULL, NULL, NULL }
}; };