diff options
author | Simon Kelley <simon@thekelleys.org.uk> | 2012-02-24 16:06:20 +0000 |
---|---|---|
committer | Simon Kelley <simon@thekelleys.org.uk> | 2012-02-24 16:06:20 +0000 |
commit | c5ad4e799861dc1477c25a74e800cfd4a2916bc5 (patch) | |
tree | 659d9f43f283838084264edaad101347b6239a7b | |
parent | 270dc2e199acbdde05229d596161bbd78d499509 (diff) | |
download | dnsmasq-2.60test14.tar.gz |
Router Advertisementv2.60test14
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | bld/Android.mk | 3 | ||||
-rw-r--r-- | man/dnsmasq.8 | 30 | ||||
-rw-r--r-- | src/bpf.c | 3 | ||||
-rw-r--r-- | src/dhcp-common.c | 6 | ||||
-rw-r--r-- | src/dhcp6.c | 20 | ||||
-rw-r--r-- | src/dnsmasq.c | 44 | ||||
-rw-r--r-- | src/dnsmasq.h | 32 | ||||
-rw-r--r-- | src/lease.c | 10 | ||||
-rw-r--r-- | src/netlink.c | 13 | ||||
-rw-r--r-- | src/option.c | 22 | ||||
-rw-r--r-- | src/outpacket.c | 108 | ||||
-rw-r--r-- | src/radv.c | 433 | ||||
-rw-r--r-- | src/radv_protocol.h | 44 | ||||
-rw-r--r-- | src/rfc3315.c | 105 |
15 files changed, 758 insertions, 121 deletions
@@ -47,9 +47,11 @@ VERSION= -DVERSION='\"`../bld/get-version`\"' 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) diff --git a/bld/Android.mk b/bld/Android.mk index 413b325..a4bc610 100644 --- a/bld/Android.mk +++ b/bld/Android.mk @@ -7,7 +7,8 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \ 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 diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 3c761df..8707360 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -529,14 +529,18 @@ The optional 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 @@ -546,6 +550,14 @@ and .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 @@ -1234,6 +1246,20 @@ added into dnsmasq's DNS view. This flag suppresses that behaviour, 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 @@ -199,7 +199,8 @@ int iface_enumerate(int family, void *parm, int (*callback)()) { /* 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 diff --git a/src/dhcp-common.c b/src/dhcp-common.c index 8b8bb67..cf2c875 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -53,14 +53,14 @@ ssize_t recv_dhcp_packet(int fd, struct msghdr *msg) /* 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; } } diff --git a/src/dhcp6.c b/src/dhcp6.c index 6c20a0f..3b4da60 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -35,7 +35,7 @@ static int join_multicast(struct in6_addr *local, int prefix, 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) { @@ -101,6 +101,13 @@ static int join_multicast(struct in6_addr *local, int prefix, 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; @@ -120,7 +127,6 @@ static int join_multicast(struct in6_addr *local, int prefix, 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) @@ -151,7 +157,7 @@ void dhcp6_packet(time_t now) 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; @@ -215,8 +221,8 @@ void dhcp6_packet(time_t now) 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(); @@ -455,13 +461,15 @@ void make_duid(time_t now) 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; diff --git a/src/dnsmasq.c b/src/dnsmasq.c index b01c3fc..d24300c 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -157,8 +157,14 @@ int main (int argc, char **argv) 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 @@ -495,6 +501,9 @@ int main (int argc, char **argv) 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) @@ -525,6 +534,8 @@ int main (int argc, char **argv) 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"), @@ -535,7 +546,10 @@ int main (int argc, char **argv) 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 @@ -652,7 +666,13 @@ int main (int argc, char **argv) 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 @@ -756,6 +776,9 @@ int main (int argc, char **argv) { if (FD_ISSET(daemon->dhcp6fd, &rset)) dhcp6_packet(now); + + if (daemon->icmp6fd != -1 && FD_ISSET(daemon->icmp6fd, &rset)) + icmp6_packet(); } #endif @@ -1372,7 +1395,15 @@ int icmp_ping(struct in_addr addr) 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); @@ -1384,6 +1415,11 @@ int icmp_ping(struct in_addr addr) 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 diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 575d9a7..a30fb17 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -60,6 +60,7 @@ typedef unsigned long long u64; #include "dhcp_protocol.h" #ifdef HAVE_DHCP6 #include "dhcp6_protocol.h" +#include "radv_protocol.h" #endif #define gettext_noop(S) (S) @@ -215,7 +216,8 @@ struct event_desc { #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. */ @@ -605,6 +607,7 @@ struct dhcp_context { struct in6_addr start6, end6; /* range of available addresses */ struct in6_addr local6; int prefix; + time_t ra_time; #endif int flags; char *interface; @@ -616,6 +619,8 @@ struct dhcp_context { #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; @@ -694,7 +699,7 @@ extern struct daemon { 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; @@ -754,7 +759,7 @@ extern struct daemon { 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 */ @@ -1054,3 +1059,24 @@ int match_bytes(struct dhcp_opt *o, unsigned char *p, int len); 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 diff --git a/src/lease.c b/src/lease.c index 57fde0f..49ce016 100644 --- a/src/lease.c +++ b/src/lease.c @@ -313,7 +313,15 @@ void lease_update_file(time_t now) } /* 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; diff --git a/src/netlink.c b/src/netlink.c index a7d8fb2..820b0a8 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -284,7 +284,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) } 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 @@ -341,6 +341,17 @@ static void nl_routechange(struct nlmsghdr *h) /* 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) diff --git a/src/option.c b/src/option.c index 96277e5..c4a62c4 100644 --- a/src/option.c +++ b/src/option.c @@ -114,6 +114,7 @@ struct myoption { #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[] = @@ -233,6 +234,7 @@ static const struct myoption 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 } }; @@ -359,6 +361,7 @@ static struct { { 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 } }; @@ -2330,17 +2333,32 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) #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) { diff --git a/src/outpacket.c b/src/outpacket.c new file mode 100644 index 0000000..2fa1e26 --- /dev/null +++ b/src/outpacket.c @@ -0,0 +1,108 @@ +/* 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 diff --git a/src/radv.c b/src/radv.c new file mode 100644 index 0000000..f451295 --- /dev/null +++ b/src/radv.c @@ -0,0 +1,433 @@ +/* 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 diff --git a/src/radv_protocol.h b/src/radv_protocol.h new file mode 100644 index 0000000..8f3244f --- /dev/null +++ b/src/radv_protocol.h @@ -0,0 +1,44 @@ +/* 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 + + + diff --git a/src/rfc3315.c b/src/rfc3315.c index ed76b24..4eb6d2f 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -19,17 +19,6 @@ #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, @@ -55,10 +44,10 @@ size_t dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name 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; } @@ -1274,12 +1263,14 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh 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); } @@ -1422,80 +1413,4 @@ static unsigned int opt6_uint(unsigned char *opt, int offset, int size) 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 |