From 205a7d12ca0a354ac9cd5b520d5c5359fa9d8bc7 Mon Sep 17 00:00:00 2001
From: Christian Walther <walther@indel.ch>
Date: Mon, 16 Dec 2019 15:48:47 +0100
Subject: [PATCH] mdns: implement goodbye messages

When MDNS_RESP_USENETIF_EXTCALLBACK is set, they are automatically sent when the interface or link go down.

Co-authored-by: Fabian Cenedese <cenedese@indel.ch>
---
 src/apps/mdns/mdns.c              | 61 ++++++++++++++++++++++++++++++++++-----
 src/apps/mdns/mdns_out.c          | 28 ++++++++++++++++++
 src/include/lwip/apps/mdns.h      |  1 +
 src/include/lwip/apps/mdns_priv.h |  2 ++
 4 files changed, 84 insertions(+), 8 deletions(-)

diff --git a/src/apps/mdns/mdns.c b/src/apps/mdns/mdns.c
index d43e2974..fdf2f6d5 100644
--- a/src/apps/mdns/mdns.c
+++ b/src/apps/mdns/mdns.c
@@ -531,9 +531,10 @@ mdns_read_answer(struct mdns_packet *pkt, struct mdns_answer *answer, u16_t *num
  * Send unsolicited answer containing all our known data
  * @param netif The network interface to send on
  * @param destination The target address to send to (usually multicast address)
+ * @param goodbye Zero to send announce, nonzero to send goodbye packet
  */
 static void
-mdns_announce(struct netif *netif, const ip_addr_t *destination)
+mdns_announce(struct netif *netif, const ip_addr_t *destination, u8_t goodbye)
 {
   struct mdns_outmsg announce;
   int i;
@@ -541,6 +542,7 @@ mdns_announce(struct netif *netif, const ip_addr_t *destination)
 
   memset(&announce, 0, sizeof(announce));
   announce.cache_flush = 1;
+  announce.goodbye = goodbye;
 #if LWIP_IPV4
   if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) {
     announce.host_replies = REPLY_HOST_A | REPLY_HOST_PTR_V4;
@@ -2234,14 +2236,24 @@ mdns_netif_ext_status_callback(struct netif *netif, netif_nsc_reason_t reason, c
   }
 
   if (reason & LWIP_NSC_STATUS_CHANGED) {
-    if (args->status_changed.state != 0) {
-      mdns_resp_restart(netif);
+    if (netif->flags & NETIF_FLAG_LINK_UP) {
+      if (args->status_changed.state != 0) {
+        mdns_resp_restart(netif);
+      }
+      else {
+        mdns_resp_goodbye(netif);
+      }
     }
-    /* TODO: send goodbye message */
   }
   if (reason & LWIP_NSC_LINK_CHANGED) {
-    if (args->link_changed.state != 0) {
-      mdns_resp_restart(netif);
+    if (netif->flags & NETIF_FLAG_UP) {
+      if (args->link_changed.state != 0) {
+        mdns_resp_restart(netif);
+      }
+      else {
+        /* the link is probably gone already and the goodbye will go nowhere, but let's try anyway */
+        mdns_resp_goodbye(netif);
+      }
     }
   }
   if (reason & (LWIP_NSC_IPV4_ADDRESS_CHANGED | LWIP_NSC_IPV4_GATEWAY_CHANGED |
@@ -2835,18 +2847,51 @@ mdns_resp_announce(struct netif *netif)
   if (mdns->state >= MDNS_STATE_ANNOUNCING) {
     /* Announce on IPv6 and IPv4 */
 #if LWIP_IPV6
-    mdns_announce(netif, &v6group);
+    mdns_announce(netif, &v6group, 0);
     mdns_start_multicast_timeouts_ipv6(netif);
 #endif
 #if LWIP_IPV4
     if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) {
-      mdns_announce(netif, &v4group);
+      mdns_announce(netif, &v4group, 0);
       mdns_start_multicast_timeouts_ipv4(netif);
     }
 #endif
   } /* else: ip address changed while probing was ongoing? @todo reset counter to restart? */
 }
 
+/**
+ * @ingroup mdns
+ * Send unsolicited answer containing all our known data with ttl zero
+ * @param netif The network interface to send on
+ */
+void
+mdns_resp_goodbye(struct netif *netif)
+{
+  struct mdns_host* mdns;
+  LWIP_ASSERT_CORE_LOCKED();
+  LWIP_ERROR("mdns_resp_goodbye: netif != NULL", (netif != NULL), return);
+
+  mdns = NETIF_TO_HOST(netif);
+  if (mdns == NULL) {
+    return;
+  }
+
+  /* No need to say goodbye if we haven't announced yet. */
+  if (mdns->state >= MDNS_STATE_ANNOUNCING) {
+    /* Announce on IPv6 and IPv4 */
+#if LWIP_IPV6
+    mdns_announce(netif, &v6group, 1);
+    mdns_start_multicast_timeouts_ipv6(netif);
+#endif
+#if LWIP_IPV4
+    if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+      mdns_announce(netif, &v4group, 1);
+      mdns_start_multicast_timeouts_ipv4(netif);
+    }
+#endif
+  }
+}
+
 /** Register a callback function that is called if probing is completed successfully
  * or with a conflict. */
 void
diff --git a/src/apps/mdns/mdns_out.c b/src/apps/mdns/mdns_out.c
index cb97afe1..9bcfa427 100644
--- a/src/apps/mdns/mdns_out.c
+++ b/src/apps/mdns/mdns_out.c
@@ -259,6 +259,9 @@ mdns_add_a_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
     /* ttl of legacy answer may not be greater then 10 seconds */
     ttl = MDNS_TTL_10;
   }
+  else if (msg->goodbye) {
+    ttl = 0;
+  }
   LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with A record\n"));
   return mdns_add_answer(reply, &netif_mdns_data(netif)->name, DNS_RRTYPE_A, DNS_RRCLASS_IN, msg->cache_flush,
                          ttl, (const u8_t *) netif_ip4_addr(netif),
@@ -278,6 +281,9 @@ mdns_add_hostv4_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg
     /* ttl of legacy answer may not be greater then 10 seconds */
     ttl = MDNS_TTL_10;
   }
+  else if (msg->goodbye) {
+    ttl = 0;
+  }
   LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with v4 PTR record\n"));
   return mdns_add_answer(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN,
                          msg->cache_flush, ttl, NULL, 0, &netif_mdns_data(netif)->name);
@@ -296,6 +302,9 @@ mdns_add_aaaa_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
     /* ttl of legacy answer may not be greater then 10 seconds */
     ttl = MDNS_TTL_10;
   }
+  else if (msg->goodbye) {
+    ttl = 0;
+  }
   LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with AAAA record\n"));
   return mdns_add_answer(reply, &netif_mdns_data(netif)->name, DNS_RRTYPE_AAAA, DNS_RRCLASS_IN, msg->cache_flush,
                          ttl, (const u8_t *) netif_ip6_addr(netif, addrindex),
@@ -315,6 +324,9 @@ mdns_add_hostv6_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg
     /* ttl of legacy answer may not be greater then 10 seconds */
     ttl = MDNS_TTL_10;
   }
+  else if (msg->goodbye) {
+    ttl = 0;
+  }
   LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with v6 PTR record\n"));
   return mdns_add_answer(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN,
                          msg->cache_flush, ttl, NULL, 0, &netif_mdns_data(netif)->name);
@@ -333,6 +345,9 @@ mdns_add_cname_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
     /* ttl of legacy answer may not be greater then 10 seconds */
     ttl = MDNS_TTL_10;
   }
