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

Harald Jensas hjensas at redhat.com
Mon Dec 23 11:24:16 GMT 2019


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