[Dnsmasq-discuss] [PATCH] DHCPv6 - List or Range reservation for single host

Harald Jensås hjensas at redhat.com
Tue Jan 21 23:31:16 GMT 2020


On Tue, 2020-01-21 at 23:38 +0100, Tore Anderson wrote:
> * Simon Kelley
> 
> > I have an alternative suggestion for the syntax of dhcp-host.
> > It's less flexible, but simpler and easier to understand and to
> > explain,
> > and uses existing semantics rather than adding new keywords.
> > 
> > The idea is just to add a prefix-length to the address. That allows
> > you
> > to define (eg) 1,2,4,8, or 16 addresses for use by a host simply
> > and
> > easily in a way which makes it difficult to accidentally overlap
> > address
> > ranges, and is fairly obvious to anyone who has done done any IPv6
> > network configuration.
> > 
> > for instance to reserve four addresses for each host we cold do:
> > 
> > dhcp-host=00:11:22:33:44:55,[fd12:3456::aa00/62]
> > dhcp-host=00:11:22:33:44:56,[fd12:3456::aa04/62]
> > dhcp-host=00:11:22:33:44:57,[fd12:3456::aa08/62]
> > 
> > As a sanity check, if the "host part" of the address isn't zero,
> > 
> > ie [fd12:3456::aa01/62]
> > 
> > that could be rejected with an error.
> 

I like the idea of using a prefix. I have a new revision of the patch
with this implemented at the bottom of this e-mail. It's far better and
more flexible than the keywords approach I came up with initially, as
it's now possible to mix individual addreses, prefixed ranges and
prefixed wildcard addresses etc.

# A list of addressses
dhcp-host=52:54:00:3f:5c:c0,[fd12:3456:789a:1::aa02][fd12:3456:789a:1::aa04][fd12:3456:789a:1::aa06],host1

# Mixing a prefix and a single address
dhcp-host=52:54:00:3f:5c:c0,[fd12:3456:789a:1::aa04/62][fd12:3456:789a:1::aa00],host1

# Prefixed wildcard
dhcp-host=52:54:00:3f:5c:c0,[::aa04/62],host1


> I have done quite a bit of IPv6 networking, but the use of /62 here
> is anything but «fairly obvious» to me.
> 
> It would have been much more intuitive to use /126, in my opinion.
> 
> Tore
> 

I too found it a bit curios with /62 at first, as I understand it the
interface identifier is always the 64 least significant bit's in IPv6
ref[1].

I think changing the patch to use the full 128 bit's as a mask is
trivial. We may also support both by subtracting 64 from any prefix
larger than 64 in the code? So /126 and /62 yield the same result.

What does other people think?

[1] 
https://en.wikipedia.org/wiki/IPv6_address#Unicast_and_anycast_address_format




>From a69c6400454cdf036808a0a40ac033688387f20d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Harald=20Jens=C3=A5s?= <hjensas at redhat.com>
Date: Mon, 13 Jan 2020 19:44:43 +0100
Subject: [PATCH] DHCPv6 - List and Range reservation for single host

Add the possibility to provide list's of individual
addresses as well as prefixed ranges of ipv6 addresses
for a dhcp-host reservation.

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.
---
 man/dnsmasq.8 | 16 ++++++++++++
 src/dnsmasq.h | 15 ++++++++++--
 src/option.c  | 68 +++++++++++++++++++++++++++++++++++++++------------
 src/rfc3315.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 147 insertions(+), 18 deletions(-)

diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index cb5cc73..57003d7 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -1079,6 +1079,22 @@ work reliably if only one of the hardware addresses is active at any
 time and there is no way for dnsmasq to enforce this. It is, for instance,
 useful to allocate a stable IP address to a laptop which
 has both wired and wireless interfaces.
