Hi!

I have already described similar problem back in year 2021 [1]. There exists race condition when higher count of clients starts at similar time and requests DHCP(v4). First two patches were already sent. I think I have sent also following patches already, but were not able to find them.

Anyway, some DHCP clients tend to handle badly situation, where dnsmasq first offers them some address. Then later in the process it realizes such address were offered also to someone else and denies acking it. We already have a workaround merged for IPv6 case, where it will ack instead different address. I think it would be nice also for IPv6 the same way, but I think it is not as urgent as for IPv4.

I have created a kind of half-lease. When it offers someone address, it makes it reserved for that address. That should prevent unnecessary race during startup, where some clients tend to receive NACK. When client continues with DHCPREQUEST, it converts a temporary lease to a permanent lease, which is then stored into lease file.

It solves a race in case more clients requests address than addresses on such network are available. When there are the same amount of temporary leases and remaining addresses, it reuses random temporary lease. Of course in such situation a failures are unavoidable. But I think it should attempt do the best available.

I am attaching also setup.sh, which I used to emulate starting multiple clients at similar time. I configured dnsmasq listening on virbr1 device and offering addresses. Then run that script as root and record communication in wireshark. Unlike current situation, there should not be any NACKs present. Even though ISC dhclient handles NACK well and is able to retry, unlike some netbooting firmware, which fails the boot in such situation.

Comments or testing would be welcome.

This is tracked on redhat bug #2028704 [2]. I have pushed those commits also to dhcp-temp-leases branch in github [3].

1. https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2021q4/015982.html
2. https://bugzilla.redhat.com/show_bug.cgi?id=2028704
3. https://github.com/InfrastructureServices/dnsmasq/tree/dhcp-temp-leases
From 3930e69abd272ea49e026ced78f8660564d34858 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemen...@redhat.com>
Date: Wed, 8 Dec 2021 00:39:30 +0100
Subject: [PATCH 1/6] Add icmp ping hash also when pinging requested address

Previously requested address were pinged always with hash 0, but pinged
from address_allocate with different hash. Try using the same hash for
the same client, regardless what source place it were used.
---
 src/dhcp.c    | 26 +++++++++++++++++---------
 src/dnsmasq.h |  1 +
 src/rfc2131.c |  3 ++-
 3 files changed, 20 insertions(+), 10 deletions(-)

diff --git a/src/dhcp.c b/src/dhcp.c
index 8e9c606..1cf0b5b 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -715,6 +715,21 @@ struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct i
   return NULL;
 }
 
+unsigned int ping_hash(unsigned char *hwaddr, int hw_len)
+{
+  int i;
+  unsigned int j = 0;
+  /* hash hwaddr: use the SDBM hashing algorithm.  Seems to give good
+     dispersal even with similarly-valued "strings". */
+  for (i = 0; i < hw_len; i++)
+    j = hwaddr[i] + (j << 6) + (j << 16) - j;
+
+  /* j == 0 is marker */
+  if (j == 0)
+    j = 1;
+  return j;
+}
+
 /* Check if and address is in use by sending ICMP ping.
    This wrapper handles a cache and load-limiting.
    Return is NULL is address in use, or a pointer to a cache entry
@@ -785,18 +800,11 @@ int address_allocate(struct dhcp_context *context,
 
   struct in_addr start, addr;
   struct dhcp_context *c, *d;
-  int i, pass;
+  int pass;
   unsigned int j; 
 
-  /* hash hwaddr: use the SDBM hashing algorithm.  Seems to give good
-     dispersal even with similarly-valued "strings". */ 
-  for (j = 0, i = 0; i < hw_len; i++)
-    j = hwaddr[i] + (j << 6) + (j << 16) - j;
+  j = ping_hash(hwaddr, hw_len);
 
