[Dnsmasq-discuss] [PATCH] Delay DHCP replies for Raspberry Pi clients

Floris Bos bos at je-eigen-domein.nl
Wed Mar 29 13:48:48 BST 2017


The PXE boot firmware implementation of the Raspberry Pi 3
has a bug causing it to fail if it receives replies
instantly.

As a workaround ensure there is a minimum delay of one
second if the client is a Pi.

On Linux it looks up the exact receive time of the UDP
packet with the SIOCGSTAMP ioctl to prevent multiple
delays if multiple packets come in around the same time,
or if there already was a delay caused by a ping check.

Signed-off-by: Floris Bos <bos at je-eigen-domein.nl>
---
 src/dhcp.c    |  19 +++++++++++
 src/dnsmasq.c | 102 +++++++++++++++++++++++++++++++++++-----------------------
 src/dnsmasq.h |   1 +
 3 files changed, 81 insertions(+), 41 deletions(-)

diff --git a/src/dhcp.c b/src/dhcp.c
index 08952c8..203d9d5 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -356,6 +356,25 @@ void dhcp_packet(time_t now, int pxe_fd)
 #ifdef HAVE_SOCKADDR_SA_LEN
   dest.sin_len = sizeof(struct sockaddr_in);
 #endif
+
+  if (mess->htype == ARPHRD_ETHER && mess->hlen == ETHER_ADDR_LEN &&
+      mess->chaddr[0] == 0xB8 && mess->chaddr[1] == 0x27 && mess->chaddr[2] == 0xEB)
+    {
+      /* Raspberry Pi 3 boot firmware has a bug in which it fails if it receives DHCP
+         replies instantly. As a workaround ensure there is a delay of at least a second */
+
+      int starttime = now;
+#ifdef HAVE_LINUX_NETWORK
+      struct timeval tv;
+
+      /* Use timestamp of the UDP packet received as start time for delay */
+      if (ioctl(fd, SIOCGSTAMP, &tv) == 0)
+        {
+          starttime = tv.tv_sec;
+        }
+#endif
+      delay_dhcp(starttime, 1, -1, 0, 0);
+    }
   
   if (pxe_fd)
     { 
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index d2cc7cc..6ae8296 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -1747,29 +1747,15 @@ int icmp_ping(struct in_addr addr)
 {
   /* Try and get an ICMP echo from a machine. */
 
-  /* Note that whilst in the three second wait, we check for 
-     (and service) events on the DNS and TFTP  sockets, (so doing that
-     better not use any resources our caller has in use...)
-     but we remain deaf to signals or further DHCP packets. */
-
-  /* There can be a problem using dnsmasq_time() to end the loop, since
-     it's not monotonic, and can go backwards if the system clock is
-     tweaked, leading to the code getting stuck in this loop and
-     ignoring DHCP requests. To fix this, we check to see if select returned
-     as a result of a timeout rather than a socket becoming available. We
-     only allow this to happen as many times as it takes to get to the wait time
-     in quarter-second chunks. This provides a fallback way to end loop. */ 
-
-  int fd, rc;
+  int fd;
   struct sockaddr_in saddr;
   struct { 
     struct ip ip;
     struct icmp icmp;
   } packet;
   unsigned short id = rand16();
-  unsigned int i, j, timeout_count;
+  unsigned int i, j;
   int gotreply = 0;
-  time_t start, now;
 
 #if defined(HAVE_LINUX_NETWORK) || defined (HAVE_SOLARIS_NETWORK)
   if ((fd = make_icmp_sock()) == -1)
@@ -1799,14 +1785,46 @@ int icmp_ping(struct in_addr addr)
   while (retry_send(sendto(fd, (char *)&packet.icmp, sizeof(struct icmp), 0, 
 			   (struct sockaddr *)&saddr, sizeof(saddr))));
   
-  for (now = start = dnsmasq_time(), timeout_count = 0; 
-       (difftime(now, start) < (float)PING_WAIT) && (timeout_count < PING_WAIT * 4);)
+  gotreply = delay_dhcp(dnsmasq_time(), PING_WAIT, fd, addr.s_addr, id);
+
+#if defined(HAVE_LINUX_NETWORK) || defined(HAVE_SOLARIS_NETWORK)
+  while (retry_send(close(fd)));
+#else
+  opt = 1;
+  setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt));
+#endif
+
+  return gotreply;
+}
+
+int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id)
+{
+  /* Delay processing DHCP packets for "sec" seconds counting from "start".
+     If "fd" is not -1 it will stop waiting if an ICMP echo reply is received
+     from "addr" with ICMP ID "id" and return 1 */
+
+  /* Note that whilst waiting, we check for
+     (and service) events on the DNS and TFTP  sockets, (so doing that
+     better not use any resources our caller has in use...)
+     but we remain deaf to signals or further DHCP packets. */
+
+  /* There can be a problem using dnsmasq_time() to end the loop, since
+     it's not monotonic, and can go backwards if the system clock is
+     tweaked, leading to the code getting stuck in this loop and
+     ignoring DHCP requests. To fix this, we check to see if select returned
+     as a result of a timeout rather than a socket becoming available. We
+     only allow this to happen as many times as it takes to get to the wait time
+     in quarter-second chunks. This provides a fallback way to end loop. */
+
+  int rc, timeout_count;
+  time_t now;
+
+  for (now = dnsmasq_time(), timeout_count = 0;
+       (difftime(now, start) <= (float)sec) && (timeout_count < sec * 4);)
     {
-      struct sockaddr_in faddr;
-      socklen_t len = sizeof(faddr);
-      
       poll_reset();
-      poll_listen(fd, POLLIN);
+      if (fd != -1)
+        poll_listen(fd, POLLIN);
       set_dns_listeners(now);
       set_log_writer();
       
@@ -1836,27 +1854,29 @@ int icmp_ping(struct in_addr addr)
       check_tftp_listeners(now);
 #endif
 
-      if (poll_check(fd, POLLIN) &&
-	  recvfrom(fd, &packet, sizeof(packet), 0,
-		   (struct sockaddr *)&faddr, &len) == sizeof(packet) &&
-	  saddr.sin_addr.s_addr == faddr.sin_addr.s_addr &&
-	  packet.icmp.icmp_type == ICMP_ECHOREPLY &&
-	  packet.icmp.icmp_seq == 0 &&
-	  packet.icmp.icmp_id == id)
-	{
-	  gotreply = 1;
-	  break;
-	}
+      if (fd != -1)
+        {
+          struct {
+            struct ip ip;
+            struct icmp icmp;
+          } packet;
+          struct sockaddr_in faddr;
+          socklen_t len = sizeof(faddr);
+
+          if (poll_check(fd, POLLIN) &&
+            recvfrom(fd, &packet, sizeof(packet), 0,
+		     (struct sockaddr *)&faddr, &len) == sizeof(packet) &&
+            addr == faddr.sin_addr.s_addr &&
+            packet.icmp.icmp_type == ICMP_ECHOREPLY &&
+            packet.icmp.icmp_seq == 0 &&
+            packet.icmp.icmp_id == id)
+            {
+              return 1;
+            }
+        }
     }
-  
-#if defined(HAVE_LINUX_NETWORK) || defined(HAVE_SOLARIS_NETWORK)
-  while (retry_send(close(fd)));
-#else
-  opt = 1;
-  setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt));
-#endif
 
-  return gotreply;
+  return 0;
 }
 #endif
 
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 6b44e53..556a73a 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -1341,6 +1341,7 @@ unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr,
 #ifdef HAVE_DHCP
 int make_icmp_sock(void);
 int icmp_ping(struct in_addr addr);
+int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id);
 #endif
 void queue_event(int event);
 void send_alarm(time_t event, time_t now);
-- 
2.7.4




More information about the Dnsmasq-discuss mailing list