cde7b60662
In some cases, DUID will change for the same machine during network boot. Support assigning small blocks of IPv6 addresses to work around changing DUID.
397 lines
14 KiB
Diff
397 lines
14 KiB
Diff
From e81e994303a89998c5796a5951192e3a0c0395bc Mon Sep 17 00:00:00 2001
|
|
From: Simon Kelley <simon@thekelleys.org.uk>
|
|
Date: Mon, 3 Feb 2020 23:58:45 +0000
|
|
Subject: [PATCH] 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.
|
|
|
|
(cherry picked from commit 79aba0f10ad0157fb4f48afbbcb03f094caff97a)
|
|
---
|
|
man/dnsmasq.8 | 8 ++++-
|
|
src/dhcp-common.c | 3 +-
|
|
src/dhcp6.c | 40 ++++++-----------------
|
|
src/dnsmasq.h | 5 +--
|
|
src/option.c | 48 +++++++++++++++++++++++++++
|
|
src/rfc3315.c | 82 +++++++++++++++++++++++++++++++++++++++++++----
|
|
6 files changed, 144 insertions(+), 42 deletions(-)
|
|
|
|
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
|
|
index f01a5ba..95cd3ca 100644
|
|
--- a/man/dnsmasq.8
|
|
+++ b/man/dnsmasq.8
|
|
@@ -1013,7 +1013,13 @@ may contain an IPv4 address or an IPv6 address, or both. IPv6 addresses must be
|
|
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 78c1d9b..99d34c8 100644
|
|
--- a/src/dhcp-common.c
|
|
+++ b/src/dhcp-common.c
|
|
@@ -418,10 +418,11 @@ void dhcp_update_configs(struct dhcp_config *configs)
|
|
|
|
#ifdef HAVE_DHCP6
|
|
if (prot == AF_INET6 &&
|
|
- (!(conf_tmp = config_find_by_address6(configs, &crec->addr.addr.addr.addr6, 128, 0)) || conf_tmp == config))
|
|
+ (!(conf_tmp = config_find_by_address6(configs, NULL, 0, &crec->addr.addr.addr.addr6)) || conf_tmp == config))
|
|
{
|
|
memcpy(&config->addr6, &crec->addr.addr.addr.addr6, IN6ADDRSZ);
|
|
config->flags |= CONFIG_ADDR6 | CONFIG_ADDR_HOSTS;
|
|
+ config->flags &= ~CONFIG_PREFIX;
|
|
continue;
|
|
}
|
|
#endif
|
|
diff --git a/src/dhcp6.c b/src/dhcp6.c
|
|
index b7cce45..11a9d83 100644
|
|
--- a/src/dhcp6.c
|
|
+++ b/src/dhcp6.c
|
|
@@ -384,14 +384,14 @@ static int complete_context6(struct in6_addr *local, int prefix,
|
|
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;
|
|
@@ -453,16 +453,15 @@ struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned c
|
|
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)
|
|
@@ -516,27 +515,6 @@ struct dhcp_context *address6_valid(struct dhcp_context *context,
|
|
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 8d84714..86b8168 100644
|
|
--- a/src/dnsmasq.h
|
|
+++ b/src/dnsmasq.h
|
|
@@ -775,6 +775,7 @@ struct dhcp_config {
|
|
struct dhcp_netid_list *netid;
|
|
#ifdef HAVE_DHCP6
|
|
struct in6_addr addr6;
|
|
+ int prefix;
|
|
#endif
|
|
struct in_addr addr;
|
|
time_t decline_time;
|
|
@@ -797,6 +798,7 @@ struct dhcp_config {
|
|
#define CONFIG_BANK 2048 /* from dhcp hosts file */
|
|
#define CONFIG_ADDR6 4096
|
|
#define CONFIG_WILDCARD 8192
|
|
+#define CONFIG_PREFIX 32768 /* addr6 is a set, size given by prefix */
|
|
|
|
struct dhcp_opt {
|
|
int opt, len, flags;
|
|
@@ -1514,7 +1516,6 @@ void dhcp6_init(void);
|
|
void dhcp6_packet(time_t now);
|
|
struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, int temp_addr,
|
|
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,
|
|
@@ -1524,7 +1525,7 @@ struct dhcp_context *address6_valid(struct dhcp_context *context,
|
|
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 f03a6b3..389eb02 100644
|
|
--- a/src/option.c
|
|
+++ b/src/option.c
|
|
@@ -965,6 +965,35 @@ static char *set_prefix(char *arg)
|
|
|
|
return arg;
|
|
}
|
|
+
|
|
+/* Legacy workaround, backported from 2.81 */
|
|
+static void dhcp_config_free(struct dhcp_config *config)
|
|
+{
|
|
+ struct hwaddr_config *mac, *tmp;
|
|
+ struct dhcp_netid_list *list, *tmplist;
|
|
+
|
|
+ for (mac = config->hwaddr; mac; mac = tmp)
|
|
+ {
|
|
+ tmp = mac->next;
|
|
+ free(mac);
|
|
+ }
|
|
+
|
|
+ if (config->flags & CONFIG_CLID)
|
|
+ free(config->clid);
|
|
+
|
|
+ for (list = config->netid; list; list = tmplist)
|
|
+ {
|
|
+ free(list->list);
|
|
+ tmplist = list->next;
|
|
+ free(list);
|
|
+ }
|
|
+
|
|
+ if (config->flags & CONFIG_NAME)
|
|
+ free(config->hostname);
|
|
+
|
|
+ free(config);
|
|
+}
|
|
+
|
|
|
|
/* This is too insanely large to keep in-line in the switch */
|
|
static int parse_dhcp_opt(char *errstr, char *arg, int flags)
|
|
@@ -1512,6 +1541,7 @@ void reset_option_bool(unsigned int opt)
|
|
daemon->options2 &= ~(1u << (opt - 32));
|
|
}
|
|
|
|
+
|
|
static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only)
|
|
{
|
|
int i;
|
|
@@ -3090,12 +3120,30 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|
#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))
|
|
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 a20776d..f4f032e 100644
|
|
--- a/src/rfc3315.c
|
|
+++ b/src/rfc3315.c
|
|
@@ -55,6 +55,8 @@ static struct prefix_class *prefix_class_from_context(struct dhcp_context *conte
|
|
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);
|
|
@@ -746,7 +748,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
|
|
/* 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);
|
|
@@ -774,8 +776,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
|
|
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))
|
|
@@ -924,14 +925,13 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
|
|
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)
|
|
{
|
|
@@ -1061,12 +1061,11 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
|
|
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;
|
|
@@ -1789,6 +1788,75 @@ static int check_address(struct state *state, struct in6_addr *addr)
|
|
}
|
|
|
|
|
|
+/* 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:
|
|
--
|
|
2.21.1
|
|
|