diff --git a/dnsmasq.conf.example b/dnsmasq.conf.example index f2ec51a..e293024 100644 --- a/dnsmasq.conf.example +++ b/dnsmasq.conf.example @@ -157,6 +157,13 @@ # an explicit netmask instead. #dhcp-range=192.168.0.0,static +# Enable DHCPv6. Note that the prefix-length does not need to be specified +# and defaults to 64 if missing/ +#dhcp-range=1234::2, 1234::500, 64, 12h + +# Not Router Advertisements, BUT NOT DHCP for this subnet. +#dhcp-range=1234::, ra-only + # Supply parameters for specified hosts using DHCP. There are lots # of valid alternatives, so we will give examples of each. Note that # IP addresses DO NOT have to be in the range given above, they just @@ -219,6 +226,12 @@ # any machine with Ethernet address starting 11:22:33: #dhcp-host=11:22:33:*:*:*,set:red +# Give a fixed IPv6 address and name to client with +# DUID 00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2 +# Note the MAC addresses CANNOT be used to identify DHCPv6 clients. +# Note also the they [] around the IPv6 address are obilgatory. +#dhcp-host=id:00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2, fred, [1234::5] + # Ignore any clients which are not specified in dhcp-host lines # or /etc/ethers. Equivalent to ISC "deny unknown-clients". # This relies on the special "known" tag which is set when @@ -270,6 +283,9 @@ # Set the NTP time server addresses to 192.168.0.4 and 10.10.0.5 #dhcp-option=option:ntp-server,192.168.0.4,10.10.0.5 +# Send DHCPv6 option. Note [] around IPv6 addresses. +dhcp-option=option6:dns-server,[1234::77],[1234::88] + # Set the NTP time server address to be the same machine as # is running dnsmasq #dhcp-option=42,0.0.0.0 diff --git a/src/dhcp-common.c b/src/dhcp-common.c index cf2c875..451a3c1 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -358,4 +358,90 @@ void dhcp_update_configs(struct dhcp_config *configs) } +#ifdef HAVE_DHCP6 +static int join_multicast_worker(struct in6_addr *local, int prefix, + int scope, int if_index, int dad, void *vparam) +{ + char ifrn_name[IFNAMSIZ]; + struct ipv6_mreq mreq; + int fd, i, max = *((int *)vparam); + struct dhcp_context *context; + struct iname *tmp; + + (void)prefix; + (void)scope; + (void)dad; + + /* record which interfaces we join on, so that we do it at most one per + interface, even when they have multiple addresses. Use outpacket + as an array of int, since it's always allocated here and easy + to expand for theoretical vast numbers of interfaces. */ + for (i = 0; i < max; i++) + if (if_index == ((int *)daemon->outpacket.iov_base)[i]) + return 1; + + if ((fd = socket(PF_INET6, SOCK_DGRAM, 0)) == -1) + return 0; + + if (!indextoname(fd, if_index, ifrn_name)) + { + close(fd); + return 0; + } + + close(fd); + + /* Are we doing DHCP on this interface? */ + if (!iface_check(AF_INET6, (struct all_addr *)local, ifrn_name)) + return 1; + + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && (strcmp(tmp->name, ifrn_name) == 0)) + return 1; + + /* weird libvirt-inspired access control */ + for (context = daemon->dhcp6; context; context = context->next) + if (!context->interface || strcmp(context->interface, ifrn_name) == 0) + break; + + if (!context) + return 1; + + mreq.ipv6mr_interface = if_index; + + inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &mreq.ipv6mr_multiaddr); + + if (daemon->dhcp6 && + setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) + return 0; + + inet_pton(AF_INET6, ALL_SERVERS, &mreq.ipv6mr_multiaddr); + + if (daemon->dhcp6 && + setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) + return 0; + + inet_pton(AF_INET6, ALL_ROUTERS, &mreq.ipv6mr_multiaddr); + + if (daemon->ra_contexts && + setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) + return 0; + + expand_buf(&daemon->outpacket, (max+1) * sizeof(int)); + ((int *)daemon->outpacket.iov_base)[max++] = if_index; + + *((int *)vparam) = max; + + return 1; +} + +void join_multicast(void) +{ + int count = 0; + + if (!iface_enumerate(AF_INET6, &count, join_multicast_worker)) + die(_("failed to join DHCPv6 multicast group: %s"), NULL, EC_BADNET); +} +#endif + #endif diff --git a/src/dhcp6.c b/src/dhcp6.c index 4639d54..9f9e161 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -24,14 +24,6 @@ struct iface_param { int ind; }; -struct listen_param { - int fd_or_iface; - struct listen_param *next; -}; - -static int join_multicast(struct in6_addr *local, int prefix, - int scope, int if_index, int dad, void *vparam); - static int complete_context6(struct in6_addr *local, int prefix, int scope, int if_index, int dad, void *vparam); @@ -41,7 +33,6 @@ void dhcp6_init(void) { int fd; struct sockaddr_in6 saddr; - struct listen_param *listenp, listen; #if defined(IP_TOS) && defined(IPTOS_CLASS_CS6) int class = IPTOS_CLASS_CS6; #endif @@ -65,88 +56,9 @@ void dhcp6_init(void) if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in6))) die(_("failed to bind DHCPv6 server socket: %s"), NULL, EC_BADNET); - /* join multicast groups on each interface we're interested in */ - listen.fd_or_iface = fd; - listen.next = NULL; - if (!iface_enumerate(AF_INET6, &listen, join_multicast)) - die(_("failed to join DHCPv6 multicast group: %s"), NULL, EC_BADNET); - for (listenp = listen.next; listenp; ) - { - struct listen_param *tmp = listenp->next; - free(listenp); - listenp = tmp; - } - daemon->dhcp6fd = fd; } -static int join_multicast(struct in6_addr *local, int prefix, - int scope, int if_index, int dad, void *vparam) -{ - char ifrn_name[IFNAMSIZ]; - struct ipv6_mreq mreq; - struct listen_param *listenp, *param = vparam; - int fd = param->fd_or_iface; - struct dhcp_context *context; - struct iname *tmp; - - (void)prefix; - (void)scope; - (void)dad; - - /* record which interfaces we join on, so - that we do it at most one per interface, even when they - have multiple addresses */ - for (listenp = param->next; listenp; listenp = listenp->next) - if (if_index == listenp->fd_or_iface) - return 1; - - if (!indextoname(fd, if_index, ifrn_name)) - return 0; - - /* Are we doing DHCP on this interface? */ - if (!iface_check(AF_INET6, (struct all_addr *)local, ifrn_name)) - return 1; - - for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) - if (tmp->name && (strcmp(tmp->name, ifrn_name) == 0)) - return 1; - - /* weird libvirt-inspired access control */ - for (context = daemon->dhcp6; context; context = context->next) - if (!context->interface || strcmp(context->interface, ifrn_name) == 0) - break; - - 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) - return 0; - - inet_pton(AF_INET6, ALL_SERVERS, &mreq.ipv6mr_multiaddr); - - if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) - return 0; - - 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; - - listenp = whine_malloc(sizeof(struct listen_param)); - listenp->fd_or_iface = if_index; - listenp->next = param->next; - param->next = listenp; - - return 1; -} - - void dhcp6_packet(time_t now) { struct dhcp_context *context; diff --git a/src/dnsmasq.c b/src/dnsmasq.c index cd59dd2..82e4cad 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -154,24 +154,41 @@ int main (int argc, char **argv) before lease_init to allocate buffers it uses.*/ dhcp_common_init(); lease_init(now); + if (daemon->dhcp) dhcp_init(); -#ifdef HAVE_DHCP6 - daemon->icmp6fd = -1; - if (daemon->dhcp6) - { - /* ra_init before dhcp6_init, so dhcp6_init can setup multicast listening */ - if (option_bool(OPT_RA)) - ra_init(now); - dhcp6_init(); - } -#endif } + +# ifdef HAVE_DHCP6 + /* Start RA subsystem if --enable-ra OR dhcp-range=, ra-only */ + if (daemon->ra_contexts || option_bool(OPT_RA)) + { + /* 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 */ + struct dhcp_context *context; + + if (!daemon->ra_contexts) + daemon->ra_contexts = daemon->dhcp6; + else + { + for (context = daemon->ra_contexts; context->next; context = context->next); + context->next = daemon->dhcp6; + } + ra_init(now); + } + + if (daemon->dhcp6) + dhcp6_init(); + + if (daemon->ra_contexts || daemon->dhcp6) + join_multicast(); +# endif + #endif if (!enumerate_interfaces()) die(_("failed to find list of interfaces: %s"), NULL, EC_MISC); - + if (option_bool(OPT_NOWILD)) { create_bound_listeners(1); @@ -211,7 +228,8 @@ int main (int argc, char **argv) #if defined(HAVE_SCRIPT) /* Note getpwnam returns static storage */ - if ((daemon->dhcp || daemon->dhcp6) && daemon->scriptuser && + if ((daemon->dhcp || daemon->dhcp6) && + daemon->scriptuser && (daemon->lease_change_command || daemon->luascript)) { if ((ent_pw = getpwnam(daemon->scriptuser))) @@ -502,7 +520,7 @@ 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)) + if (daemon->ra_contexts) my_syslog(MS_DHCP | LOG_INFO, _("IPv6 router advertisement enabled")); #ifdef HAVE_DHCP @@ -668,7 +686,7 @@ int main (int argc, char **argv) FD_SET(daemon->dhcp6fd, &rset); bump_maxfd(daemon->dhcp6fd, &maxfd); - if (daemon->icmp6fd != -1) + if (daemon->ra_contexts) { FD_SET(daemon->icmp6fd, &rset); bump_maxfd(daemon->icmp6fd, &maxfd); @@ -777,7 +795,7 @@ int main (int argc, char **argv) if (FD_ISSET(daemon->dhcp6fd, &rset)) dhcp6_packet(now); - if (daemon->icmp6fd != -1 && FD_ISSET(daemon->icmp6fd, &rset)) + if (daemon->ra_contexts && FD_ISSET(daemon->icmp6fd, &rset)) icmp6_packet(); } #endif @@ -953,6 +971,16 @@ static void async_event(int pipe, time_t now) lease_prune(NULL, now); lease_update_file(now); } +#ifdef HAVE_DHCP6 + else if (daemon->ra_contexts) + { + /* Not doing DHCP, so no lease system, manage + alarms for ra only */ + time_t next_event = periodic_ra(now); + if (next_event != 0) + alarm((unsigned)difftime(next_event, now)); + } +#endif #endif break; @@ -1117,6 +1145,16 @@ void clear_cache_and_reload(time_t now) lease_update_file(now); lease_update_dns(); } +#ifdef HAVE_DHCP6 + else if (daemon->ra_contexts) + { + /* Not doing DHCP, so no lease system, manage + alarms for ra only */ + time_t next_event = periodic_ra(now); + if (next_event != 0) + alarm((unsigned)difftime(next_event, now)); + } +#endif #endif } @@ -1402,7 +1440,7 @@ int icmp_ping(struct in_addr addr) set_log_writer(&wset, &maxfd); #ifdef HAVE_DHCP6 - if (daemon->icmp6fd != -1) + if (daemon->ra_contexts) { FD_SET(daemon->icmp6fd, &rset); bump_maxfd(daemon->icmp6fd, &maxfd); @@ -1421,7 +1459,7 @@ int icmp_ping(struct in_addr addr) check_dns_listeners(&rset, now); #ifdef HAVE_DHCP6 - if (daemon->icmp6fd != -1 && FD_ISSET(daemon->icmp6fd, &rset)) + if (daemon->ra_contexts && FD_ISSET(daemon->icmp6fd, &rset)) icmp6_packet(); #endif diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 70b5dac..e72e53c 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1059,6 +1059,9 @@ void log_tags(struct dhcp_netid *netid, u32 xid); 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); +# ifdef HAVE_DHCP6 +void join_multicast(void); +# endif #endif /* outpacket.c */ diff --git a/src/lease.c b/src/lease.c index 49ce016..b4a1c30 100644 --- a/src/lease.c +++ b/src/lease.c @@ -317,7 +317,7 @@ void lease_update_file(time_t now) #ifdef HAVE_DHCP6 /* do timed RAs and determine when the next is */ - if (option_bool(OPT_RA)) + if (daemon->ra_contexts) next_event = periodic_ra(now); #endif diff --git a/src/netlink.c b/src/netlink.c index a8cea33..993c1f9 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -343,7 +343,7 @@ static void nl_routechange(struct nlmsghdr *h) #ifdef HAVE_DHCP6 /* force RAs to sync new network and pick up new interfaces. */ - if (option_bool(OPT_RA)) + if (daemon->ra_contexts) { ra_start_unsolicted(dnsmasq_time()); /* cause lease_update_file to run after we return, in case we were called from diff --git a/src/radv.c b/src/radv.c index e4adc8c..d8e522d 100644 --- a/src/radv.c +++ b/src/radv.c @@ -15,9 +15,10 @@ */ -/* NB. This code may be called during a DHCPv4 transaction which is in ping-wait +/* NB. This code may be called during a DHCPv4 or transaction which is in ping-wait It therefore cannot use any DHCP buffer resources except outpacket, which is - not used by DHCPv4 code. */ + not used by DHCPv4 code. This code may also be called when DHCP 4 or 6 isn't + active, so we ensure that outpacket is allocated here too */ #include "dnsmasq.h" @@ -47,7 +48,6 @@ 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) @@ -56,6 +56,9 @@ void ra_init(time_t now) int val = 255; /* radvd uses this value */ size_t len = sizeof(int); + /* ensure this is around even if we're not doing DHCPv6 */ + expand_buf(&daemon->outpacket, sizeof(struct dhcp_packet)); + ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); @@ -74,19 +77,6 @@ void ra_init(time_t now) 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); }