From 71cd4d3ee5bd37faf66ce0b5efb94a4d585004da Mon Sep 17 00:00:00 2001
From: "David J. Fiddes" <D.J@fiddes.net>
Date: Thu, 3 Oct 2019 12:06:06 +0100
Subject: [PATCH] dhcp/dhcp6:  Ensure that DHCP/DHCPv6 DNS server updates don't
 overlap

This change changes the DHCP and DHCPv6 DNS server option update
processing so that both IPv4 and IPv6 servers can be held in the
list of active DNS servers. Previously when a DHCP offer is processed
the list of active DNS servers is replaced. This can lead to failed
DNS resolution when there is a connectivity over one IP protocol
but not over the other. This change improves the possibility of
DNS resolution falling back to a working DNS server.

Tests:
 - On LWIP configured to use 2 DNS server entries. Configure DHCP and
   DHCPv6 to have 0,1 or 2 DNS servers on each protcol. Ensure that
   the list of active DNS servers reflects the network configuration.
   In the case of overflow ensure that one protocol wins based on the
   first processed DHCP response.
 - On a network with intermittent IPv6 connectivity ensure that DNS
   resolution continues to function as DHCP and DHCPv6 updates are
   received.
---
 src/core/ipv4/dhcp.c  | 14 ++++++++++----
 src/core/ipv6/dhcp6.c | 21 ++++++++++++---------
 2 files changed, 22 insertions(+), 13 deletions(-)

diff --git a/src/core/ipv4/dhcp.c b/src/core/ipv4/dhcp.c
index 6da9cd6b..70f944b9 100644
--- a/src/core/ipv4/dhcp.c
+++ b/src/core/ipv4/dhcp.c
@@ -625,6 +625,7 @@ dhcp_handle_ack(struct netif *netif, struct dhcp_msg *msg_in)
 
 #if LWIP_DHCP_PROVIDE_DNS_SERVERS || LWIP_DHCP_GET_NTP_SRV
   u8_t n;
+  u8_t idx;
 #endif /* LWIP_DHCP_PROVIDE_DNS_SERVERS || LWIP_DHCP_GET_NTP_SRV */
 #if LWIP_DHCP_GET_NTP_SRV
   ip4_addr_t ntp_server_addrs[LWIP_DHCP_MAX_NTP_SERVERS];
@@ -693,10 +694,15 @@ dhcp_handle_ack(struct netif *netif, struct dhcp_msg *msg_in)
 
 #if LWIP_DHCP_PROVIDE_DNS_SERVERS
   /* DNS servers */
-  for (n = 0; (n < LWIP_DHCP_PROVIDE_DNS_SERVERS) && dhcp_option_given(dhcp, DHCP_OPTION_IDX_DNS_SERVER + n); n++) {
-    ip_addr_t dns_addr;
-    ip_addr_set_ip4_u32_val(dns_addr, lwip_htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_DNS_SERVER + n)));
-    dns_setserver(n, &dns_addr);
+  idx = 0;
+  for (n = 0; (n < LWIP_DHCP_PROVIDE_DNS_SERVERS) && dhcp_option_given(dhcp, DHCP_OPTION_IDX_DNS_SERVER + idx); n++) {
+    const ip_addr_t* dns_slot_ip = dns_getserver(n);
+    if (ip_addr_isany(dns_slot_ip) || IP_IS_V4(dns_slot_ip)) {
+      ip_addr_t dns_addr;
+      ip_addr_set_ip4_u32_val(dns_addr, lwip_htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_DNS_SERVER + idx)));
+      dns_setserver(n, &dns_addr);
+      idx++;
+    }
   }
 #endif /* LWIP_DHCP_PROVIDE_DNS_SERVERS */
 }
diff --git a/src/core/ipv6/dhcp6.c b/src/core/ipv6/dhcp6.c
index 002f688d..b18d6e9a 100644
--- a/src/core/ipv6/dhcp6.c
+++ b/src/core/ipv6/dhcp6.c
@@ -538,16 +538,19 @@ dhcp6_handle_config_reply(struct netif *netif, struct pbuf *p_msg_in)
 
     ip_addr_set_zero_ip6(&dns_addr);
     dns_addr6 = ip_2_ip6(&dns_addr);
-    for (n = 0, idx = op_start; (idx < op_start + op_len) && (n < LWIP_DHCP6_PROVIDE_DNS_SERVERS);
-         n++, idx += sizeof(struct ip6_addr_packed)) {
-      u16_t copied = pbuf_copy_partial(p_msg_in, dns_addr6, sizeof(struct ip6_addr_packed), idx);
-      if (copied != sizeof(struct ip6_addr_packed)) {
-        /* pbuf length mismatch */
-        return;
+    idx = op_start;
+    for (n = 0; (idx < op_start + op_len) && (n < LWIP_DHCP6_PROVIDE_DNS_SERVERS); n++ ) {
+      const ip_addr_t* dns_slot_ip = dns_getserver(n);
+      if (ip_addr_isany(dns_slot_ip) || IP_IS_V6(dns_slot_ip)) {
+        u16_t copied = pbuf_copy_partial(p_msg_in, dns_addr6, sizeof(struct ip6_addr_packed), idx);
+        if (copied != sizeof(struct ip6_addr_packed)) {
+          /* pbuf length mismatch */
+          return;
+        }
+        ip6_addr_assign_zone(dns_addr6, IP6_UNKNOWN, netif);
+        dns_setserver(n, &dns_addr);
+        idx += sizeof(struct ip6_addr_packed);
       }
-      ip6_addr_assign_zone(dns_addr6, IP6_UNKNOWN, netif);
-      /* @todo: do we need a different offset than DHCP(v4)? */
-      dns_setserver(n, &dns_addr);
     }
   }
   /* @ todo: parse and set Domain Search List */
-- 
2.21.0