-  /* j == 0 is marker */
-  if (j == 0)
-    j = 1;
-  
   for (pass = 0; pass <= 1; pass++)
     for (c = context; c; c = c->current)
       if (c->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index a8937ce..88ff980 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -1499,6 +1499,7 @@ struct dhcp_context *address_available(struct dhcp_context *context,
 struct dhcp_context *narrow_context(struct dhcp_context *context, 
 				    struct in_addr taddr,
 				    struct dhcp_netid *netids);
+unsigned int ping_hash(unsigned char *hwaddr, int hw_len);
 struct ping_result *do_icmp_ping(time_t now, struct in_addr addr,
 				 unsigned int hash, int loopback);
 int address_allocate(struct dhcp_context *context,
diff --git a/src/rfc2131.c b/src/rfc2131.c
index ecda2d3..a7ce872 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -1133,7 +1133,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 		   !config_find_by_address(daemon->dhcp_conf, lease->addr))
 	    mess->yiaddr = lease->addr;
 	  else if (opt && address_available(context, addr, tagif_netid) && !lease_find_by_addr(addr) && 
-		   !config_find_by_address(daemon->dhcp_conf, addr) && do_icmp_ping(now, addr, 0, loopback))
+		   !config_find_by_address(daemon->dhcp_conf, addr) &&
+		   do_icmp_ping(now, addr, ping_hash(emac, emac_len), loopback))
 	    mess->yiaddr = addr;
 	  else if (emac_len == 0)
 	    message = _("no unique-id");
-- 
2.36.1

From 5b4dcaad46b82fdf389a4d5a7909c654866ee471 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemen...@redhat.com>
Date: Wed, 8 Dec 2021 00:11:46 +0100
Subject: [PATCH 2/6] Simplify ICMP ping from dhcp

Do not export whole record with hash. Instead return just value
signalling when hash has matched.

Make a bit simpler search of free addresses in dhcp.
---
 src/dhcp.c    | 33 ++++++++++++++-------------------
 src/dnsmasq.h |  4 ++--
 2 files changed, 16 insertions(+), 21 deletions(-)

diff --git a/src/dhcp.c b/src/dhcp.c
index 1cf0b5b..3c185be 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -734,9 +734,8 @@ unsigned int ping_hash(unsigned char *hwaddr, int hw_len)
    This wrapper handles a cache and load-limiting.
    Return is NULL is address in use, or a pointer to a cache entry
    recording that it isn't. */
-struct ping_result *do_icmp_ping(time_t now, struct in_addr addr, unsigned int hash, int loopback)
+int do_icmp_ping(time_t now, struct in_addr addr, unsigned int hash, int loopback)
 {
-  static struct ping_result dummy;
   struct ping_result *r, *victim = NULL;
   int count, max = (int)(0.6 * (((float)PING_CACHE_TIME)/
 				((float)PING_WAIT)));
@@ -754,19 +753,14 @@ struct ping_result *do_icmp_ping(time_t now, struct in_addr addr, unsigned int h
       {
 	count++;
 	if (r->addr.s_addr == addr.s_addr)
-	  return r;
+	  return (1 + (hash == r->hash));
       }
   
   /* didn't find cached entry */
   if ((count >= max) || option_bool(OPT_NO_PING) || loopback)
-    {
       /* overloaded, or configured not to check, loopback interface, return "not in use" */
-      dummy.hash = hash;
-      return &dummy;
-    }
-  else if (icmp_ping(addr))
-    return NULL; /* address in use. */
-  else
+    return 2;
+  else if (!icmp_ping(addr))
     {
       /* at this point victim may hold an expired record */
       if (!victim)
@@ -785,9 +779,10 @@ struct ping_result *do_icmp_ping(time_t now, struct in_addr addr, unsigned int h
 	  victim->addr = addr;
 	  victim->time = now;
 	  victim->hash = hash;
+	  return 2;
 	}
-      return victim;
     }
+  return 0; /* address in use or no memory. */
 }
 
 int address_allocate(struct dhcp_context *context,
@@ -849,13 +844,13 @@ int address_allocate(struct dhcp_context *context,
 		  c->addr_epoch--;
 		else
 		  {
-		    struct ping_result *r;
+		    int r = do_icmp_ping(now, addr, j, loopback);
 		    
-		    if ((r = do_icmp_ping(now, addr, j, loopback)))
+		    if (r)
 		      {
 			/* consec-ip mode: we offered this address for another client
 			   (different hash) recently, don't offer it to this one. */
-			if (!option_bool(OPT_CONSEC_ADDR) || r->hash == j)
+			if (!option_bool(OPT_CONSEC_ADDR) || r > 1)
 			  {
 			    *addrp = addr;
 			    return 1;
@@ -870,12 +865,12 @@ int address_allocate(struct dhcp_context *context,
 		      }
 		  }
 	      }
-	    
-	    addr.s_addr = htonl(ntohl(addr.s_addr) + 1);
-	    
-	    if (addr.s_addr == htonl(ntohl(c->end.s_addr) + 1))
+
+	    if (addr.s_addr == c->end.s_addr)
 	      addr = c->start;
-	    
+	    else
+	      addr.s_addr = htonl(ntohl(addr.s_addr) + 1);
+
 	  } while (addr.s_addr != start.s_addr);
 	}
 
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 88ff980..04eac51 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -1500,8 +1500,8 @@ struct dhcp_context *narrow_context(struct dhcp_context *context,
 				    struct in_addr taddr,
 				    struct dhcp_netid *netids);
 unsigned int ping_hash(unsigned char *hwaddr, int hw_len);
-struct ping_result *do_icmp_ping(time_t now, struct in_addr addr,
-				 unsigned int hash, int loopback);
+int do_icmp_ping(time_t now, struct in_addr addr,
+		 unsigned int hash, int loopback);
 int address_allocate(struct dhcp_context *context,
 		     struct in_addr *addrp, unsigned char *hwaddr, int hw_len,
 		     struct dhcp_netid *netids, time_t now, int loopback);
-- 
2.36.1

From 7885f99da642306bc3ae65f591f36dd4a704918d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemen...@redhat.com>
Date: Fri, 10 Dec 2021 20:18:20 +0100
Subject: [PATCH 3/6] Create temporary leases on DISCOVER message

Previously only ping cache contained hash of few last pinged addresses.
They allowed skipping of address proposed to different host in
allocate_address. If no-ping were used together with dhcp-sequential-ip,
nothing would prevent offering single address to multiple clients.

Use temporary leases to store clients interested right when DHCPDISCOVER is
received. It makes sure that address is 'reserved' for that client when
he requests it. Uses short expiration time.
---
 src/dbus.c    |  2 +-
 src/dnsmasq.h |  3 ++-
 src/lease.c   | 25 +++++++++++++++++--------
 src/rfc2131.c | 30 ++++++++++++++++++++++++------
 4 files changed, 44 insertions(+), 16 deletions(-)

diff --git a/src/dbus.c b/src/dbus.c
index bf6b661..571743f 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -537,7 +537,7 @@ static DBusMessage *dbus_add_lease(DBusMessage* message)
 				      "ia_id and is_temporary must be zero for IPv4 lease");
       
       if (!(lease = lease_find_by_addr(addr.addr4)))
-    	lease = lease4_allocate(addr.addr4);
+    	lease = lease4_allocate(addr.addr4, 0);
     }
 #ifdef HAVE_DHCP6
   else if (inet_pton(AF_INET6, ipaddr, &addr.addr6))
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 04eac51..6241c61 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -795,6 +795,7 @@ struct frec {
 #define LEASE_TA            64  /* IPv6 temporary lease */
 #define LEASE_HAVE_HWADDR  128  /* Have set hwaddress */
 #define LEASE_EXP_CHANGED  256  /* Lease expiry time changed */
+#define LEASE_TEMP       0x200  /* Lease contains just DISCOVER reservation */
 
 struct dhcp_lease {
   int clid_len;          /* length of client identifier */
@@ -1515,7 +1516,7 @@ char *host_from_dns(struct in_addr addr);
 void lease_update_file(time_t now);
 void lease_update_dns(int force);
 void lease_init(time_t now);
-struct dhcp_lease *lease4_allocate(struct in_addr addr);
+struct dhcp_lease *lease4_allocate(struct in_addr addr, int temp);
 #ifdef HAVE_DHCP6
 struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type);
 struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len, 
diff --git a/src/lease.c b/src/lease.c
index 81477d5..f64149c 100644
--- a/src/lease.c
+++ b/src/lease.c
@@ -69,7 +69,7 @@ static int read_leases(time_t now, FILE *leasestream)
 		
 	if (inet_pton(AF_INET, daemon->namebuff, &addr.addr4))
 	  {
-	    if ((lease = lease4_allocate(addr.addr4)))
+	    if ((lease = lease4_allocate(addr.addr4, 0)))
 	      domain = get_domain(lease->addr);
 	    
 	    hw_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, DHCP_CHADDR_MAX, NULL, &hw_type);
@@ -264,6 +264,8 @@ void lease_update_file(time_t now)
       for (lease = leases; lease; lease = lease->next)
 	{
 
+	  if (lease->flags & LEASE_TEMP)
+	    continue;
 #ifdef HAVE_DHCP6
 	  if (lease->flags & (LEASE_TA | LEASE_NA))
 	    continue;
@@ -760,7 +762,7 @@ struct in_addr lease_find_max_addr(struct dhcp_context *context)
   return addr;
 }
 
-static struct dhcp_lease *lease_allocate(void)
+static struct dhcp_lease *lease_allocate(int temp)
 {
   struct dhcp_lease *lease;
   if (!leases_left || !(lease = whine_malloc(sizeof(struct dhcp_lease))))
@@ -775,16 +777,22 @@ static struct dhcp_lease *lease_allocate(void)
   lease->hwaddr_len = 256; /* illegal value */
   lease->next = leases;
   leases = lease;
-  
-  file_dirty = 1;
   leases_left--;
+  if (!temp)
+    {
+      file_dirty = 1;
+    }
+  else
+    {
+      lease->flags |= LEASE_TEMP;
+    }
 
   return lease;
 }
 
-struct dhcp_lease *lease4_allocate(struct in_addr addr)
+struct dhcp_lease *lease4_allocate(struct in_addr addr, int temp)
 {
-  struct dhcp_lease *lease = lease_allocate();
+  struct dhcp_lease *lease = lease_allocate(temp);
   if (lease)
     {
       lease->addr = addr;
@@ -797,7 +805,7 @@ struct dhcp_lease *lease4_allocate(struct in_addr addr)
 #ifdef HAVE_DHCP6
 struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type)
 {
-  struct dhcp_lease *lease = lease_allocate();
+  struct dhcp_lease *lease = lease_allocate(0);
 
   if (lease)
     {
@@ -883,7 +891,8 @@ void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
       lease->hwaddr_len = hw_len;
       lease->hwaddr_type = hw_type;
       lease->flags |= LEASE_CHANGED;
-      file_dirty = 1; /* run script on change */
+      if ((lease->flags & LEASE_TEMP) != 0)
+	file_dirty = 1; /* run script on change */
     }
 
   /* only update clid when one is available, stops packets
diff --git a/src/rfc2131.c b/src/rfc2131.c
index a7ce872..9e540fb 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -68,6 +68,22 @@ static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dh
 static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid);
 static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor);
 
+static struct dhcp_lease *temp_lease4_allocate(struct dhcp_context *context,
+		     struct dhcp_packet *mess, unsigned char *hwaddr, int hw_len,
+		     struct dhcp_netid *netids, time_t now, int loopback)
+{
+  struct dhcp_lease *lease;
+  if (!address_allocate(context, &mess->yiaddr, hwaddr, hw_len, netids, now, loopback))
+    return NULL;
+  lease = lease4_allocate(mess->yiaddr, 1);
+  if (lease)
+    {
+      lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0, now, 0);
+      lease_set_expires(lease, PING_CACHE_TIME, now);
+    }
+  return lease;
+}
+
 size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 		  size_t sz, time_t now, int unicast_dest, int loopback,
 		  int *is_inform, int pxe, struct in_addr fallback, time_t recvtime)
@@ -623,7 +639,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 		       lease_prune(lease, now);
 		       lease = NULL;
 		     }
-		   if (!address_allocate(context, &mess->yiaddr, mess->chaddr, mess->hlen, tagif_netid, now, loopback))
+		   if (!address_allocate(context, &mess->yiaddr, mess->chaddr,
+					 mess->hlen, tagif_netid, now, loopback))
 		     message = _("no address available");
 		}
 	      else
@@ -651,7 +668,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 
 	  if (!message && 
 	      !lease && 
-	      (!(lease = lease4_allocate(mess->yiaddr))))
+	      (!(lease = lease4_allocate(mess->yiaddr, 0))))
 	    message = _("no leases left");
 	  
 	  if (!message)
@@ -662,7 +679,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 	      if (hostname)
 		lease_set_hostname(lease, hostname, 1, get_domain(lease->addr), domain); 
 	      /* infinite lease unless nailed in dhcp-host line. */
-	      lease_set_expires(lease,  
+	      lease_set_expires(lease,
 				have_config(config, CONFIG_TIME) ? config->lease_time : 0xffffffff, 
 				now); 
 	      lease_set_interface(lease, int_index, now);
@@ -1138,8 +1155,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 	    mess->yiaddr = addr;
 	  else if (emac_len == 0)
 	    message = _("no unique-id");
-	  else if (!address_allocate(context, &mess->yiaddr, emac, emac_len, tagif_netid, now, loopback))
-	    message = _("no address available");      
+	  else if (!temp_lease4_allocate(context, mess, emac, emac_len, tagif_netid, now, loopback))
+	    message = _("no address available");
 	}
       
       daemon->metrics[METRIC_DHCPDISCOVER]++;
@@ -1341,7 +1358,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 	      
 	      else if (!lease)
 		{	     
-		  if ((lease = lease4_allocate(mess->yiaddr)))
+		  if ((lease = lease4_allocate(mess->yiaddr, 0)))
 		    do_classes = 1;
 		  else
 		    message = _("no leases left");
@@ -1472,6 +1489,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 	    }
 	  
 	  time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4));
+	  lease->flags &= ~LEASE_TEMP;
 	  lease_set_hwaddr(lease, mess->chaddr, clid, mess->hlen, mess->htype, clid_len, now, do_classes);
 	  
 	  /* if all the netids in the ignore_name list are present, ignore client-supplied name */
-- 
2.36.1

From 253d4b647d42ba624a4417e361748c7418220385 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemen...@redhat.com>
Date: Sat, 11 Dec 2021 05:52:49 +0100
Subject: [PATCH 4/6] Reuse temporary leases if no free address remains

When more clients sends DISCOVER message than we have free addresses, we
need to offer addresses already offered to someone else. It allows
offering at least something when address pool is almost empty. Someone
will probably do not receive address in the end, but that is best
attempt possible.
---
 src/dhcp.c    | 195 +++++++++++++++++++++++++++++++++-----------------
 src/dnsmasq.h |   4 +-
 src/rfc2131.c |  14 ++--
 3 files changed, 140 insertions(+), 73 deletions(-)

diff --git a/src/dhcp.c b/src/dhcp.c
index 3c185be..98a34d9 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -785,96 +785,157 @@ int do_icmp_ping(time_t now, struct in_addr addr, unsigned int hash, int loopbac
   return 0; /* address in use or no memory. */
 }
 
+static struct in_addr dhcp_context_start_addr(struct dhcp_context *c, unsigned int hash)
+{
+  struct in_addr start;
+  if (option_bool(OPT_CONSEC_ADDR))
+    /* seed is largest extant lease addr in this context */
+    start = lease_find_max_addr(c);
+  else
+    /* pick a seed based on hwaddr */
+    start.s_addr = htonl(ntohl(c->start.s_addr) +
+			  ((hash + c->addr_epoch) % (1 + ntohl(c->end.s_addr) - ntohl(c->start.s_addr))));
+  return start;
+}
+
+static int dhcp_context_my_addr(const struct dhcp_context *context, struct in_addr addr)
+{
+  const struct dhcp_context *d;
+
+  /* eliminate addresses in use by the server. */
+  for (d = context; d; d = d->current)
+    if (addr.s_addr == d->router.s_addr)
+      return 1;
+  return 0;
+}
+
+static struct in_addr dhcp_context_next_addr(const struct dhcp_context *c,
+					     struct in_addr addr)
+{
+  if (addr.s_addr == c->end.s_addr)
+    addr = c->start;
+  else
+    addr.s_addr = htonl(ntohl(addr.s_addr) + 1);
+  return addr;
+}
+
+/* Addresses which end in .255 and .0 are broken in Windows even when using
+   supernetting. ie dhcp-range=192.168.0.1,192.168.1.254,255,255,254.0
+   then 192.168.0.255 is a valid IP address, but not for Windows as it's
+   in the class C range. See  KB281579. We therefore don't allocate these
+   addresses to avoid hard-to-diagnose problems. Thanks Bill. */
+static int bug_kb281579(struct in_addr addr)
+{
+  unsigned int h;
+  return (IN_CLASSC(ntohl(addr.s_addr)) &&
+	  ((h=ntohl(addr.s_addr) & 0xff) == 0xff || h == 0x0));
+}
+
+static int address_free(struct dhcp_context *c,
+		     struct in_addr addr, unsigned int j,
+		     time_t now, int loopback)
+{
+  if (	!lease_find_by_addr(addr) &&
+	!config_find_by_address(daemon->dhcp_conf, addr) &&
+	!bug_kb281579(addr))
+      {
+	/* in consec-ip mode, skip addresses equal to
+	   the number of addresses rejected by clients. This
+	   should avoid the same client being offered the same
+	   address after it has rjected it. */
+	if (option_bool(OPT_CONSEC_ADDR) && c->addr_epoch)
+	  c->addr_epoch--;
+	else
+	  {
+	    int r = do_icmp_ping(now, addr, j, loopback);
+
+	    if (r)
+	      {
+		/* consec-ip mode: we offered this address for another client
+		   (different hash) recently, don't offer it to this one. */
+		if (!option_bool(OPT_CONSEC_ADDR) || r > 1)
+		  return 1;
+	      }
+	    else
+	      {
+		/* address in use: perturb address selection so that we are
+		   less likely to try this address again. */
+		if (!option_bool(OPT_CONSEC_ADDR))
+		  c->addr_epoch++;
+	      }
+	  }
+      }
+  return 0;
+}
+
 int address_allocate(struct dhcp_context *context,
-		     struct in_addr *addrp, unsigned char *hwaddr, int hw_len, 
-		     struct dhcp_netid *netids, time_t now, int loopback)   
+		     struct in_addr *addrp, unsigned int hash,
+		     struct dhcp_netid *netids, time_t now, int loopback)
 {
   /* Find a free address: exclude anything in use and anything allocated to
      a particular hwaddr/clientid/hostname in our configuration.
      Try to return from contexts which match netids first. */
 
   struct in_addr start, addr;
-  struct dhcp_context *c, *d;
+  struct dhcp_context *c;
   int pass;
-  unsigned int j; 
-
-  j = ping_hash(hwaddr, hw_len);
 
   for (pass = 0; pass <= 1; pass++)
     for (c = context; c; c = c->current)
       if (c->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
 	continue;
-      else if (!match_netid(c->filter, netids, pass))
-	continue;
-      else
+      else if (match_netid(c->filter, netids, pass))
 	{
-	  if (option_bool(OPT_CONSEC_ADDR))
-	    /* seed is largest extant lease addr in this context */
-	    start = lease_find_max_addr(c);
-	  else
-	    /* pick a seed based on hwaddr */
-	    start.s_addr = htonl(ntohl(c->start.s_addr) + 
-				 ((j + c->addr_epoch) % (1 + ntohl(c->end.s_addr) - ntohl(c->start.s_addr))));
-
 	  /* iterate until we find a free address. */
-	  addr = start;
-	  
-	  do {
-	    /* eliminate addresses in use by the server. */
-	    for (d = context; d; d = d->current)
-	      if (addr.s_addr == d->router.s_addr)
-		break;
+	  addr = start = dhcp_context_start_addr(c, hash);
 
-	    /* Addresses which end in .255 and .0 are broken in Windows even when using 
-	       supernetting. ie dhcp-range=192.168.0.1,192.168.1.254,255,255,254.0
-	       then 192.168.0.255 is a valid IP address, but not for Windows as it's
-	       in the class C range. See  KB281579. We therefore don't allocate these 
-	       addresses to avoid hard-to-diagnose problems. Thanks Bill. */	    
-	    if (!d &&
-		!lease_find_by_addr(addr) && 
-		!config_find_by_address(daemon->dhcp_conf, addr) &&
-		(!IN_CLASSC(ntohl(addr.s_addr)) || 
-		 ((ntohl(addr.s_addr) & 0xff) != 0xff && ((ntohl(addr.s_addr) & 0xff) != 0x0))))
+	  do {
+	    if (!dhcp_context_my_addr(context, addr) &&
+		address_free(c, addr, hash, now, loopback))
 	      {
-		/* in consec-ip mode, skip addresses equal to
-		   the number of addresses rejected by clients. This
-		   should avoid the same client being offered the same
-		   address after it has rjected it. */
-		if (option_bool(OPT_CONSEC_ADDR) && c->addr_epoch)
-		  c->addr_epoch--;
-		else
-		  {
-		    int r = do_icmp_ping(now, addr, j, loopback);
-		    
-		    if (r)
-		      {
-			/* consec-ip mode: we offered this address for another client
-			   (different hash) recently, don't offer it to this one. */
-			if (!option_bool(OPT_CONSEC_ADDR) || r > 1)
-			  {
-			    *addrp = addr;
-			    return 1;
-			  }
-		      }
-		    else
-		      {
-			/* address in use: perturb address selection so that we are
-			   less likely to try this address again. */
-			if (!option_bool(OPT_CONSEC_ADDR))
-			  c->addr_epoch++;
-		      }
-		  }
+		*addrp = addr;
+		return 1;
 	      }
+	    addr = dhcp_context_next_addr(c, addr);
+	  } while (addr.s_addr != start.s_addr);
+	}
 
-	    if (addr.s_addr == c->end.s_addr)
-	      addr = c->start;
-	    else
-	      addr.s_addr = htonl(ntohl(addr.s_addr) + 1);
+  return 0;
+}
+
+struct dhcp_lease *find_temp_lease(struct dhcp_context *context, unsigned int hash,
+		     struct dhcp_netid *netids)
+{
+  /* Find a free address: exclude anything in use and anything allocated to
+     a particular hwaddr/clientid/hostname in our configuration.
+     Try to return from contexts which match netids first. */
+
+  struct in_addr start, addr;
+  struct dhcp_context *c;
+  int pass;
+  struct dhcp_lease *lease;
+
+  for (pass = 0; pass <= 1; pass++)
+    for (c = context; c; c = c->current)
+      if (c->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
+	continue;
+      else if (match_netid(c->filter, netids, pass))
+	{
+	  /* iterate until we find a free address. */
+	  addr = start = dhcp_context_start_addr(c, hash);
+
+	  do {
+	    if (!dhcp_context_my_addr(context, addr) &&
+		!config_find_by_address(daemon->dhcp_conf, addr) &&
+		(lease = lease_find_by_addr(addr)) &&
+		(lease->flags & LEASE_TEMP))
+	      return lease;
 
+	    addr = dhcp_context_next_addr(c, addr);
 	  } while (addr.s_addr != start.s_addr);
 	}
 
-  return 0;
+  return NULL;
 }
 
 void dhcp_read_ethers(void)
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 6241c61..fbf69fc 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -1504,8 +1504,10 @@ unsigned int ping_hash(unsigned char *hwaddr, int hw_len);
 int do_icmp_ping(time_t now, struct in_addr addr,
 		 unsigned int hash, int loopback);
 int address_allocate(struct dhcp_context *context,
-		     struct in_addr *addrp, unsigned char *hwaddr, int hw_len,
+		     struct in_addr *addrp, unsigned int ping_hash,
 		     struct dhcp_netid *netids, time_t now, int loopback);
+struct dhcp_lease *find_temp_lease(struct dhcp_context *context,
+				   unsigned int hash, struct dhcp_netid *netids);
 void dhcp_read_ethers(void);
 struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct in_addr addr);
 char *host_from_dns(struct in_addr addr);
diff --git a/src/rfc2131.c b/src/rfc2131.c
index 9e540fb..10d345e 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -73,9 +73,13 @@ static struct dhcp_lease *temp_lease4_allocate(struct dhcp_context *context,
 		     struct dhcp_netid *netids, time_t now, int loopback)
 {
   struct dhcp_lease *lease;
-  if (!address_allocate(context, &mess->yiaddr, hwaddr, hw_len, netids, now, loopback))
-    return NULL;
-  lease = lease4_allocate(mess->yiaddr, 1);
+  unsigned int hash = ping_hash(hwaddr, hw_len);
+  if (!address_allocate(context, &mess->yiaddr, hash, netids, now, loopback))
+    /* When no unused address is available, reuse first temporary lease
+     * with matching address. */
+    lease = find_temp_lease(context, hash, netids);
+  else
+    lease = lease4_allocate(mess->yiaddr, 1);
   if (lease)
     {
       lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0, now, 0);
@@ -639,8 +643,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 		       lease_prune(lease, now);
 		       lease = NULL;
 		     }
-		   if (!address_allocate(context, &mess->yiaddr, mess->chaddr,
-					 mess->hlen, tagif_netid, now, loopback))
+		   if (!address_allocate(context, &mess->yiaddr, ping_hash(mess->chaddr, mess->hlen),
+					 tagif_netid, now, loopback))
 		     message = _("no address available");
 		}
 	      else
-- 
2.36.1

From 2bd6ad80beb240cd681904fce1779b14a012f629 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemen...@redhat.com>
Date: Mon, 13 Dec 2021 15:12:04 +0100
Subject: [PATCH 5/6] Ensure temporary leases are ignored on save

Mark lease as permanent after ack. Ensure it is flagged as modified if
previous lease were temporary. Ensures lease script woud be run always
on ack only, temporary leases are not propagated to script or dbus.

Reuse repeated parts of lease write code, move common parts to
functions.
---
 src/dnsmasq.h |   1 +
 src/lease.c   | 150 ++++++++++++++++++++++++--------------------------
 src/rfc2131.c |   2 +-
 3 files changed, 75 insertions(+), 78 deletions(-)

diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index fbf69fc..1ba3cf2 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -1539,6 +1539,7 @@ void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
 void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, char *domain, char *config_domain);
 void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now);
 void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now);
