dnsmasq/dnsmasq-2.81-prefix-ranges-...

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