+  else if (msg->goodbye) {
+    ttl = 0;
+  }
   LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with CNAME record\n"));
   return mdns_add_answer(reply, &netif_mdns_data(netif)->alias, DNS_RRTYPE_CNAME, DNS_RRCLASS_IN, msg->cache_flush, ttl, NULL, 0, &netif_mdns_data(netif)->name);
 }
@@ -352,6 +367,9 @@ mdns_add_servicetype_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg
     /* ttl of legacy answer may not be greater then 10 seconds */
     ttl = MDNS_TTL_10;
   }
+  else if (msg->goodbye) {
+    ttl = 0;
+  }
   LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with service type PTR record\n"));
   return mdns_add_answer(reply, &service_dnssd, DNS_RRTYPE_PTR, DNS_RRCLASS_IN,
                          0, ttl, NULL, 0, &service_type);
@@ -371,6 +389,9 @@ mdns_add_servicename_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg
     /* ttl of legacy answer may not be greater then 10 seconds */
     ttl = MDNS_TTL_10;
   }
+  else if (msg->goodbye) {
+    ttl = 0;
+  }
   LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with service name PTR record\n"));
   return mdns_add_answer(reply, &service_type, DNS_RRTYPE_PTR, DNS_RRCLASS_IN,
                          0, ttl, NULL, 0, &service_instance);
@@ -396,6 +417,9 @@ mdns_add_srv_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
     /* ttl of legacy answer may not be greater then 10 seconds */
     ttl = MDNS_TTL_10;
   }
+  else if (msg->goodbye) {
+    ttl = 0;
+  }
   srvdata[0] = lwip_htons(SRV_PRIORITY);
   srvdata[1] = lwip_htons(SRV_WEIGHT);
   srvdata[2] = lwip_htons(service->port);
@@ -419,6 +443,9 @@ mdns_add_txt_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
     /* ttl of legacy answer may not be greater then 10 seconds */
     ttl = MDNS_TTL_10;
   }
+  else if (msg->goodbye) {
+    ttl = 0;
+  }
   LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with TXT record\n"));
   return mdns_add_answer(reply, &service_instance, DNS_RRTYPE_TXT, DNS_RRCLASS_IN,
                          msg->cache_flush, ttl, (u8_t *) &service->txtdata.name,
@@ -1053,6 +1080,7 @@ mdns_clear_outmsg(struct mdns_outmsg *outmsg)
   outmsg->unicast_reply_requested = 0;
   outmsg->legacy_query = 0;
   outmsg->probe_query_recv = 0;
+  outmsg->goodbye = 0;
   outmsg->host_questions = 0;
   outmsg->host_replies = 0;
   outmsg->host_reverse_v6_replies = 0;
diff --git a/src/include/lwip/apps/mdns.h b/src/include/lwip/apps/mdns.h
index 513ee19d..67976536 100644
--- a/src/include/lwip/apps/mdns.h
+++ b/src/include/lwip/apps/mdns.h
@@ -137,6 +137,7 @@ err_t mdns_resp_add_service_txtitem(struct mdns_service *service, const char *tx
 void mdns_resp_restart_delay(struct netif *netif, uint32_t delay);
 void mdns_resp_restart(struct netif *netif);
 void mdns_resp_announce(struct netif *netif);
+void mdns_resp_goodbye(struct netif *netif);
 
 /**
  * @ingroup mdns
diff --git a/src/include/lwip/apps/mdns_priv.h b/src/include/lwip/apps/mdns_priv.h
index bcd6db34..9015c11f 100644
--- a/src/include/lwip/apps/mdns_priv.h
+++ b/src/include/lwip/apps/mdns_priv.h
@@ -153,6 +153,8 @@ struct mdns_outmsg {
   /** If the query is a probe msg we need to respond immediately. Independent of
    *  the QU or QM flag. */
   u8_t probe_query_recv;
+  /** If goodbye message (ttl = 0) */
+  u8_t goodbye;
   /* Question bitmask for host information */
   u8_t host_questions;
   /* Questions bitmask per service */
-- 
2.11.0 (Apple Git-81)