+void lease_set_permanent(struct dhcp_lease *lease);
 struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int hw_type,  
 					unsigned char *clid, int clid_len);
 struct dhcp_lease *lease_find_by_addr(struct in_addr addr);
diff --git a/src/lease.c b/src/lease.c
index f64149c..bd23b0e 100644
--- a/src/lease.c
+++ b/src/lease.c
@@ -248,11 +248,43 @@ static void ourprintf(int *errp, char *format, ...)
   va_end(ap);
 }
 
+static void lease_print_id(unsigned char *id, int id_len, const char *delim, int *errp)
+{
+  int i;
+
+  for (i = 0; i < id_len - 1; i++)
+    ourprintf(errp, "%.2x:", id[i]);
+  ourprintf(errp, "%.2x%s", id[i], delim);
+}
+
+static void lease_dump_begin(struct dhcp_lease *lease, int *errp)
+{
+#ifdef HAVE_BROKEN_RTC
+  ourprintf(errp, "%u ", lease->length);
+#else
+  ourprintf(errp, "%lu ", (unsigned long)lease->expires);
+#endif
+
+  if (lease->hwaddr_type != ARPHRD_ETHER || lease->hwaddr_len == 0)
+    ourprintf(errp, "%.2x-", lease->hwaddr_type);
+  lease_print_id(lease->hwaddr, lease->hwaddr_len, "", errp);
+}
+
+static void lease_dump_end(struct dhcp_lease *lease, int *errp)
+{
+  ourprintf(errp, "%s ", lease->hostname ? lease->hostname : "*");
+
+  if (lease->clid && lease->clid_len != 0)
+    lease_print_id(lease->clid, lease->clid_len, "\n", errp);
+  else
+    ourprintf(errp, "*\n");
+}
+
 void lease_update_file(time_t now)
 {
   struct dhcp_lease *lease;
   time_t next_event;
-  int i, err = 0;
+  int err = 0;
 
   if (file_dirty != 0 && daemon->lease_stream)
     {
@@ -263,79 +295,35 @@ void lease_update_file(time_t now)
       
       for (lease = leases; lease; lease = lease->next)
 	{
-
-	  if (lease->flags & LEASE_TEMP)
-	    continue;
 #ifdef HAVE_DHCP6
 	  if (lease->flags & (LEASE_TA | LEASE_NA))
 	    continue;
 #endif
 
-#ifdef HAVE_BROKEN_RTC
-	  ourprintf(&err, "%u ", lease->length);
-#else
-	  ourprintf(&err, "%lu ", (unsigned long)lease->expires);
-#endif
-
-	  if (lease->hwaddr_type != ARPHRD_ETHER || lease->hwaddr_len == 0) 
-	    ourprintf(&err, "%.2x-", lease->hwaddr_type);
-	  for (i = 0; i < lease->hwaddr_len; i++)
+	  if (!(lease->flags & LEASE_TEMP))
 	    {
-	      ourprintf(&err, "%.2x", lease->hwaddr[i]);
-	      if (i != lease->hwaddr_len - 1)
-		ourprintf(&err, ":");
+	      lease_dump_begin(lease, &err);
+	      inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN);
+	      ourprintf(&err, " %s ", daemon->addrbuff);
+	      lease_dump_end(lease, &err);
 	    }
-	  
-	  inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN); 
-
-	  ourprintf(&err, " %s ", daemon->addrbuff);
-	  ourprintf(&err, "%s ", lease->hostname ? lease->hostname : "*");
-	  	  
-	  if (lease->clid && lease->clid_len != 0)
-	    {
-	      for (i = 0; i < lease->clid_len - 1; i++)
-		ourprintf(&err, "%.2x:", lease->clid[i]);
-	      ourprintf(&err, "%.2x\n", lease->clid[i]);
-	    }
-	  else
-	    ourprintf(&err, "*\n");	  
 	}
       
 #ifdef HAVE_DHCP6  
       if (daemon->duid)
 	{
 	  ourprintf(&err, "duid ");
-	  for (i = 0; i < daemon->duid_len - 1; i++)
-	    ourprintf(&err, "%.2x:", daemon->duid[i]);
-	  ourprintf(&err, "%.2x\n", daemon->duid[i]);
-	  
-	  for (lease = leases; lease; lease = lease->next)
-	    {
-	      
-	      if (!(lease->flags & (LEASE_TA | LEASE_NA)))
-		continue;
+	  lease_print_id(daemon->duid, daemon->duid_len, "\n", &err);
 
-#ifdef HAVE_BROKEN_RTC
-	      ourprintf(&err, "%u ", lease->length);
-#else
-	      ourprintf(&err, "%lu ", (unsigned long)lease->expires);
-#endif
-    
-	      inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN);
-	 
-	      ourprintf(&err, "%s%u %s ", (lease->flags & LEASE_TA) ? "T" : "",
-			lease->iaid, daemon->addrbuff);
-	      ourprintf(&err, "%s ", lease->hostname ? lease->hostname : "*");
-	      
-	      if (lease->clid && lease->clid_len != 0)
-		{
-		  for (i = 0; i < lease->clid_len - 1; i++)
-		    ourprintf(&err, "%.2x:", lease->clid[i]);
-		  ourprintf(&err, "%.2x\n", lease->clid[i]);
-		}
-	      else
-		ourprintf(&err, "*\n");	  
-	    }
+	  for (lease = leases; lease; lease = lease->next)
+	    if (!(lease->flags & LEASE_TEMP) && lease->flags & (LEASE_TA | LEASE_NA))
+	      {
+		lease_dump_begin(lease, &err);
+		inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN);
+		ourprintf(&err, "%s%u %s ", (lease->flags & LEASE_TA) ? "T" : "",
+			  lease->iaid, daemon->addrbuff);
+		lease_dump_end(lease, &err);
+	      }
 	}
 #endif      
 	  
@@ -562,7 +550,8 @@ void lease_prune(struct dhcp_lease *target, time_t now)
       tmp = lease->next;
       if ((lease->expires != 0 && difftime(now, lease->expires) >= 0) || lease == target)
 	{
-	  file_dirty = 1;
+	  if ((lease->flags & LEASE_TEMP) == 0)
+	    file_dirty = 1;
 	  if (lease->hostname)
 	    dns_dirty = 1;
 
@@ -820,6 +809,13 @@ struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type)
 }
 #endif
 
+static void set_modified(struct dhcp_lease *lease, int flag)
+{
+  if ((lease->flags & LEASE_TEMP) == 0)
+    file_dirty = 1; /* run script on change */
+  lease->flags |= flag;
+}
+
 void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now)
 {
   time_t exp;
@@ -844,8 +840,7 @@ void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now)
       dns_dirty = 1;
       lease->expires = exp;
 #ifndef HAVE_BROKEN_RTC
-      lease->flags |= LEASE_AUX_CHANGED | LEASE_EXP_CHANGED;
-      file_dirty = 1;
+      set_modified(lease, LEASE_AUX_CHANGED | LEASE_EXP_CHANGED);
 #endif
     }
   
@@ -853,8 +848,7 @@ void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now)
   if (len != lease->length)
     {
       lease->length = len;
-      lease->flags |= LEASE_AUX_CHANGED;
-      file_dirty = 1; 
+      set_modified(lease, LEASE_AUX_CHANGED);
     }
 #endif
 } 
@@ -890,9 +884,7 @@ void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
 	memcpy(lease->hwaddr, hwaddr, hw_len);
       lease->hwaddr_len = hw_len;
       lease->hwaddr_type = hw_type;
-      lease->flags |= LEASE_CHANGED;
-      if ((lease->flags & LEASE_TEMP) != 0)
-	file_dirty = 1; /* run script on change */
+      set_modified(lease, LEASE_CHANGED);
     }
 
   /* only update clid when one is available, stops packets
@@ -905,8 +897,7 @@ void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
 
       if (lease->clid_len != clid_len)
 	{
-	  lease->flags |= LEASE_AUX_CHANGED;
-	  file_dirty = 1;
+	  set_modified(lease, LEASE_AUX_CHANGED);
 	  free(lease->clid);
 	  if (!(lease->clid = whine_malloc(clid_len)))
 	    return;
@@ -916,8 +907,7 @@ void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
 	}
       else if (memcmp(lease->clid, clid, clid_len) != 0)
 	{
-	  lease->flags |= LEASE_AUX_CHANGED;
-	  file_dirty = 1;
+	  set_modified(lease, LEASE_AUX_CHANGED);
 #ifdef HAVE_DHCP6
 	  change = 1;
 #endif	
@@ -933,6 +923,12 @@ void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
 #endif
 }
 
+void lease_set_permanent(struct dhcp_lease *lease)
+{
+  if (lease->flags & LEASE_TEMP)
+    set_modified(lease, LEASE_CHANGED);
+}
+
 static void kill_name(struct dhcp_lease *lease)
 {
   /* run script to say we lost our old name */
@@ -1044,9 +1040,8 @@ void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, ch
   if (auth)
     lease->flags |= LEASE_AUTH_NAME;
   
-  file_dirty = 1;
   dns_dirty = 1; 
-  lease->flags |= LEASE_CHANGED; /* run script on change */
+  set_modified(lease, LEASE_CHANGED);
 }
 
 void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now)
@@ -1145,9 +1140,10 @@ int do_script_run(time_t now)
       }
   
   for (lease = leases; lease; lease = lease->next)
-    if ((lease->flags & (LEASE_NEW | LEASE_CHANGED)) || 
-	((lease->flags & LEASE_AUX_CHANGED) && option_bool(OPT_LEASE_RO)) ||
-	((lease->flags & LEASE_EXP_CHANGED) && option_bool(OPT_LEASE_RENEW)))
+    if ((lease->flags & (LEASE_TEMP)) == 0 &&
+	((lease->flags & (LEASE_NEW | LEASE_CHANGED)) ||
+	 ((lease->flags & LEASE_AUX_CHANGED) && option_bool(OPT_LEASE_RO)) ||
+	 ((lease->flags & LEASE_EXP_CHANGED) && option_bool(OPT_LEASE_RENEW))))
       {
 #ifdef HAVE_SCRIPT
 	queue_script((lease->flags & LEASE_NEW) ? ACTION_ADD : ACTION_OLD, lease, 
diff --git a/src/rfc2131.c b/src/rfc2131.c
index 10d345e..9bf4053 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -1493,7 +1493,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 	    }
 	  
 	  time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4));
