From 79aba0f10ad0157fb4f48afbbcb03f094caff97a Mon Sep 17 00:00:00 2001 From: Simon Kelley 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. --- CHANGELOG | 8 +++++ man/dnsmasq.8 | 8 ++++- src/dhcp-common.c | 3 +- src/dhcp6.c | 40 ++++++----------------- src/dnsmasq.h | 5 +-- src/option.c | 18 +++++++++++ src/rfc3315.c | 82 +++++++++++++++++++++++++++++++++++++++++++---- 7 files changed, 122 insertions(+), 42 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 80ea482..319b230 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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 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 @@ 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 dc7f945..a03edc9 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -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 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 @@ 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; 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_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, 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 @@ 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; 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 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: