From e5a0f6a331759b9fa3fb915c3bafd0bccf5df5a9 Mon Sep 17 00:00:00 2001
From: Jasper Verschueren <jasper.verschueren@apart-audio.com>
Date: Tue, 20 Nov 2018 09:01:42 +0100
Subject: [PATCH] mDNS: conflict resolution added

If the host observes a response (after probing) containing RR's
that he thought were unique to him, there is a conflict. If a host
observes such conflict, it resets back to probing and the probing
procedures will resolve the conflict. (RFC6762 section 9)
---
 src/apps/mdns/mdns.c | 135 +++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 130 insertions(+), 5 deletions(-)

diff --git a/src/apps/mdns/mdns.c b/src/apps/mdns/mdns.c
index e350ea05a..451c679f0 100644
--- a/src/apps/mdns/mdns.c
+++ b/src/apps/mdns/mdns.c
@@ -1682,13 +1682,18 @@ mdns_probe_conflict(struct netif *netif)
 }
 
 /**
- * Handle response MDNS packet
- * Only prints debug for now. Will need more code to do conflict resolution.
+ * Handle response MDNS packet:
+ *  - Handle responses on probe query
+ *  - Perform conflict resolution on every packet (RFC6762 section 9)
+ *
+ * @param pkt   incoming packet
+ * @param netif network interface on which packet was received
  */
 static void
 mdns_handle_response(struct mdns_packet *pkt, struct netif *netif)
 {
   struct mdns_host* mdns = NETIF_TO_HOST(netif);
+  u16_t total_answers_left;
 
   /* Ignore responses with a source port different from 5353
    * (LWIP_IANA_PORT_MDNS) -> RFC6762 section 6 */
@@ -1707,12 +1712,13 @@ mdns_handle_response(struct mdns_packet *pkt, struct netif *netif)
       return;
     }
   }
-
-  while (pkt->answers_left) {
+  /* We need to check all resource record sections: answers, authoritative and additional */
+  total_answers_left = pkt->answers_left + pkt->authoritative_left + pkt->additional_left;
+  while (total_answers_left) {
     struct mdns_answer ans;
     err_t res;
 
-    res = mdns_read_answer(pkt, &ans, &pkt->answers_left);
+    res = mdns_read_answer(pkt, &ans, &total_answers_left);
     if (res != ERR_OK) {
       LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping response packet\n"));
       return;
@@ -1722,6 +1728,11 @@ mdns_handle_response(struct mdns_packet *pkt, struct netif *netif)
     mdns_domain_debug_print(&ans.info.domain);
     LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", ans.info.type, ans.info.klass));
 
+    if (ans.info.type == DNS_RRTYPE_ANY || ans.info.klass != DNS_RRCLASS_IN) {
+      /* Skip answers for ANY type or if class != IN */
+      continue;
+    }
+
     /* "Conflicting Multicast DNS responses received *before* the first probe
      * packet is sent MUST be silently ignored" so drop answer if we haven't
      * started probing yet. */
@@ -1751,9 +1762,123 @@ mdns_handle_response(struct mdns_packet *pkt, struct netif *netif)
 
       if (conflict != 0) {
         mdns_probe_conflict(netif);
+        break;
+      }
+    }
+    /* Perform conflict resolution (RFC6762 section 9):
+     * We assume a conflict if the hostname or service name matches the answers
+     * domain. Only if the rdata matches exactly we reset our assumption to no
+     * conflict. As stated in the RFC:
+     * What may be considered inconsistent is context sensitive, except that
+     * resource records with identical rdata are never considered inconsistent,
+     * even if they originate from different hosts.
+     */
+    else if ((mdns->state == MDNS_STATE_ANNOUNCING) ||
+             (mdns->state == MDNS_STATE_COMPLETE)) {
+      struct mdns_domain domain;
+      u8_t i;
+      u8_t conflict = 0;
+
+      /* Evaluate unique hostname records -> A and AAAA */
+      res = mdns_build_host_domain(&domain, mdns);
+      if (res == ERR_OK && mdns_domain_eq(&ans.info.domain, &domain)) {
+        LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response matches host domain, assuming conflict\n"));
+        /* This means a conflict has taken place, except when the packet contains
+         * exactly the same rdata. */
+        conflict = 1;
+        /* Evaluate rdata -> to see if it's a copy of our own data */
+        if (ans.info.type == DNS_RRTYPE_A) {
+#if LWIP_IPV4
+          if (ans.rd_length == sizeof(ip4_addr_t) &&
+              pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip4_addr(netif), ans.rd_length) == 0) {
+            LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response equals our own IPv4 address record -> no conflict\n"));
+            conflict = 0;
+          }
+#endif
+        }
+        else if (ans.info.type == DNS_RRTYPE_AAAA) {
+#if LWIP_IPV6
+          if (ans.rd_length == sizeof(ip6_addr_p_t)) {
+            for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+              if (pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip6_addr(netif, i), ans.rd_length) == 0) {
+                LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response equals our own iPv6 address record, num = %d -> no conflict\n",i));
+                conflict = 0;
+              }
+            }
+          }
+#endif
+        }
+      }
+      /* Evaluate unique service name records -> SRV and TXT */
+      for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+        struct mdns_service* service = mdns->services[i];
+        if (!service) {
+          continue;
+        }
+        res = mdns_build_service_domain(&domain, service, 1);
+        if ((res == ERR_OK) && mdns_domain_eq(&ans.info.domain, &domain)) {
+          LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response matches service domain, assuming conflict\n"));
+          /* This means a conflict has taken place, except when the packet contains
+           * exactly the same rdata. */
+          conflict = 1;
+          /* Evaluate rdata -> to see if it's a copy of our own data */
+          if (ans.info.type == DNS_RRTYPE_SRV) {
+            /* Read and compare to with our SRV record */
+            u16_t field16, len, read_pos;
+            struct mdns_domain srv_ans, my_ans;
+            read_pos = ans.rd_offset;
+            do {
+              /* Check priority field */
+              len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+              if (len != sizeof(field16) || lwip_ntohs(field16) != SRV_PRIORITY) {
+                break;
+              }
+              read_pos += len;
+              /* Check weight field */
+              len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+              if (len != sizeof(field16) || lwip_ntohs(field16) != SRV_WEIGHT) {
+                break;
+              }
+              read_pos += len;
+              /* Check port field */
+              len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+              if (len != sizeof(field16) || lwip_ntohs(field16) != service->port) {
+                break;
+              }
+              read_pos += len;
+              /* Check host field */
+              len = mdns_readname(pkt->pbuf, read_pos, &srv_ans);
+              mdns_build_host_domain(&my_ans, mdns);
+              if (len == MDNS_READNAME_ERROR || !mdns_domain_eq(&srv_ans, &my_ans)) {
+                break;
+              }
+              LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response equals our own SRV record -> no conflict\n"));
+              conflict = 0;
+            } while (0);
+          } else if (ans.info.type == DNS_RRTYPE_TXT) {
+            mdns_prepare_txtdata(service);
+            if (service->txtdata.length == ans.rd_length &&
+                pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.name, ans.rd_length) == 0) {
+              LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response equals our own TXT record -> no conflict\n"));
+              conflict = 0;
+            }
+          }
+        }
+      }
+      if (conflict != 0) {
+        /* Reset host to probing to reconfirm uniqueness */
+        LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: Conflict resolution -> reset to probing state\n"));
+        mdns->state = MDNS_STATE_PROBE_WAIT;
+        mdns->sent_num = 0;
+        sys_timeout(MDNS_INITIAL_PROBE_DELAY_MS, mdns_probe_and_announce, netif);
+        break;
       }
     }
   }
+  /* Clear all xxx_left variables because we parsed all answers */
+  pkt->answers_left = 0;
+  pkt->authoritative_left = 0;
+  pkt->additional_left = 0;
 }
 
 /**
-- 
2.19.1