+
+For DHCPv6 it is possible to provide multiple IPv6 addresses for a single
+dhcp-host. It is also possible to use a prefix in the configuration to
+set up a range of addresses. For example:
+\fB--dhcp-host=52:54:00:3f:5c:c0,[fd12:3456::aa02][fd12:3456::aa04],host1\fP
+will make the two addresses \fBfd12:3456::aa02\fP and \fBfd12:3456::aa04\fP
+available to the host with hardware address 52:54:00:3f:5c:c0.
+\fB--dhcp-host=52:54:00:3f:5c:c0,[fd12:3456::aa00/62],host1\fP
+will make the range of 4 addresses between the base address (fd12:3456::aa00)
+and the end address (fd12:3456::aa03) available to the host with hardware
+address 52:54:00:3f:5c:c0. Multiple non-prefixed, prefixed or prefixed
+wildcard addresses with only the host-identifier part can be mixed, eg:
+\fB--dhcp-host=52:54:00:3f:5c:c0,[::aa03][::aa04/63][::aa08/62],host1\fP.
+Providing multiple addresses is useful for network booting where individual
+boot stages will request addresses with different IAID's.
+
 .TP
 .B --dhcp-hostsfile=<path>
 Read DHCP host information from the specified file. If a directory
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 7fb440c..5c771b6 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -759,14 +759,24 @@ struct hwaddr_config {
   struct hwaddr_config *next;
 };
 
+#ifdef HAVE_DHCP6
+struct in6_addr_list {
+  struct in6_addr addr6;
+  int prefix;
+  unsigned long flags;
+  struct in6_addr_list *next;
+};
+#endif
+
 struct dhcp_config {
-  unsigned int flags;
+  unsigned long flags;
   int clid_len;          /* length of client identifier */
   unsigned char *clid;   /* clientid */
   char *hostname, *domain;
   struct dhcp_netid_list *netid;
 #ifdef HAVE_DHCP6
-  struct in6_addr addr6;
+  struct in6_addr addr6; /* internal only, user opts in addr6_list */
+  struct in6_addr_list *addr6_list;
 #endif
   struct in_addr addr;
   time_t decline_time;
@@ -790,6 +800,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_ADDR6_CAND    32768    /* IPv6 address candidate(s) available */
 
 struct dhcp_opt {
   int opt, len, flags;
diff --git a/src/option.c b/src/option.c
index f110b75..28a1f3f 100644
--- a/src/option.c
+++ b/src/option.c
@@ -1015,6 +1015,16 @@ static void dhcp_netid_list_free(struct dhcp_netid_list *netid)
     }
 }
 
+static void dhcp_addr6_list_free(struct in6_addr_list *addr6_list)
+{
+  while (addr6_list)
+    {
+      struct in6_addr_list *tmp = addr6_list;
+      addr6_list = addr6_list->next;
+      free(tmp);
+    }
+}
+
 static void dhcp_config_free(struct dhcp_config *config)
 {
   if (config)
@@ -1027,6 +1037,9 @@ static void dhcp_config_free(struct dhcp_config *config)
 	  free(tmp);
         }
       dhcp_netid_list_free(config->netid);
+#ifdef HAVE_DHCP6
+      dhcp_addr6_list_free(config->addr6_list);
+#endif
       if (config->flags & CONFIG_CLID)
         free(config->clid);
       free(config);
