[Dnsmasq-discuss] [PATCH] DHCPv6 - Multiple reservations for single host

Harald Jensås hjensas at redhat.com
Tue Jan 7 09:01:59 GMT 2020


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 <hjensas at 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





More information about the Dnsmasq-discuss mailing list