OBJS = cache.o rfc1035.o util.o option.o forward.o network.o \
dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
- helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o dhcp-common.o
+ helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
+ dhcp-common.o outpacket.o radv.o
-HDRS = dnsmasq.h config.h dhcp_protocol.h dhcp6_protocol.h dns_protocol.h
+HDRS = dnsmasq.h config.h dhcp_protocol.h dhcp6_protocol.h \
+ dns_protocol.h radv_protocol.h
all : $(BUILDDIR)
forward.c helper.c lease.c log.c \
netlink.c network.c option.c rfc1035.c \
rfc2131.c tftp.c util.c conntrack.c \
- dhcp6.c rfc3315.c dhcp-common.c
+ dhcp6.c rfc3315.c dhcp-common.c outpacket.c \
+ radv.c
LOCAL_MODULE := dnsmasq
sets an alphanumeric label which marks this network so that
dhcp options may be specified on a per-network basis.
When it is prefixed with 'tag:' instead, then its meaning changes from setting
-a tag to matching it. Only one tag may be set, but more than one tag may be matched.
+a tag to matching it. Only one tag may be set, but more than one tag
+may be matched.
+
The end address may be replaced by the keyword
.B static
which tells dnsmasq to enable DHCP for the network specified, but not
to dynamically allocate IP addresses: only hosts which have static
addresses given via
.B dhcp-host
-or from /etc/ethers will be served. The end address may be replaced by
+or from /etc/ethers will be served.
+
+The end address may be replaced by
the keyword
.B proxy
in which case dnsmasq will provide proxy-DHCP on the specified
.B pxe-service
for details, applies to IPv4 only.)
+The end address may be replaced by
+the keyword
+.B ra-only
+which tells dnsmasq to offer Router Advertisement only on this subnet,
+and not DHCP. This applies to IPv6 only, see
+.B enable-ra
+for details.
+
The interface:<interface name> section is not normally used. See the
NOTES section for details of this.
.TP
this is useful, for instance, to allow Windows clients to update
Active Directory servers. See RFC 4702 for details.
.TP
+.B --enable-ra
+Enable dnsmasq's IPv6 Router Advertisement feature. DHCPv6 doesn't
+handle complete network configuration in the same way as DHCPv4. Router
+discovery and (possibly) prefix discovery for autonomous address
+creation are handled by a different protocol. When DHCP is in use,
+only a subset of this is needed, and dnsmasq can handle it, using
+existing DHCP configuration to provide most data. When RA is enabled,
+dnsmasq will advertise a prefix for each dhcp-range, with default
+router and recursive DNS server as the relevant link-local address on
+the machine running dnsmasq. The "managed address" bits are set,
+except for a dhcp-range which is marked as "ra-only". In which case RA
+is provided by no DHCPv6 service and the managed address bits are
+cleared.
+.TP
.B --enable-tftp[=<interface>]
Enable the TFTP server function. This is deliberately limited to that
needed to net-boot a client. Only reading is allowed; the tsize and
{
/* Assume ethernet again here */
struct sockaddr_dl *sdl = (struct sockaddr_dl *)&ifr->ifr_addr;
- if (sdl->sdl_alen != 0 && !((*callback)(ARPHRD_ETHER, LLADDR(sdl), sdl->sdl_alen, parm)))
+ if (sdl->sdl_alen != 0 && !((*callback)((int)if_nametoindex(ifr->ifr_name),
+ ARPHRD_ETHER, LLADDR(sdl), sdl->sdl_alen, parm)))
goto err;
}
#endif
/* Very new Linux kernels return the actual size needed,
older ones always return truncated size */
- if ((size_t)sz == daemon->dhcp_packet.iov_len)
+ if ((size_t)sz == msg->msg_iov->iov_len)
{
- if (!expand_buf(&daemon->dhcp_packet, sz + 100))
+ if (!expand_buf(msg->msg_iov, sz + 100))
return -1;
}
else
{
- expand_buf(&daemon->dhcp_packet, sz);
+ expand_buf(msg->msg_iov, sz);
break;
}
}
static int complete_context6(struct in6_addr *local, int prefix,
int scope, int if_index, int dad, void *vparam);
-static int make_duid1(unsigned int type, char *mac, size_t maclen, void *parm);
+static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm);
void dhcp6_init(void)
{
if (if_index == listenp->fd_or_iface)
return 1;
+ mreq.ipv6mr_interface = if_index;
+ inet_pton(AF_INET6, ALL_ROUTERS, &mreq.ipv6mr_multiaddr);
+
+ if (daemon->icmp6fd != -1 &&
+ setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
+ return 0;
+
if (!indextoname(fd, if_index, ifrn_name))
return 0;
if (!context)
return 1;
- mreq.ipv6mr_interface = if_index;
inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &mreq.ipv6mr_multiaddr);
if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
struct cmsghdr align; /* this ensures alignment */
char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
} control_u;
- union mysockaddr from;
+ struct sockaddr_in6 from;
struct all_addr dest;
ssize_t sz;
struct ifreq ifr;
lease_prune(NULL, now); /* lose any expired leases */
- msg.msg_iov = &daemon->dhcp_packet;
- sz = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback, sz, IN6_IS_ADDR_MULTICAST(&from.in6.sin6_addr), now);
+ sz = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback,
+ sz, IN6_IS_ADDR_MULTICAST(&from.sin6_addr), now);
lease_update_file(now);
lease_update_dns();
die("Cannot create DHCPv6 server DUID: %s", NULL, EC_MISC);
}
-static int make_duid1(unsigned int type, char *mac, size_t maclen, void *parm)
+static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm)
{
/* create DUID as specified in RFC3315. We use the MAC of the
first interface we find that isn't loopback or P-to-P */
unsigned char *p;
+ (void)index;
+
daemon->duid = p = safe_malloc(maclen + 8);
daemon->duid_len = maclen + 8;
if (daemon->dhcp)
dhcp_init();
#ifdef HAVE_DHCP6
+ daemon->icmp6fd = -1;
if (daemon->dhcp6)
- dhcp6_init();
+ {
+ /* ra_init before dhcp6_init, so dhcp6_init can setup multicast listening */
+ if (option_bool(OPT_RA))
+ ra_init(now);
+ dhcp6_init();
+ }
#endif
}
#endif
if (daemon->max_logs != 0)
my_syslog(LOG_INFO, _("asynchronous logging enabled, queue limit is %d messages"), daemon->max_logs);
+
+ if (option_bool(OPT_RA))
+ my_syslog(MS_DHCP | LOG_INFO, _("IPv6 router advertisement enabled"));
#ifdef HAVE_DHCP
if (daemon->dhcp || daemon->dhcp6)
my_syslog(MS_DHCP | LOG_INFO,
(dhcp_tmp->flags & CONTEXT_STATIC) ?
_("DHCP, static leases only on %.0s%s, lease time %s") :
+ (dhcp_tmp->flags & CONTEXT_RA_ONLY) ?
+ _("router advertisement only on %.0s%s, lifetime %s") :
(dhcp_tmp->flags & CONTEXT_PROXY) ?
_("DHCP, proxy on subnet %.0s%s%.0s") :
_("DHCP, IP range %s -- %s, lease time %s"),
if (family == AF_INET)
{
family = AF_INET6;
- dhcp_tmp = daemon->dhcp6;
+ if (daemon->ra_contexts)
+ dhcp_tmp = daemon->ra_contexts;
+ else
+ dhcp_tmp = daemon->dhcp6;
goto again;
}
#endif
if (daemon->dhcp6)
{
FD_SET(daemon->dhcp6fd, &rset);
- bump_maxfd(daemon->dhcp6fd, &maxfd);
+ bump_maxfd(daemon->dhcp6fd, &maxfd);
+
+ if (daemon->icmp6fd != -1)
+ {
+ FD_SET(daemon->icmp6fd, &rset);
+ bump_maxfd(daemon->icmp6fd, &maxfd);
+ }
}
#endif
{
if (FD_ISSET(daemon->dhcp6fd, &rset))
dhcp6_packet(now);
+
+ if (daemon->icmp6fd != -1 && FD_ISSET(daemon->icmp6fd, &rset))
+ icmp6_packet();
}
#endif
FD_SET(fd, &rset);
set_dns_listeners(now, &rset, &maxfd);
set_log_writer(&wset, &maxfd);
-
+
+#ifdef HAVE_DHCP6
+ if (daemon->icmp6fd != -1)
+ {
+ FD_SET(daemon->icmp6fd, &rset);
+ bump_maxfd(daemon->icmp6fd, &maxfd);
+ }
+#endif
+
if (select(maxfd+1, &rset, &wset, NULL, &tv) < 0)
{
FD_ZERO(&rset);
check_log_writer(&wset);
check_dns_listeners(&rset, now);
+#ifdef HAVE_DHCP6
+ if (daemon->icmp6fd != -1 && FD_ISSET(daemon->icmp6fd, &rset))
+ icmp6_packet();
+#endif
+
#ifdef HAVE_TFTP
check_tftp_listeners(&rset, now);
#endif
#include "dhcp_protocol.h"
#ifdef HAVE_DHCP6
#include "dhcp6_protocol.h"
+#include "radv_protocol.h"
#endif
#define gettext_noop(S) (S)
#define OPT_CONSEC_ADDR 34
#define OPT_CONNTRACK 35
#define OPT_FQDN_UPDATE 36
-#define OPT_LAST 37
+#define OPT_RA 37
+#define OPT_LAST 38
/* 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. */
struct in6_addr start6, end6; /* range of available addresses */
struct in6_addr local6;
int prefix;
+ time_t ra_time;
#endif
int flags;
char *interface;
#define CONTEXT_NETMASK 2
#define CONTEXT_BRDCAST 4
#define CONTEXT_PROXY 8
+#define CONTEXT_RA_ONLY 16
+#define CONTEXT_RA_DONE 32
struct ping_result {
struct in_addr addr;
int port, query_port, min_port;
unsigned long local_ttl, neg_ttl, max_ttl;
struct hostsfile *addn_hosts;
- struct dhcp_context *dhcp, *dhcp6;
+ struct dhcp_context *dhcp, *dhcp6, *ra_contexts;
struct dhcp_config *dhcp_conf;
struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6;
struct dhcp_vendor *dhcp_vendors;
int duid_len;
unsigned char *duid;
struct iovec outpacket;
- int dhcp6fd;
+ int dhcp6fd, icmp6fd;
#endif
/* DBus stuff */
/* void * here to avoid depending on dbus headers outside dbus.c */
void dhcp_update_configs(struct dhcp_config *configs);
void check_dhcp_hosts(int fatal);
#endif
+
+/* outpacket.c */
+#ifdef HAVE_DHCP6
+void end_opt6(int container);
+int save_counter(int newval);
+void *expand(size_t headroom);
+int new_opt6(int opt);
+void *put_opt6(void *data, size_t len);
+void put_opt6_long(unsigned int val);
+void put_opt6_short(unsigned int val);
+void put_opt6_char(unsigned int val);
+void put_opt6_string(char *s);
+#endif
+
+/* radv.c */
+#ifdef HAVE_DHCP6
+void ra_init(time_t now);
+void icmp6_packet(void);
+time_t periodic_ra(time_t now);
+void ra_start_unsolicted(time_t now);
+#endif
}
/* Set alarm for when the first lease expires + slop. */
- for (next_event = 0, lease = leases; lease; lease = lease->next)
+ next_event = 0;
+
+#ifdef HAVE_DHCP6
+ /* do timed RAs and determine when the next is */
+ if (option_bool(OPT_RA))
+ next_event = periodic_ra(now);
+#endif
+
+ for (lease = leases; lease; lease = lease->next)
if (lease->expires != 0 &&
(next_event == 0 || difftime(next_event, lease->expires + 10) > 0.0))
next_event = lease->expires + 10;
}
if (mac && callback_ok && !((link->ifi_flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) &&
- !((*callback)((unsigned int)link->ifi_type, mac, maclen, parm)))
+ !((*callback)((int)link->ifi_index, (unsigned int)link->ifi_type, mac, maclen, parm)))
callback_ok = 0;
}
#endif
/* Force re-reading resolv file right now, for luck. */
daemon->last_resolv = 0;
+#ifdef HAVE_DHCP6
+ /* force RAs to sync new network and pick up new interfaces. */
+ if (option_bool(OPT_RA))
+ {
+ ra_start_unsolicted(dnsmasq_time());
+ /* cause lease_update_file to run after we return, in case we were called from
+ iface_enumerate and can't re-enter it now */
+ alarm(1);
+ }
+#endif
+
if (daemon->srv_save)
{
if (daemon->srv_save->sfd)
#define LOPT_CONNTRACK 303
#define LOPT_FQDN 304
#define LOPT_LUASCRIPT 305
+#define LOPT_RA 306
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
{ "conntrack", 0, 0, LOPT_CONNTRACK },
{ "dhcp-client-update", 0, 0, LOPT_FQDN },
{ "dhcp-luascript", 1, 0, LOPT_LUASCRIPT },
+ { "enable-ra", 0, 0, LOPT_RA },
{ NULL, 0, 0, 0 }
};
{ LOPT_INCR_ADDR, OPT_CONSEC_ADDR, NULL, gettext_noop("Attempt to allocate sequential IP addresses to DHCP clients."), NULL },
{ LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL },
{ LOPT_FQDN, OPT_FQDN_UPDATE, NULL, gettext_noop("Allow DHCP clients to do their own DDNS updates."), NULL },
+ { LOPT_RA, OPT_RA, NULL, gettext_noop("Send router-advertisements for interfaces doing DHCPv6"), NULL },
{ 0, 0, NULL, NULL, NULL }
};
#ifdef HAVE_DHCP6
else if (inet_pton(AF_INET6, a[0], &new->start6))
{
- new->next = daemon->dhcp6;
new->prefix = 64; /* default */
- daemon->dhcp6 = new;
+
if (strcmp(a[1], "static") == 0)
{
memcpy(&new->end6, &new->start6, IN6ADDRSZ);
new->flags |= CONTEXT_STATIC;
}
+ else if (strcmp(a[1], "ra-only") == 0)
+ {
+ memcpy(&new->end6, &new->start6, IN6ADDRSZ);
+ new->flags |= CONTEXT_RA_ONLY;
+ }
else if (!inet_pton(AF_INET6, a[1], &new->end6))
option = '?';
+ if (new->flags & CONTEXT_RA_ONLY)
+ {
+ new->next = daemon->ra_contexts;
+ daemon->ra_contexts = new;
+ }
+ else
+ {
+ new->next = daemon->dhcp6;
+ daemon->dhcp6 = new;
+ }
+
/* bare integer < 128 is prefix value */
if (option != '?' && k >= 3)
{
--- /dev/null
+/* dnsmasq is Copyright (c) 2000-2012 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_DHCP6
+
+static size_t outpacket_counter;
+
+void end_opt6(int container)
+{
+ void *p = daemon->outpacket.iov_base + container + 2;
+ u16 len = outpacket_counter - container - 4 ;
+
+ PUTSHORT(len, p);
+}
+
+int save_counter(int newval)
+{
+ int ret = outpacket_counter;
+ if (newval != -1)
+ outpacket_counter = newval;
+
+ return ret;
+}
+
+void *expand(size_t headroom)
+{
+ void *ret;
+
+ if (expand_buf(&daemon->outpacket, outpacket_counter + headroom))
+ {
+ ret = daemon->outpacket.iov_base + outpacket_counter;
+ outpacket_counter += headroom;
+ return ret;
+ }
+
+ return NULL;
+}
+
+int new_opt6(int opt)
+{
+ int ret = outpacket_counter;
+ void *p;
+
+ if ((p = expand(4)))
+ {
+ PUTSHORT(opt, p);
+ PUTSHORT(0, p);
+ }
+
+ return ret;
+}
+
+void *put_opt6(void *data, size_t len)
+{
+ void *p;
+
+ if ((p = expand(len)))
+ memcpy(p, data, len);
+
+ return p;
+}
+
+void put_opt6_long(unsigned int val)
+{
+ void *p;
+
+ if ((p = expand(4)))
+ PUTLONG(val, p);
+}
+
+void put_opt6_short(unsigned int val)
+{
+ void *p;
+
+ if ((p = expand(2)))
+ PUTSHORT(val, p);
+}
+
+void put_opt6_char(unsigned int val)
+{
+ unsigned char *p;
+
+ if ((p = expand(1)))
+ *p = val;
+}
+
+void put_opt6_string(char *s)
+{
+ put_opt6(s, strlen(s));
+}
+
+#endif
--- /dev/null
+/* dnsmasq is Copyright (c) 2000-2012 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/>.
+*/
+
+
+/* NB. This code may be called during a DHCPv4 transaction which is in ping-wait
+ It therefore cannot use any DHCP buffer resources except outpacket, which is
+ not used by DHCPv4 code. */
+
+#include "dnsmasq.h"
+#include <netinet/icmp6.h>
+
+#ifdef HAVE_DHCP6
+
+struct ra_param {
+ int ind, managed, found_context, first;
+ char *if_name;
+ struct in6_addr link_local;
+};
+
+struct search_param {
+ time_t now; int iface;
+};
+
+static void send_ra(int iface, char *iface_name, struct in6_addr *dest);
+static int add_prefixes(struct in6_addr *local, int prefix,
+ int scope, int if_index, int dad, void *vparam);
+static int iface_search(struct in6_addr *local, int prefix,
+ int scope, int if_index, int dad, void *vparam);
+static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm);
+
+static int unicast_hop_limit, multicast_hop_limit;
+static time_t ra_short_period_start;
+
+void ra_init(time_t now)
+{
+ struct dhcp_context *context;
+ struct icmp6_filter filter;
+ int fd;
+#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
+ int class = IPTOS_CLASS_CS6;
+#endif
+ int val = 255; /* radvd uses this value */
+ size_t len1 = sizeof(int);
+ size_t len2 = sizeof(int);
+
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+
+ if ((fd = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1 ||
+ getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &unicast_hop_limit, &len1) ||
+ getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &multicast_hop_limit, &len2) ||
+#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
+ setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &class, sizeof(class)) == -1 ||
+#endif
+ !fix_fd(fd) ||
+ !set_ipv6pktinfo(fd) ||
+ setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)) ||
+ setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)) ||
+ setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) == -1)
+ die (_("cannot create ICMPv6 socket: %s"), NULL, EC_BADNET);
+
+ daemon->icmp6fd = fd;
+
+ /* link the DHCP6 contexts to the ra-only ones so we can traverse them all
+ from ->ra_contexts, but only the non-ra-onlies from ->dhcp6 */
+ if (!daemon->ra_contexts)
+ daemon->ra_contexts = daemon->dhcp6;
+ else
+ {
+ for (context = daemon->ra_contexts; context->next; context = context->next);
+ context->next = daemon->dhcp6;
+ }
+
+ if (!daemon->dhcp6)
+ die(_("cannot do router advertisement unless DHCPv6 is enabled"), NULL, EC_BADCONF);
+
+ ra_start_unsolicted(now);
+}
+
+void ra_start_unsolicted(time_t now)
+{
+ struct dhcp_context *context;
+
+ /* init timers so that we do ra's for all soon. some ra_times will end up zeroed
+ if it's not appropriate to advertise those contexts.
+ This gets re-called on a netlink route-change to re-do the advertisement
+ and pick up new interfaces */
+
+ /* range 0 - 5 */
+ for (context = daemon->ra_contexts; context; context = context->next)
+ context->ra_time = now + (rand16()/13000);
+
+ /* re-do ras after a short time, in case the first gets lost.
+ This is reset once that's done. */
+ ra_short_period_start = now;
+}
+
+void icmp6_packet(void)
+{
+ char interface[IF_NAMESIZE+1];
+ ssize_t sz;
+ int if_index = 0;
+ struct cmsghdr *cmptr;
+ struct msghdr msg;
+ union {
+ struct cmsghdr align; /* this ensures alignment */
+ char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+ } control_u;
+ struct sockaddr_in6 from;
+ unsigned char *p;
+ char *mac = "";
+ struct iname *tmp;
+ struct dhcp_context *context;
+
+ /* Note: use outpacket for input buffer */
+ msg.msg_control = control_u.control6;
+ msg.msg_controllen = sizeof(control_u);
+ msg.msg_flags = 0;
+ msg.msg_name = &from;
+ msg.msg_namelen = sizeof(from);
+ msg.msg_iov = &daemon->outpacket;
+ msg.msg_iovlen = 1;
+
+ if ((sz = recv_dhcp_packet(daemon->icmp6fd, &msg)) == -1 || sz < 8)
+ return;
+
+ 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;
+ }
+
+ if (!indextoname(daemon->icmp6fd, if_index, interface))
+ return;
+
+ if (!iface_check(AF_LOCAL, NULL, interface))
+ return;
+
+ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+ if (tmp->name && (strcmp(tmp->name, interface) == 0))
+ return;
+
+ /* weird libvirt-inspired access control */
+ for (context = daemon->dhcp6; context; context = context->next)
+ if (!context->interface || strcmp(context->interface, interface) == 0)
+ break;
+
+ if (!context)
+ return;
+
+ p = (unsigned char *)daemon->outpacket.iov_base;
+
+ if (p[0] != ICMP6_ROUTER_SOLICIT || p[1] != 0)
+ return;
+
+ /* look for link-layer address option for logging */
+ if (sz >= 16 && p[8] == ICMP6_OPT_SOURCE_MAC && (p[9] * 8) + 8 <= sz)
+ {
+ print_mac(daemon->namebuff, &p[10], (p[9] * 8) - 2);
+ mac = daemon->namebuff;
+ }
+
+ my_syslog(MS_DHCP | LOG_INFO, "RTR-SOLICIT(%s) %s", interface, mac);
+
+ send_ra(if_index, interface, &from.sin6_addr);
+}
+
+static void send_ra(int iface, char *iface_name, struct in6_addr *dest)
+{
+ struct ra_packet *ra;
+ struct ra_param parm;
+ struct ifreq ifr;
+ struct sockaddr_in6 addr;
+ struct dhcp_context *context;
+
+ save_counter(0);
+ ra = expand(sizeof(struct ra_packet));
+
+ ra->type = ICMP6_ROUTER_ADVERT;
+ ra->code = 0;
+ ra->hop_limit = dest ? unicast_hop_limit : multicast_hop_limit;
+ ra->flags = 0;
+ ra->lifetime = htons(1800); /* AdvDefaultLifetime*/
+ ra->reachable_time = 0;
+ ra->retrans_time = 0;
+
+ parm.ind = iface;
+ parm.managed = 0;
+ parm.found_context = 0;
+ parm.if_name = iface_name;
+ parm.first = 1;
+
+ for (context = daemon->ra_contexts; context; context = context->next)
+ context->flags &= ~CONTEXT_RA_DONE;
+
+ if (!iface_enumerate(AF_INET6, &parm, add_prefixes) ||
+ !parm.found_context)
+ return;
+
+ strncpy(ifr.ifr_name, iface_name, IF_NAMESIZE);
+
+ if (ioctl(daemon->icmp6fd, SIOCGIFMTU, &ifr) != -1)
+ {
+ put_opt6_char(ICMP6_OPT_MTU);
+ put_opt6_char(1);
+ put_opt6_short(0);
+ put_opt6_long(ifr.ifr_mtu);
+ }
+
+ iface_enumerate(AF_LOCAL, &iface, add_lla);
+
+ /* RDNSS, RFC 6106 */
+ put_opt6_char(ICMP6_OPT_RDNSS);
+ put_opt6_char(3);
+ put_opt6_short(0);
+ put_opt6_long(1800); /* lifetime - twice RA retransmit */
+ put_opt6(&parm.link_local, IN6ADDRSZ);
+
+
+ /* set managed bits unless we're providing only RA on this link */
+ if (parm.managed)
+ ra->flags = 0xc0;
+
+ /* decide where we're sending */
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ addr.sin6_port = htons(IPPROTO_ICMPV6);
+ if (dest)
+ {
+ memcpy(&addr.sin6_addr, dest, sizeof(struct in6_addr));
+ if (IN6_IS_ADDR_LINKLOCAL(dest) ||
+ IN6_IS_ADDR_MC_LINKLOCAL(dest))
+ addr.sin6_scope_id = iface;
+ }
+ else
+ inet_pton(AF_INET6, ALL_HOSTS, &addr.sin6_addr);
+
+ send_from(daemon->icmp6fd, 0, daemon->outpacket.iov_base, save_counter(0),
+ (union mysockaddr *)&addr, (struct all_addr *)&parm.link_local, iface);
+
+}
+
+static int add_prefixes(struct in6_addr *local, int prefix,
+ int scope, int if_index, int dad, void *vparam)
+{
+ struct dhcp_context *context, *tmp;
+ struct ra_param *param = vparam;
+ struct prefix_opt *opt;
+
+ (void)scope; /* warning */
+ (void)dad;
+
+ if (if_index == param->ind)
+ {
+ if (IN6_IS_ADDR_LINKLOCAL(local))
+ param->link_local = *local;
+ else if (!IN6_IS_ADDR_LOOPBACK(local) &&
+ !IN6_IS_ADDR_LINKLOCAL(local) &&
+ !IN6_IS_ADDR_MULTICAST(local))
+ {
+ for (context = daemon->ra_contexts; context; context = context->next)
+ if (prefix == context->prefix &&
+ is_same_net6(local, &context->start6, prefix) &&
+ is_same_net6(local, &context->end6, prefix))
+ {
+ if (!(context->flags & CONTEXT_RA_ONLY))
+ param->managed = 1;
+
+ if (context->flags & CONTEXT_RA_DONE)
+ continue;
+
+ /* subsequent prefixes on the same interface don't need timers */
+ if (!param->first)
+ context->ra_time = 0;
+ param->first = 0;
+ param->found_context = 1;
+ context->flags |= CONTEXT_RA_DONE;
+
+ /* mark this subnet and duplicates: as done. */
+ for (tmp = context->next; tmp; tmp = tmp->next)
+ if (tmp->prefix == prefix &&
+ is_same_net6(local, &tmp->start6, prefix) &&
+ is_same_net6(local, &tmp->end6, prefix))
+ {
+ tmp->flags |= CONTEXT_RA_DONE;
+ context->ra_time = 0;
+ }
+
+ if ((opt = expand(sizeof(struct prefix_opt))))
+ {
+ u64 addrpart = addr6part(&context->start6);
+ u64 mask = (prefix == 64) ? (u64)-1LL : (1LLU << (128 - prefix)) - 1LLU;
+ unsigned int time = context->lease_time;
+
+ /* lifetimes must be min 2 hrs, by RFC 2462 */
+ if (time < 7200)
+ time = 7200;
+
+ opt->type = ICMP6_OPT_PREFIX;
+ opt->len = 4;
+ opt->prefix_len = prefix;
+ /* autonomous only is we're not doing dhcp */
+ opt->flags = (context->flags & CONTEXT_RA_ONLY) ? 0xc0 : 0x00;
+ opt->valid_lifetime = opt->preferred_lifetime = htonl(time);
+ opt->reserved = 0;
+
+ opt->prefix = context->start6;
+ setaddr6part(&opt->prefix, addrpart & ~mask);
+
+ inet_ntop(AF_INET6, &opt->prefix, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s", param->if_name, daemon->addrbuff);
+ }
+ }
+ }
+ }
+ return 1;
+}
+
+static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm)
+{
+ (void)type;
+
+ if (index == *((int *)parm))
+ {
+ /* size is in units of 8 octets and includes type and length (2 bytes)
+ add 7 to round up */
+ int len = (maclen + 9) >> 3;
+ unsigned char *p = expand(len << 3);
+ memset(p, 0, len << 3);
+ *p++ = ICMP6_OPT_SOURCE_MAC;
+ *p++ = len;
+ memcpy(p, mac, maclen);
+
+ return 0;
+ }
+
+ return 1;
+}
+
+time_t periodic_ra(time_t now)
+{
+ struct search_param param;
+ struct dhcp_context *context;
+ time_t next_event;
+ char interface[IF_NAMESIZE+1];
+
+ param.now = now;
+
+ while (1)
+ {
+ /* find overdue events, and time of first future event */
+ for (next_event = 0, context = daemon->ra_contexts; context; context = context->next)
+ if (context->ra_time != 0)
+ {
+ if (difftime(context->ra_time, now) < 0.0)
+ break; /* overdue */
+
+ if (next_event == 0 || difftime(next_event, context->ra_time + 2) > 0.0)
+ next_event = context->ra_time + 2;
+ }
+
+ /* none overdue */
+ if (!context)
+ break;
+
+ /* There's a context overdue, but we can't find an interface
+ associated with it, because it's for a subnet we dont
+ have an interface on. Probably we're doing DHCP on
+ a remote subnet via a relay. Zero the timer, since we won't
+ ever be able to send ra's and satistfy it. */
+ if (iface_enumerate(AF_INET6, ¶m, iface_search))
+ context->ra_time = 0;
+ else if (indextoname(daemon->icmp6fd, param.iface, interface))
+ send_ra(param.iface, interface, NULL);
+ }
+
+ return next_event;
+}
+
+static int iface_search(struct in6_addr *local, int prefix,
+ int scope, int if_index, int dad, void *vparam)
+{
+ struct search_param *param = vparam;
+ struct dhcp_context *context, *tmp;
+
+ (void)scope;
+ (void)dad;
+
+ for (context = daemon->ra_contexts; context; context = context->next)
+ if (prefix == context->prefix &&
+ is_same_net6(local, &context->start6, prefix) &&
+ is_same_net6(local, &context->end6, prefix))
+ if (context->ra_time != 0 && difftime(context->ra_time, param->now) < 0.0)
+ {
+ /* found an interface that's overdue for RA determine new
+ timeout value and zap other contexts on the same interface
+ so they don't timeout independently .*/
+ param->iface = if_index;
+
+ if (difftime(param->now, ra_short_period_start) < 60.0)
+ /* range 5 - 20 */
+ context->ra_time = param->now + 5 + (rand16()/4400);
+ else
+ /* range 450 - 600 */
+ context->ra_time = param->now + 450 + (rand16()/440);
+
+ return 0; /* found, abort */
+ }
+
+ return 1; /* keep searching */
+}
+
+#endif
--- /dev/null
+/* dnsmasq is Copyright (c) 2000-2012 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/>.
+*/
+
+#define ALL_HOSTS "FF02::1"
+#define ALL_ROUTERS "FF02::2"
+
+struct ra_packet {
+ u8 type, code;
+ u16 checksum;
+ u8 hop_limit, flags;
+ u16 lifetime;
+ u32 reachable_time;
+ u32 retrans_time;
+};
+
+struct prefix_opt {
+ u8 type, len, prefix_len, flags;
+ u32 valid_lifetime, preferred_lifetime, reserved;
+ struct in6_addr prefix;
+};
+
+#define ICMP6_ROUTER_SOLICIT 133
+#define ICMP6_ROUTER_ADVERT 134
+
+#define ICMP6_OPT_SOURCE_MAC 1
+#define ICMP6_OPT_PREFIX 3
+#define ICMP6_OPT_MTU 5
+#define ICMP6_OPT_RDNSS 25
+
+
+
#ifdef HAVE_DHCP6
-static size_t outpacket_counter;
-
-static void end_opt6(int container);
-static int save_counter(int newval);
-static void *expand(size_t headroom);
-static int new_opt6(int opt);
-static void *put_opt6(void *data, size_t len);
-static void put_opt6_short(unsigned int val);
-static void put_opt6_long(unsigned int val);
-static void put_opt6_string(char *s);
-
static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid **relay_tagsp, struct dhcp_context *context,
int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz, int is_unicast, time_t now);
static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dhcp_netid *tags, struct dhcp_context *context,
for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
vendor->netid.next = &vendor->netid;
- outpacket_counter = 0;
+ save_counter(0);
if (dhcp6_maybe_relay(NULL, &relay_tags, context, interface, iface_name, fallback, daemon->dhcp_packet.iov_base, sz, is_unicast, now))
- return outpacket_counter;
+ return save_counter(0);
return 0;
}
len += strlen(send_domain) + 1;
o = new_opt6(OPTION6_FQDN);
- p = expand(len + 3);
- *(p++) = fqdn_flags;
- p = do_rfc1035_name(p, hostname);
- if (send_domain)
- p = do_rfc1035_name(p, send_domain);
- *p = 0;
+ if ((p = expand(len + 3)))
+ {
+ *(p++) = fqdn_flags;
+ p = do_rfc1035_name(p, hostname);
+ if (send_domain)
+ p = do_rfc1035_name(p, send_domain);
+ *p = 0;
+ }
end_opt6(o);
}
return ret;
}
-static void end_opt6(int container)
-{
- void *p = daemon->outpacket.iov_base + container + 2;
- u16 len = outpacket_counter - container - 4 ;
-
- PUTSHORT(len, p);
-}
-
-static int save_counter(int newval)
-{
- int ret = outpacket_counter;
- if (newval != -1)
- outpacket_counter = newval;
-
- return ret;
-}
-
-static void *expand(size_t headroom)
-{
- void *ret;
-
- if (expand_buf(&daemon->outpacket, outpacket_counter + headroom))
- {
- ret = daemon->outpacket.iov_base + outpacket_counter;
- outpacket_counter += headroom;
- return ret;
- }
-
- return NULL;
-}
-
-static int new_opt6(int opt)
-{
- int ret = outpacket_counter;
- void *p;
-
- if ((p = expand(4)))
- {
- PUTSHORT(opt, p);
- PUTSHORT(0, p);
- }
-
- return ret;
-}
-
-static void *put_opt6(void *data, size_t len)
-{
- void *p;
-
- if ((p = expand(len)))
- memcpy(p, data, len);
-
- return p;
-}
-
-static void put_opt6_long(unsigned int val)
-{
- void *p;
-
- if ((p = expand(4)))
- PUTLONG(val, p);
-}
-
-static void put_opt6_short(unsigned int val)
-{
- void *p;
-
- if ((p = expand(2)))
- PUTSHORT(val, p);
-}
-
-static void put_opt6_string(char *s)
-{
- put_opt6(s, strlen(s));
-}
-
#endif