Reposting this, as it seems my e-mail client mangled the patch by inserting line-breaks etc.
On Mon, 2019-12-23 at 12:24 +0100, Harald Jensas wrote: > Hi, > > The patch below is a slight alteration to a possible solution > discussed in > http://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2017q1/011289.html > . > > My approach here does not require making dhcp-host conditional on a > tag. However, making dhcp-host conditional on a tag would be a nice > addition that could be introduced as a follow up to this to have a > match on the tag of the final OS to keep the provisioned system > consistently configured with a specific address can be very handy. > For > the Openstack use-case I am working in, this however is'nt necessary. > > I have confirmed that the patch below together with a small change in > Openstack Ironic (see: https://review.opendev.org/700002) solved the > long standing issue when doing network booting and node provisioning > in combination with static only dhcp configuration. > > We are looking forward to comments and feedback regarding this > approach. > > Thank you! > > Regards > Harald Jensås > From 8b238dcf99dcf3332ec1c76fbb5af283db65a637 Mon Sep 17 00:00:00 2001 From: Harald Jensås <hjen...@redhat.com> Date: Wed, 18 Dec 2019 23:59:11 +0100 Subject: [PATCH] DHCPv6 - Multiple reservations for single host This change adds support for multiple dhcpv6 host reservations. The same clid or hwaddr can be used in multiple --dhcp-host entries. When receiving a request and a config containing an ip address is found, a test is done to see if the address is already leased to a different CLID/IAID. In case the ip address in the config was already used, skip_entry is incremented and find_config() is re-executed. find_config() will now skip the first config it finds, and continue looking for another config entry to return. This repeats until all possible config entries has been exhausted. 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. RFC 8415 which updates RFC 3315 describes a single client request multiple IA's of any kind. These clients do this, using a new SOLICIT to request each IA. The clients could pack all IA's in one SOLICIT, but doing it individually as the above mentioned implementations do should not be a problem. --- src/dhcp-common.c | 19 ++++++++++++++++--- src/dnsmasq.h | 3 ++- src/lease.c | 2 +- src/rfc2131.c | 6 +++--- src/rfc3315.c | 29 +++++++++++++++++++++++------ 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/dhcp-common.c b/src/dhcp-common.c index 602873e..5e770de 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -299,7 +299,8 @@ struct dhcp_config *find_config(struct dhcp_config *configs, struct dhcp_context *context, unsigned char *clid, int clid_len, unsigned char *hwaddr, int hw_len, - int hw_type, char *hostname) + int hw_type, char *hostname, + int skip_entries) { int count, new; struct dhcp_config *config, *candidate; @@ -312,15 +313,23 @@ struct dhcp_config *find_config(struct dhcp_config *configs, if (config->clid_len == clid_len && memcmp(config->clid, clid, clid_len) == 0 && is_config_in_context(context, config)) + { + if (--skip_entries > 0) + continue; return config; - + } + /* dhcpcd prefixes ASCII client IDs by zero which is wrong, but we try and cope with that here. This is IPv4 only. context==NULL implies IPv4, see lease_update_from_configs() */ if ((!context || !(context->flags & CONTEXT_V6)) && *clid == 0 && config->clid_len == clid_len-1 && memcmp(config->clid, clid+1, clid_len-1) == 0 && is_config_in_context(context, config)) + { + if (--skip_entries > 0) + continue; return config; + } } @@ -328,7 +337,11 @@ struct dhcp_config *find_config(struct dhcp_config *configs, for (config = configs; config; config = config->next) if (config_has_mac(config, hwaddr, hw_len, hw_type) && is_config_in_context(context, config)) - return config; + { + if (--skip_entries > 0) + continue; + return config; + } if (hostname && context) for (config = configs; config; config = config->next) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 8e047fc..8760517 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1563,7 +1563,8 @@ struct dhcp_config *find_config(struct dhcp_config *configs, struct dhcp_context *context, unsigned char *clid, int clid_len, unsigned char *hwaddr, int hw_len, - int hw_type, char *hostname); + int hw_type, char *hostname, + int skip_entries); int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type); #ifdef HAVE_LINUX_NETWORK char *whichdevice(void); diff --git a/src/lease.c b/src/lease.c index 081d90e..c34b90a 100644 --- a/src/lease.c +++ b/src/lease.c @@ -230,7 +230,7 @@ void lease_update_from_configs(void) if (lease->flags & (LEASE_TA | LEASE_NA)) continue; else if ((config = find_config(daemon->dhcp_conf, NULL, lease->clid, lease->clid_len, - lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, NULL)) && + lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, NULL, 0)) && (config->flags & CONFIG_NAME) && (!(config->flags & CONFIG_ADDR) || config->addr.s_addr == lease->addr.s_addr)) lease_set_hostname(lease, config->hostname, 1, get_domain(lease->addr), NULL); diff --git a/src/rfc2131.c b/src/rfc2131.c index 033c5db..a7179c3 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -504,7 +504,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, mess->op = BOOTREPLY; config = find_config(daemon->dhcp_conf, context, clid, clid_len, - mess->chaddr, mess->hlen, mess->htype, NULL); + mess->chaddr, mess->hlen, mess->htype, NULL, 0); /* set "known" tag for known hosts */ if (config) @@ -514,7 +514,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, netid = &known_id; } else if (find_config(daemon->dhcp_conf, NULL, clid, clid_len, - mess->chaddr, mess->hlen, mess->htype, NULL)) + mess->chaddr, mess->hlen, mess->htype, NULL, 0)) { known_id.net = "known-othernet"; known_id.next = netid; @@ -781,7 +781,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, to avoid impersonation by name. */ struct dhcp_config *new = find_config(daemon->dhcp_conf, context, NULL, 0, mess->chaddr, mess->hlen, - mess->htype, hostname); + mess->htype, hostname, 0); if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr) { config = new; diff --git a/src/rfc3315.c b/src/rfc3315.c index 2ef9073..97672b9 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -535,10 +535,27 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ } } } - - if (state->clid && - (config = find_config(daemon->dhcp_conf, state->context, state->clid, state->clid_len, state->mac, state->mac_len, state->mac_type, NULL)) && - have_config(config, CONFIG_NAME)) + + if (state->clid) + { + /* Loop to find config that is not already used*/ + int skip_entries = 0; + do { + config = find_config(daemon->dhcp_conf, state->context, state->clid, + state->clid_len, state->mac, state->mac_len, + state->mac_type, NULL, skip_entries); + /* Always use config with no address */ + if (config && !&config->addr6) + break; + /* Check if address not leased to another CLID/IAID */ + if (config && check_address(state, &config->addr6)) + break; + /* Skip one more entry in the next find_config pass */ + skip_entries++; + } while (config != NULL); + } + + if (state->clid && config && have_config(config, CONFIG_NAME)) { state->hostname = config->hostname; state->domain = config->domain; @@ -557,7 +574,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ /* Search again now we have a hostname. Only accept configs without CLID here, (it won't match) to avoid impersonation by name. */ - struct dhcp_config *new = find_config(daemon->dhcp_conf, state->context, NULL, 0, NULL, 0, 0, state->hostname); + struct dhcp_config *new = find_config(daemon->dhcp_conf, state->context, NULL, 0, NULL, 0, 0, state->hostname, 0); if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr) config = new; } @@ -583,7 +600,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ ignore = 1; } else if (state->clid && - find_config(daemon->dhcp_conf, NULL, state->clid, state->clid_len, state->mac, state->mac_len, state->mac_type, NULL)) + find_config(daemon->dhcp_conf, NULL, state->clid, state->clid_len, state->mac, state->mac_len, state->mac_type, NULL, 0)) { known_id.net = "known-othernet"; known_id.next = state->tags; -- 2.24.1 _______________________________________________ Dnsmasq-discuss mailing list Dnsmasq-discuss@lists.thekelleys.org.uk http://lists.thekelleys.org.uk/mailman/listinfo/dnsmasq-discuss