-	  lease->flags &= ~LEASE_TEMP;
+	  lease_set_permanent(lease);
 	  lease_set_hwaddr(lease, mess->chaddr, clid, mess->hlen, mess->htype, clid_len, now, do_classes);
 	  
 	  /* if all the netids in the ignore_name list are present, ignore client-supplied name */
-- 
2.36.1

From 58d5fe58b201292dcbfaf1981d54fc677c48eb65 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemen...@redhat.com>
Date: Mon, 13 Dec 2021 17:45:29 +0100
Subject: [PATCH 6/6] Handle case when temporary lease is from different subnet

Force single temporary lease if IP pool is almost exahusted and
there remains only temporary leases from different subnet. Track
temporary count because it iterates lists multiple times and could be
processor intensive. Avoid iterating in void if none temporary leases
can be freed.
---
 src/dnsmasq.h |  2 ++
 src/lease.c   | 34 ++++++++++++++++++++++++++++++----
 src/rfc2131.c | 21 +++++++++++++++++----
 3 files changed, 49 insertions(+), 8 deletions(-)

diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 1ba3cf2..69ddd7c 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -1545,6 +1545,8 @@ struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int h
 struct dhcp_lease *lease_find_by_addr(struct in_addr addr);
 struct in_addr lease_find_max_addr(struct dhcp_context *context);
 void lease_prune(struct dhcp_lease *target, time_t now);
+void lease_free_any_temporary(time_t now);
+int lease_have_temporary(void);
 void lease_update_from_configs(void);
 int do_script_run(time_t now);
 void rerun_scripts(void);
diff --git a/src/lease.c b/src/lease.c
index bd23b0e..00659d2 100644
--- a/src/lease.c
+++ b/src/lease.c
@@ -19,7 +19,7 @@
 #ifdef HAVE_DHCP
 
 static struct dhcp_lease *leases = NULL, *old_leases = NULL;
-static int dns_dirty, file_dirty, leases_left;
+static int dns_dirty, file_dirty, leases_left, lease_temporaries;
 
 static int read_leases(time_t now, FILE *leasestream)
 {
@@ -552,6 +552,8 @@ void lease_prune(struct dhcp_lease *target, time_t now)
 	{
 	  if ((lease->flags & LEASE_TEMP) == 0)
 	    file_dirty = 1;
+	  else
+	    lease_temporaries--;
 	  if (lease->hostname)
 	    dns_dirty = 1;
 
@@ -570,8 +572,26 @@ void lease_prune(struct dhcp_lease *target, time_t now)
 	up = &lease->next;
     }
 } 
-	
-  
+
+/* Find and release single temporary address. */
+void lease_free_any_temporary(time_t now)
+{
+  struct dhcp_lease *lease;
+
+  for (lease = leases; lease; lease = lease->next)
+    if (lease->flags & LEASE_TEMP)
+      {
+	lease_prune(lease, now);
+	break;
+      }
+}
+
+int lease_have_temporary(void)
+{
+  return (lease_temporaries > 0);
+}
+
+
 struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int hw_type,
 					unsigned char *clid, int clid_len)
 {
@@ -623,6 +643,7 @@ struct dhcp_lease *lease_find_by_addr(struct in_addr addr)
   return NULL;
 }
 
+
 #ifdef HAVE_DHCP6
 /* find address for {CLID, IAID, address} */
 struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len, 
@@ -774,6 +795,7 @@ static struct dhcp_lease *lease_allocate(int temp)
   else
     {
       lease->flags |= LEASE_TEMP;
+      lease_temporaries++;
     }
 
   return lease;
@@ -926,7 +948,11 @@ void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
 void lease_set_permanent(struct dhcp_lease *lease)
 {
   if (lease->flags & LEASE_TEMP)
-    set_modified(lease, LEASE_CHANGED);
+    {
+      lease->flags &= ~LEASE_TEMP;
+      set_modified(lease, LEASE_CHANGED);
+      lease_temporaries--;
+    }
 }
 
 static void kill_name(struct dhcp_lease *lease)
diff --git a/src/rfc2131.c b/src/rfc2131.c
index 9bf4053..b26791e 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -72,12 +72,25 @@ static struct dhcp_lease *temp_lease4_allocate(struct dhcp_context *context,
 		     struct dhcp_packet *mess, unsigned char *hwaddr, int hw_len,
 		     struct dhcp_netid *netids, time_t now, int loopback)
 {
-  struct dhcp_lease *lease;
+  struct dhcp_lease *lease = NULL;
   unsigned int hash = ping_hash(hwaddr, hw_len);
   if (!address_allocate(context, &mess->yiaddr, hash, netids, now, loopback))
-    /* When no unused address is available, reuse first temporary lease
-     * with matching address. */
-    lease = find_temp_lease(context, hash, netids);
+    {
+      if (lease_have_temporary())
+	{
+	  /* When no unused address is available, reuse first temporary lease
+	  * with matching address. */
+	  lease = find_temp_lease(context, hash, netids);
+	  if (!lease)
+	    {
+	      /* Not found temp lease in requested range.
+	       * Force free any temporary lease and retry. */
+	      lease_free_any_temporary(now);
+	      if (address_allocate(context, &mess->yiaddr, hash, netids, now, loopback))
+		lease = lease4_allocate(mess->yiaddr, 1);
+	    }
+	}
+    }
   else
     lease = lease4_allocate(mess->yiaddr, 1);
   if (lease)
-- 
2.36.1

Attachment: setup.sh
Description: application/shellscript

_______________________________________________
Dnsmasq-discuss mailing list
Dnsmasq-discuss@lists.thekelleys.org.uk
https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss

Reply via email to