@@ -3264,24 +3277,47 @@ 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] == ']')
 		{
-		  arg[strlen(arg)-1] = 0;
-		  arg++;
-		  
-		  if (!inet_pton(AF_INET6, arg, &new->addr6))
+		  char *closing_bracket;
+		  char *prefix;
+		  while (strlen(arg))
 		    {
-		      dhcp_config_free(new);
-		      ret_err(_("bad IPv6 address"));
+		      struct in6_addr_list *newaddr6 = malloc(sizeof(struct in6_addr_list));
+		      newaddr6->next = new->addr6_list;
+		      new->addr6_list = newaddr6;
+		      new->addr6_list->flags = (option == LOPT_BANK) ? CONFIG_BANK : 0;
+
+		      closing_bracket = split_chr(arg, ']'); /* split and dump closing bracket */
+		      arg++; /* dump open bracket */
+
+		      prefix = split_chr(arg, '/');
+		      if (prefix == NULL)
+		        prefix = "64";
+		      new->addr6_list->prefix = atoi(prefix);
+
+		      if (!inet_pton(AF_INET6, arg, &new->addr6_list->addr6))
+		        {
+		          dhcp_config_free(new);
+		          ret_err(_("bad IPv6 address"));
+		        }
+
+		      if ((new->addr6_list->prefix <= 63) &&
+		          (addr6part(&new->addr6_list->addr6) << new->addr6_list->prefix) != 0)
+		        {
+		          dhcp_config_free(new);
+		          ret_err(_("bad IPv6 base address with prefix"));
+		        }
+
+		      for (i = 0; i < 8; i++)
+		        if (new->addr6_list->addr6.s6_addr[i] != 0)
+		          break;
+
+		      /* set WILDCARD if network part all zeros */
+		      if (i == 8)
+		          new->addr6_list->flags |= CONFIG_WILDCARD;
+
+		      strcpy(arg, closing_bracket);
 		    }
-
-		  for (i= 0; i < 8; i++)
-		    if (new->addr6.s6_addr[i] != 0)
-		      break;
-
-		  /* set WILDCARD if network part all zeros */
-		  if (i == 8)
-		    new->flags |= CONFIG_WILDCARD;
-		  
-		  new->flags |= CONFIG_ADDR6;
+		  new->flags |= CONFIG_ADDR6_CAND;
 		}
 #endif
 	      else
diff --git a/src/rfc3315.c b/src/rfc3315.c
index 9471f5c..2462efe 100644
--- a/src/rfc3315.c
+++ b/src/rfc3315.c
@@ -664,6 +664,72 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
 	    if (address_assigned)
 		address_assigned = 2;
 
+	    if (have_config(config, CONFIG_ADDR6_CAND))
+	      {
+		struct in6_addr_list *list;
+		struct in6_addr addr6;
+		int hextet;
+		u64 pbit;
+
+		/* unset CONFIG_ADDR6, config->addr6 */
+	        if (have_config(config, CONFIG_ADDR6))
+		  {
+		    config->flags ^= CONFIG_ADDR6;
+		    config->addr6 = addr6;
+		  }
+
+		for (list = config->addr6_list; list; list = list->next)
+		  {
+		    if ((list->flags & CONFIG_WILDCARD) && (state->context->prefix == 64))
+		      {
+		        addr6 = state->context->start6;
+		        setaddr6part(&addr6, addr6part(&list->addr6));
+		      }
+		    else
+		        addr6 = list->addr6;
+
+		    if ((list->prefix > 63) && check_address(state, &addr6))
+		      {
+			/* unleased address found, set config->addr6 + flags */
+		        memcpy(&config->addr6, &addr6, sizeof(struct in6_addr));
+		        config->flags |= CONFIG_ADDR6;
+		        break;
+		      }
+		    else if (list->prefix <= 63)
+		      {
+			/* get prefix bits and invert with XOR */
+		        u64 prefixbits = (0xFFFFFFFFFFFFFFFF << (64 - list->prefix)) ^ 0xFFFFFFFFFFFFFFFF;
+
+		        for (pbit = 0; pbit <= prefixbits; pbit++)
+		          {
+		            if (check_address(state, &addr6))
+		              {
+		                /* unleased address found, set config->addr6 + flags */
+		                memcpy(&config->addr6, &addr6, sizeof(struct in6_addr));
+		                config->flags |= CONFIG_ADDR6;
+		                break;
+		              }
+
+		            /* increment address */
+		            for (hextet = 15; hextet >= 0; --hextet)
+		              {
+		                if (addr6.s6_addr[hextet] < 255)
+		                  {
+		                    addr6.s6_addr[hextet]++;
+		                    break;
+		                  }
+		                  else
+		                    addr6.s6_addr[hextet] = 0;
+		              }
+
+		          }
+
+		        if (have_config(config, CONFIG_ADDR6))
+		          break;
+		      }
+		  }
+	      }
+
 	    for (ia_counter = 0; ia_option; ia_counter++, ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
 	      {
 		/* worry about alignment here. */
-- 
2.24.1






More information about the Dnsmasq-discuss mailing list