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.
This commit is contained in:
Simon Kelley
2020-02-03 23:58:45 +00:00
parent 515ba97595
commit 79aba0f10a
7 changed files with 122 additions and 42 deletions

View File

@@ -69,6 +69,14 @@ version 2.81
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

View File

@@ -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.

View File

@@ -424,10 +424,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.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

View File

@@ -418,14 +418,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;
@@ -494,16 +494,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)
@@ -557,27 +556,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;

View File

@@ -767,6 +767,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;
@@ -790,6 +791,7 @@ struct dhcp_config {
#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_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,
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_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,

View File

@@ -3264,8 +3264,11 @@ 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))
{
@@ -3273,6 +3276,21 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
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;

View File

@@ -49,6 +49,8 @@ static void end_ia(int t1cntr, unsigned int min_time, int do_fuzz);
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 @@ 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);
@@ -699,8 +701,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))
@@ -838,14 +839,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)
{
@@ -971,12 +971,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;
@@ -1667,6 +1666,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: