Commit c3a04081 authored by Simon Kelley's avatar Simon Kelley

[fd00::} and [fe80::] special addresses in DHCPv6 options.

parent ae76242f
...@@ -11,6 +11,12 @@ version 2.69 ...@@ -11,6 +11,12 @@ version 2.69
--dhcp-option=option6:23,[::] Thanks to Tsachi Kimeldorfer --dhcp-option=option6:23,[::] Thanks to Tsachi Kimeldorfer
for spotting the problem. for spotting the problem.
Add [fd00::] and [fe80::] as special addresses in DHCPv6
options, analogous to [::]. [fd00::] is replaced with the
actual ULA of the interface on the machine running
dnsmasq, [fe80::] with the link-local address.
Thanks to Tsachi Kimeldorfer for championing this.
version 2.68 version 2.68
Use random addresses for DHCPv6 temporary address Use random addresses for DHCPv6 temporary address
......
...@@ -917,9 +917,11 @@ and to set the time-server address to 192.168.0.4, do ...@@ -917,9 +917,11 @@ and to set the time-server address to 192.168.0.4, do
.B --dhcp-option = 42,192.168.0.4 .B --dhcp-option = 42,192.168.0.4
or or
.B --dhcp-option = option:ntp-server, 192.168.0.4 .B --dhcp-option = option:ntp-server, 192.168.0.4
The special address 0.0.0.0 (or [::] for DHCPv6) is taken to mean "the address of the The special address 0.0.0.0 is taken to mean "the address of the
machine running dnsmasq". Data types allowed are comma separated machine running dnsmasq".
dotted-quad IP addresses, a decimal number, colon-separated hex digits
Data types allowed are comma separated
dotted-quad IPv4 addresses, []-wrapped IPv6 addresses, a decimal number, colon-separated hex digits
and a text string. If the optional tags are given then and a text string. If the optional tags are given then
this option is only sent when all the tags are matched. this option is only sent when all the tags are matched.
...@@ -935,7 +937,9 @@ keyword, followed by the option number or option name. The IPv6 option ...@@ -935,7 +937,9 @@ keyword, followed by the option number or option name. The IPv6 option
name space is disjoint from the IPv4 option name space. IPv6 addresses name space is disjoint from the IPv4 option name space. IPv6 addresses
in options must be bracketed with square brackets, eg. in options must be bracketed with square brackets, eg.
.B --dhcp-option=option6:ntp-server,[1234::56] .B --dhcp-option=option6:ntp-server,[1234::56]
For IPv6, [::] means "the global address of
the machine running dnsmasq", whilst [fd00::] is replaced with the
ULA, if it exists, and [fe80::] with the link-local address.
Be careful: no checking is done that the correct type of data for the Be careful: no checking is done that the correct type of data for the
option number is sent, it is quite possible to option number is sent, it is quite possible to
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
struct iface_param { struct iface_param {
struct dhcp_context *current; struct dhcp_context *current;
struct dhcp_relay *relay; struct dhcp_relay *relay;
struct in6_addr fallback, relay_local; struct in6_addr fallback, relay_local, ll_addr, ula_addr;
int ind, addr_match; int ind, addr_match;
}; };
...@@ -158,6 +158,8 @@ void dhcp6_packet(time_t now) ...@@ -158,6 +158,8 @@ void dhcp6_packet(time_t now)
parm.ind = if_index; parm.ind = if_index;
parm.addr_match = 0; parm.addr_match = 0;
memset(&parm.fallback, 0, IN6ADDRSZ); memset(&parm.fallback, 0, IN6ADDRSZ);
memset(&parm.ll_addr, 0, IN6ADDRSZ);
memset(&parm.ula_addr, 0, IN6ADDRSZ);
for (context = daemon->dhcp6; context; context = context->next) for (context = daemon->dhcp6; context; context = context->next)
if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0) if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0)
...@@ -210,7 +212,7 @@ void dhcp6_packet(time_t now) ...@@ -210,7 +212,7 @@ void dhcp6_packet(time_t now)
lease_prune(NULL, now); /* lose any expired leases */ lease_prune(NULL, now); /* lose any expired leases */
port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback, port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback,
sz, &from.sin6_addr, now); &parm.ll_addr, &parm.ula_addr, sz, &from.sin6_addr, now);
lease_update_file(now); lease_update_file(now);
lease_update_dns(0); lease_update_dns(0);
...@@ -309,6 +311,11 @@ static int complete_context6(struct in6_addr *local, int prefix, ...@@ -309,6 +311,11 @@ static int complete_context6(struct in6_addr *local, int prefix,
if (if_index == param->ind) if (if_index == param->ind)
{ {
if (IN6_IS_ADDR_LINKLOCAL(local))
param->ll_addr = *local;
else if (IN6_IS_ADDR_ULA(local))
param->ula_addr = *local;
if (!IN6_IS_ADDR_LOOPBACK(local) && if (!IN6_IS_ADDR_LOOPBACK(local) &&
!IN6_IS_ADDR_LINKLOCAL(local) && !IN6_IS_ADDR_LINKLOCAL(local) &&
!IN6_IS_ADDR_MULTICAST(local)) !IN6_IS_ADDR_MULTICAST(local))
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include <getopt.h> #include <getopt.h>
#include "config.h" #include "config.h"
#include "ip6addr.h"
typedef unsigned char u8; typedef unsigned char u8;
typedef unsigned short u16; typedef unsigned short u16;
...@@ -1314,7 +1315,8 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, ...@@ -1314,7 +1315,8 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac,
/* rfc3315.c */ /* rfc3315.c */
#ifdef HAVE_DHCP6 #ifdef HAVE_DHCP6
unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name, unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
struct in6_addr *fallback, size_t sz, struct in6_addr *client_addr, time_t now); struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
size_t sz, struct in6_addr *client_addr, time_t now);
void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id); void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id);
unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface); unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface);
......
/* dnsmasq is Copyright (c) 2000-2014 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 IN6_IS_ADDR_ULA(a) \
((((__const uint32_t *) (a))[0] & htonl (0xff000000)) \
== htonl (0xfd000000))
#define IN6_IS_ADDR_ULA_ZERO(a) \
(((__const uint32_t *) (a))[0] == htonl (0xfd000000) \
&& ((__const uint32_t *) (a))[1] == 0 \
&& ((__const uint32_t *) (a))[2] == 0 \
&& ((__const uint32_t *) (a))[3] == 0)
#define IN6_IS_ADDR_LINK_LOCAL_ZERO(a) \
(((__const uint32_t *) (a))[0] == htonl (0xfe800000) \
&& ((__const uint32_t *) (a))[1] == 0 \
&& ((__const uint32_t *) (a))[2] == 0 \
&& ((__const uint32_t *) (a))[3] == 0)
...@@ -31,8 +31,8 @@ struct ra_param { ...@@ -31,8 +31,8 @@ struct ra_param {
int ind, managed, other, found_context, first; int ind, managed, other, found_context, first;
char *if_name; char *if_name;
struct dhcp_netid *tags; struct dhcp_netid *tags;
struct in6_addr link_local, link_global; struct in6_addr link_local, link_global, ula;
unsigned int pref_time, adv_interval; unsigned int glob_pref_time, link_pref_time, ula_pref_time, adv_interval;
}; };
struct search_param { struct search_param {
...@@ -206,6 +206,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de ...@@ -206,6 +206,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
struct dhcp_opt *opt_cfg; struct dhcp_opt *opt_cfg;
struct ra_interface *ra_param = find_iface_param(iface_name); struct ra_interface *ra_param = find_iface_param(iface_name);
int done_dns = 0, old_prefix = 0; int done_dns = 0, old_prefix = 0;
unsigned int min_pref_time;
#ifdef HAVE_LINUX_NETWORK #ifdef HAVE_LINUX_NETWORK
FILE *f; FILE *f;
#endif #endif
...@@ -228,7 +229,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de ...@@ -228,7 +229,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
parm.if_name = iface_name; parm.if_name = iface_name;
parm.first = 1; parm.first = 1;
parm.now = now; parm.now = now;
parm.pref_time = 0; parm.glob_pref_time = parm.link_pref_time = parm.ula_pref_time = 0;
parm.adv_interval = calc_interval(ra_param); parm.adv_interval = calc_interval(ra_param);
/* set tag with name == interface */ /* set tag with name == interface */
...@@ -245,6 +246,18 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de ...@@ -245,6 +246,18 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
if (!iface_enumerate(AF_INET6, &parm, add_prefixes)) if (!iface_enumerate(AF_INET6, &parm, add_prefixes))
return; return;
/* Find smallest preferred time within address classes,
to use as lifetime for options. This is a rather arbitrary choice. */
min_pref_time = 0xffffffff;
if (parm.glob_pref_time != 0 && parm.glob_pref_time < min_pref_time)
min_pref_time = parm.glob_pref_time;
if (parm.ula_pref_time != 0 && parm.ula_pref_time < min_pref_time)
min_pref_time = parm.ula_pref_time;
if (parm.link_pref_time != 0 && parm.link_pref_time < min_pref_time)
min_pref_time = parm.link_pref_time;
/* Look for constructed contexts associated with addresses which have gone, /* Look for constructed contexts associated with addresses which have gone,
and advertise them with preferred_time == 0 RFC 6204 4.3 L-13 */ and advertise them with preferred_time == 0 RFC 6204 4.3 L-13 */
for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp) for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp)
...@@ -340,23 +353,49 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de ...@@ -340,23 +353,49 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
if (opt_cfg->opt == OPTION6_DNS_SERVER) if (opt_cfg->opt == OPTION6_DNS_SERVER)
{ {
struct in6_addr *a = (struct in6_addr *)opt_cfg->val; struct in6_addr *a;
int len;
done_dns = 1; done_dns = 1;
if (opt_cfg->len == 0 || (IN6_IS_ADDR_UNSPECIFIED(a) && parm.pref_time == 0))
if (opt_cfg->len == 0)
continue; continue;
/* reduce len for any addresses we can't substitute */
for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, i = 0;
i < opt_cfg->len; i += IN6ADDRSZ, a++)
if ((IN6_IS_ADDR_UNSPECIFIED(a) && parm.glob_pref_time == 0) ||
(IN6_IS_ADDR_ULA_ZERO(a) && parm.ula_pref_time == 0) ||
(IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && parm.link_pref_time == 0))
len -= IN6ADDRSZ;
if (len != 0)
{
put_opt6_char(ICMP6_OPT_RDNSS); put_opt6_char(ICMP6_OPT_RDNSS);
put_opt6_char((opt_cfg->len/8) + 1); put_opt6_char((len/8) + 1);
put_opt6_short(0); put_opt6_short(0);
put_opt6_long(parm.pref_time); put_opt6_long(min_pref_time);
/* zero means "self" */
for (i = 0; i < opt_cfg->len; i += IN6ADDRSZ, a++) for (a = (struct in6_addr *)opt_cfg->val, i = 0; i < opt_cfg->len; i += IN6ADDRSZ, a++)
if (IN6_IS_ADDR_UNSPECIFIED(a)) if (IN6_IS_ADDR_UNSPECIFIED(a))
{
if (parm.glob_pref_time != 0)
put_opt6(&parm.link_global, IN6ADDRSZ); put_opt6(&parm.link_global, IN6ADDRSZ);
}
else if (IN6_IS_ADDR_ULA_ZERO(a))
{
if (parm.ula_pref_time != 0)
put_opt6(&parm.ula, IN6ADDRSZ);
}
else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a))
{
if (parm.link_pref_time != 0)
put_opt6(&parm.link_local, IN6ADDRSZ);
}
else else
put_opt6(a, IN6ADDRSZ); put_opt6(a, IN6ADDRSZ);
} }
}
if (opt_cfg->opt == OPTION6_DOMAIN_SEARCH && opt_cfg->len != 0) if (opt_cfg->opt == OPTION6_DOMAIN_SEARCH && opt_cfg->len != 0)
{ {
...@@ -365,7 +404,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de ...@@ -365,7 +404,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
put_opt6_char(ICMP6_OPT_DNSSL); put_opt6_char(ICMP6_OPT_DNSSL);
put_opt6_char(len + 1); put_opt6_char(len + 1);
put_opt6_short(0); put_opt6_short(0);
put_opt6_long(parm.pref_time); put_opt6_long(min_pref_time);
put_opt6(opt_cfg->val, opt_cfg->len); put_opt6(opt_cfg->val, opt_cfg->len);
/* pad */ /* pad */
...@@ -374,13 +413,13 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de ...@@ -374,13 +413,13 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
} }
} }
if (daemon->port == NAMESERVER_PORT && !done_dns && parm.pref_time != 0) if (daemon->port == NAMESERVER_PORT && !done_dns && parm.link_pref_time != 0)
{ {
/* default == us, as long as we are supplying DNS service. */ /* default == us, as long as we are supplying DNS service. */
put_opt6_char(ICMP6_OPT_RDNSS); put_opt6_char(ICMP6_OPT_RDNSS);
put_opt6_char(3); put_opt6_char(3);
put_opt6_short(0); put_opt6_short(0);
put_opt6_long(parm.pref_time); put_opt6_long(min_pref_time);
put_opt6(&parm.link_local, IN6ADDRSZ); put_opt6(&parm.link_local, IN6ADDRSZ);
} }
...@@ -426,7 +465,16 @@ static int add_prefixes(struct in6_addr *local, int prefix, ...@@ -426,7 +465,16 @@ static int add_prefixes(struct in6_addr *local, int prefix,
if (if_index == param->ind) if (if_index == param->ind)
{ {
if (IN6_IS_ADDR_LINKLOCAL(local)) if (IN6_IS_ADDR_LINKLOCAL(local))
{
/* Can there be more than one LL address?
Select the one with the longest preferred time
if there is. */
if (preferred > param->link_pref_time)
{
param->link_pref_time = preferred;
param->link_local = *local; param->link_local = *local;
}
}
else if (!IN6_IS_ADDR_LOOPBACK(local) && else if (!IN6_IS_ADDR_LOOPBACK(local) &&
!IN6_IS_ADDR_MULTICAST(local)) !IN6_IS_ADDR_MULTICAST(local))
{ {
...@@ -517,11 +565,22 @@ static int add_prefixes(struct in6_addr *local, int prefix, ...@@ -517,11 +565,22 @@ static int add_prefixes(struct in6_addr *local, int prefix,
if (!constructed || preferred > time) if (!constructed || preferred > time)
preferred = time; preferred = time;
if (preferred > param->pref_time) if (IN6_IS_ADDR_ULA(local))
{
if (preferred > param->ula_pref_time)
{ {
param->pref_time = preferred; param->ula_pref_time = preferred;
param->ula = *local;
}
}
else
{
if (preferred > param->glob_pref_time)
{
param->glob_pref_time = preferred;
param->link_global = *local; param->link_global = *local;
} }
}
if (real_prefix != 0) if (real_prefix != 0)
{ {
...@@ -546,7 +605,6 @@ static int add_prefixes(struct in6_addr *local, int prefix, ...@@ -546,7 +605,6 @@ static int add_prefixes(struct in6_addr *local, int prefix,
if (!option_bool(OPT_QUIET_RA)) if (!option_bool(OPT_QUIET_RA))
my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s", param->if_name, daemon->addrbuff); my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s", param->if_name, daemon->addrbuff);
} }
} }
} }
} }
......
...@@ -24,7 +24,7 @@ struct state { ...@@ -24,7 +24,7 @@ struct state {
int clid_len, iaid, ia_type, interface, hostname_auth, lease_allocate; int clid_len, iaid, ia_type, interface, hostname_auth, lease_allocate;
char *client_hostname, *hostname, *domain, *send_domain; char *client_hostname, *hostname, *domain, *send_domain;
struct dhcp_context *context; struct dhcp_context *context;
struct in6_addr *link_address, *fallback; struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr;
unsigned int xid, fqdn_flags; unsigned int xid, fqdn_flags;
char *iface_name; char *iface_name;
void *packet_options, *end; void *packet_options, *end;
...@@ -73,7 +73,8 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time ...@@ -73,7 +73,8 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time
unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name, unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
struct in6_addr *fallback, size_t sz, struct in6_addr *client_addr, time_t now) struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
size_t sz, struct in6_addr *client_addr, time_t now)
{ {
struct dhcp_vendor *vendor; struct dhcp_vendor *vendor;
int msg_type; int msg_type;
...@@ -93,6 +94,8 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if ...@@ -93,6 +94,8 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if
state.interface = interface; state.interface = interface;
state.iface_name = iface_name; state.iface_name = iface_name;
state.fallback = fallback; state.fallback = fallback;
state.ll_addr = ll_addr;
state.ula_addr = ula_addr;
state.mac_len = 0; state.mac_len = 0;
state.tags = NULL; state.tags = NULL;
state.link_address = NULL; state.link_address = NULL;
...@@ -1269,37 +1272,60 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh) ...@@ -1269,37 +1272,60 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh)
continue; continue;
} }
if (opt_cfg->opt == OPTION6_DNS_SERVER)
{
done_dns = 1;
if (opt_cfg->len == 0)
continue;
}
if (opt_cfg->opt == OPTION6_REFRESH_TIME) if (opt_cfg->opt == OPTION6_REFRESH_TIME)
done_refresh = 1; done_refresh = 1;
o = new_opt6(opt_cfg->opt);
if (opt_cfg->flags & DHOPT_ADDR6) if (opt_cfg->flags & DHOPT_ADDR6)
{ {
int j; int len, j;
struct in6_addr *a = (struct in6_addr *)opt_cfg->val; struct in6_addr *a;
for (j = 0; j < opt_cfg->len; j+=IN6ADDRSZ, a++)
if (opt_cfg->opt == OPTION6_DNS_SERVER)
done_dns = 1;
for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, j = 0;
j < opt_cfg->len; j += IN6ADDRSZ, a++)
if ((IN6_IS_ADDR_ULA_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ula_addr)) ||
(IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ll_addr)))
len -= IN6ADDRSZ;
if (len != 0)
{
o = new_opt6(opt_cfg->opt);
for (a = (struct in6_addr *)opt_cfg->val, j = 0; j < opt_cfg->len; j+=IN6ADDRSZ, a++)
{ {
/* zero means "self" (but not in vendorclass options.) */
if (IN6_IS_ADDR_UNSPECIFIED(a)) if (IN6_IS_ADDR_UNSPECIFIED(a))
{ {
if (!add_local_addrs(state->context)) if (!add_local_addrs(state->context))
put_opt6(state->fallback, IN6ADDRSZ); put_opt6(state->fallback, IN6ADDRSZ);
} }
else if (IN6_IS_ADDR_ULA_ZERO(a))
{
if (!IN6_IS_ADDR_UNSPECIFIED(state->ula_addr))
put_opt6(state->ula_addr, IN6ADDRSZ);
}
else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a))
{
if (!IN6_IS_ADDR_UNSPECIFIED(state->ll_addr))
put_opt6(state->ll_addr, IN6ADDRSZ);
}
else else
put_opt6(a, IN6ADDRSZ); put_opt6(a, IN6ADDRSZ);
} }
end_opt6(o);
}
} }
else if (opt_cfg->val) else
{
o = new_opt6(opt_cfg->opt);
if (opt_cfg->val)
put_opt6(opt_cfg->val, opt_cfg->len); put_opt6(opt_cfg->val, opt_cfg->len);
end_opt6(o); end_opt6(o);
} }
}
if (daemon->port == NAMESERVER_PORT && !done_dns) if (daemon->port == NAMESERVER_PORT && !done_dns)
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment