Support prefixed ranges of ipv6 addresses in dhcp-host.
When a request matching the clid or mac address is
recieved the server will iterate over all candidate
addresses until it find's one that is not already
leased to a different clid/iaid and advertise
this address.
Using multiple reservations for a single host makes it
possible to maintain a static leases only configuration
which support network booting systems with UEFI firmware
that request a new address (a new SOLICIT with a new IA_NA
option using a new IAID) for different boot modes, for
instance 'PXE over IPv6', and 'HTTP-Boot over IPv6'. Open
Virtual Machine Firmware (OVMF) and most UEFI firmware
build on the EDK2 code base exhibit this behaviour.
diff --git a/CHANGELOG b/CHANGELOG
index 80ea482..319b230 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -69,6 +69,14 @@
different interfaces on the same IPv6 net, and we're doing
RA/DHCP service on only one of them. Thanks to NIIBE Yutaka
for spotting this case and making the initial patch.
+
+ Support prefixed ranges of ipv6 addresses in dhcp-host.
+ This eases problems chain-netbooting, where each link in the
+ chain requests an address using a different UID. With a single
+ address, only one gets the "static" address, but with this
+ fix, enough addresses can be reserved for all the stages of the
+ boot. Many thanks to Harald Jensås for his work on this idea and
+ earlier patches.
version 2.80
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index cb5cc73..e4143b0 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -1013,7 +1013,13 @@
IPv6 addresses may contain only the host-identifier part:
.B --dhcp-host=laptop,[::56]
in which case they act as wildcards in constructed dhcp ranges, with
-the appropriate network part inserted.
+the appropriate network part inserted. For IPv6, the address may include a prefix length:
+.B --dhcp-host=laptop,[1234:50/126]
+which (in this case) specifies four addresses, 1234::50 to 1234::53. This is useful
+when a host presents either a consistent name or hardware-ID, but varying DUIDs, since it allows
+dnsmasq to honour the static address allocation but assign a different adddress for each DUID. This
+typically occurs when chain netbooting, as each stage of the chain gets in turn allocates an address.
+
Note that in IPv6 DHCP, the hardware address may not be
available, though it normally is for direct-connected clients, or
clients using DHCP relays which support RFC 6939.
diff --git a/src/dhcp-common.c b/src/dhcp-common.c
index dc7f945..a03edc9 100644
--- a/src/dhcp-common.c
+++ b/src/dhcp-common.c
@@ -424,10 +424,11 @@
#ifdef HAVE_DHCP6
if (prot == AF_INET6 &&
- (!(conf_tmp = config_find_by_address6(configs, &crec->addr.addr6, 128, 0)) || conf_tmp == config))
+ (!(conf_tmp = config_find_by_address6(configs, NULL, 0, &crec->addr.addr6)) || conf_tmp == config))
{
memcpy(&config->addr6, &crec->addr.addr6, IN6ADDRSZ);
config->flags |= CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS;
+ config->flags &= ~CONFIG_PREFIX;
continue;
}
#endif
diff --git a/src/dhcp6.c b/src/dhcp6.c
index 51788ed..63c0c93 100644
--- a/src/dhcp6.c
+++ b/src/dhcp6.c
@@ -418,14 +418,14 @@
return 1;
}
-struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr)
+struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, struct in6_addr *addr)
{
struct dhcp_config *config;
for (config = configs; config; config = config->next)
if ((config->flags & CONFIG_ADDR6) &&
- is_same_net6(&config->addr6, net, prefix) &&
- (prefix == 128 || addr6part(&config->addr6) == addr))
+ (!net || is_same_net6(&config->addr6, net, prefix)) &&
+ is_same_net6(&config->addr6, addr, (config->flags & CONFIG_PREFIX) ? config->prefix : 128))
return config;
return NULL;
@@ -494,16 +494,15 @@
for (d = context; d; d = d->current)
if (addr == addr6part(&d->local6))
break;
+
+ *ans = c->start6;
+ setaddr6part (ans, addr);
if (!d &&
!lease6_find_by_addr(&c->start6, c->prefix, addr) &&
- !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, addr))
- {
- *ans = c->start6;
- setaddr6part (ans, addr);
- return c;
- }
-
+ !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, ans))
+ return c;
+
addr++;
if (addr == addr6part(&c->end6) + 1)
@@ -557,27 +556,6 @@
return NULL;
}
-int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr)
-{
- if (!config || !(config->flags & CONFIG_ADDR6))
- return 0;
-
- if ((config->flags & CONFIG_WILDCARD) && context->prefix == 64)
- {
- *addr = context->start6;
- setaddr6part(addr, addr6part(&config->addr6));
- return 1;
- }
-
- if (is_same_net6(&context->start6, &config->addr6, context->prefix))
- {
- *addr = config->addr6;
- return 1;
- }
-
- return 0;
-}
-
void make_duid(time_t now)
{
(void)now;
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 7fb440c..2677dc5 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -767,6 +767,7 @@
struct dhcp_netid_list *netid;
#ifdef HAVE_DHCP6
struct in6_addr addr6;
+ int prefix;
#endif
struct in_addr addr;
time_t decline_time;
@@ -790,6 +791,7 @@
#define CONFIG_ADDR6 4096
#define CONFIG_WILDCARD 8192
#define CONFIG_ADDR6_HOSTS 16384 /* address added by from /etc/hosts */
+#define CONFIG_PREFIX 32768 /* addr6 is a set, size given by prefix */
struct dhcp_opt {
int opt, len, flags;
@@ -1508,7 +1510,6 @@
void dhcp6_packet(time_t now);
struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, int temp_addr,
unsigned int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans);
-int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr);
struct dhcp_context *address6_available(struct dhcp_context *context,
struct in6_addr *taddr,
struct dhcp_netid *netids,
@@ -1518,7 +1519,7 @@
struct dhcp_netid *netids,
int plain_range);
struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net,
- int prefix, u64 addr);
+ int prefix, struct in6_addr *addr);
void make_duid(time_t now);
void dhcp_construct_contexts(time_t now);
void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac,
diff --git a/src/option.c b/src/option.c
index f77545f..627aecf 100644
--- a/src/option.c
+++ b/src/option.c
@@ -3264,8 +3264,11 @@
#ifdef HAVE_DHCP6
else if (arg[0] == '[' && arg[strlen(arg)-1] == ']')
{
+ char *pref;
+
arg[strlen(arg)-1] = 0;
arg++;
+ pref = split_chr(arg, '/');
if (!inet_pton(AF_INET6, arg, &new->addr6))
{
@@ -3273,6 +3276,21 @@
ret_err(_("bad IPv6 address"));
}
+ if (pref)
+ {
+ u64 addrpart = addr6part(&new->addr6);
+
+ if (!atoi_check(pref, &new->prefix) ||
+ new->prefix > 128 ||
+ (((1<<(128-new->prefix))-1) & addrpart) != 0)
+ {
+ dhcp_config_free(new);
+ ret_err(_("bad IPv6 prefix"));
+ }
+
+ new->flags |= CONFIG_PREFIX;
+ }
+
for (i= 0; i < 8; i++)
if (new->addr6.s6_addr[i] != 0)
break;
diff --git a/src/rfc3315.c b/src/rfc3315.c
index 9471f5c..e0fd7be 100644
--- a/src/rfc3315.c
+++ b/src/rfc3315.c
@@ -49,6 +49,8 @@
static void mark_context_used(struct state *state, struct in6_addr *addr);
static void mark_config_used(struct dhcp_context *context, struct in6_addr *addr);
static int check_address(struct state *state, struct in6_addr *addr);
+static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state);
+static int config_implies(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr);
static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, void *ia_option,
unsigned int *min_time, struct in6_addr *addr, time_t now);
static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now);
@@ -675,7 +677,7 @@
/* If the client asks for an address on the same network as a configured address,
offer the configured address instead, to make moving to newly-configured
addresses automatic. */
- if (!(c->flags & CONTEXT_CONF_USED) && config_valid(config, c, &addr) && check_address(state, &addr))
+ if (!(c->flags & CONTEXT_CONF_USED) && config_valid(config, c, &addr, state))
{
req_addr = addr;
mark_config_used(c, &addr);
@@ -699,8 +701,7 @@
for (c = state->context; c; c = c->current)
if (!(c->flags & CONTEXT_CONF_USED) &&
match_netid(c->filter, solicit_tags, plain_range) &&
- config_valid(config, c, &addr) &&
- check_address(state, &addr))
+ config_valid(config, c, &addr, state))
{
mark_config_used(state->context, &addr);
if (have_config(config, CONFIG_TIME))
@@ -838,14 +839,13 @@
struct in6_addr req_addr;
struct dhcp_context *dynamic, *c;
unsigned int lease_time;
- struct in6_addr addr;
int config_ok = 0;
/* align. */
memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
if ((c = address6_valid(state->context, &req_addr, tagif, 1)))
- config_ok = config_valid(config, c, &addr) && IN6_ARE_ADDR_EQUAL(&addr, &req_addr);
+ config_ok = config_implies(config, c, &req_addr);
if ((dynamic = address6_available(state->context, &req_addr, tagif, 1)) || c)
{
@@ -971,12 +971,11 @@
if ((this_context = address6_available(state->context, &req_addr, tagif, 1)) ||
(this_context = address6_valid(state->context, &req_addr, tagif, 1)))
{
- struct in6_addr addr;
unsigned int lease_time;
get_context_tag(state, this_context);
- if (config_valid(config, this_context, &addr) && IN6_ARE_ADDR_EQUAL(&addr, &req_addr) && have_config(config, CONFIG_TIME))
+ if (config_implies(config, this_context, &req_addr) && have_config(config, CONFIG_TIME))
lease_time = config->lease_time;
else
lease_time = this_context->lease_time;
@@ -1667,6 +1666,75 @@
}
+/* return true of *addr could have been generated from config. */
+static int config_implies(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr)
+{
+ int prefix;
+ struct in6_addr wild_addr;
+
+ if (!config || !(config->flags & CONFIG_ADDR6))
+ return 0;
+
+ prefix = (config->flags & CONFIG_PREFIX) ? config->prefix : 128;
+ wild_addr = config->addr6;
+
+ if (!is_same_net6(&context->start6, addr, context->prefix))
+ return 0;
+
+ if ((config->flags & CONFIG_WILDCARD))
+ {
+ if (context->prefix != 64)
+ return 0;
+
+ wild_addr = context->start6;
+ setaddr6part(&wild_addr, addr6part(&config->addr6));
+ }
+
+ if (is_same_net6(&wild_addr, addr, prefix))
+ return 1;
+
+ return 0;
+}
+
+static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state)
+{
+ u64 addrpart;
+
+ if (!config || !(config->flags & CONFIG_ADDR6))
+ return 0;
+
+ addrpart = addr6part(&config->addr6);
+
+ if ((config->flags & CONFIG_WILDCARD))
+ {
+ if (context->prefix != 64)
+ return 0;
+
+ *addr = context->start6;
+ setaddr6part(addr, addrpart);
+ }
+ else if (is_same_net6(&context->start6, &config->addr6, context->prefix))
+ *addr = config->addr6;
+ else
+ return 0;
+
+ while(1) {
+ if (check_address(state, addr))
+ return 1;
+
+ if (!(config->flags & CONFIG_PREFIX))
+ return 0;
+
+ /* config may specify a set of addresses, return first one not in use
+ by another client */
+
+ addrpart++;
+ setaddr6part(addr, addrpart);
+ if (!is_same_net6(addr, &config->addr6, config->prefix))
+ return 0;
+ }
+}
+
/* Calculate valid and preferred times to send in leases/renewals.
Inputs are: