From e81e994303a89998c5796a5951192e3a0c0395bc 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. (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