diff --git a/doc/mdns.txt b/doc/mdns.txt index c5929b76..0c28970d 100644 --- a/doc/mdns.txt +++ b/doc/mdns.txt @@ -57,11 +57,11 @@ Answers will use the supplied TTL (in seconds) MDNS allows UTF-8 names, but it is recommended to stay within ASCII, since the default case-insensitive comparison assumes this. -It is recommended to call this function after an IPv4 address has been set, -since there is currently no check if the v4 address is valid. +Call mdns_resp_announce() every time the IP address on the netif has changed. -Call mdns_resp_netif_settings_changed() every time the IP address -on the netif has changed. +Call mdns_resp_restart() every time the network interface comes up after being +down, for example cable connected after being disconnected, administrative +interface comes up after being down, or the device wakes up from sleep. To stop responding on a netif, run mdns_resp_remove_netif(struct netif *netif) @@ -108,6 +108,5 @@ and point them to .local:80 Relevant information will be sent as additional records to reduce number of requests required from a client. -Removing services is currently not supported. Services are removed when the -netif is removed. - +To remove a service from a netif, run + mdns_resp_del_service(struct netif *netif, s8_t slot) \ No newline at end of file diff --git a/src/apps/mdns/mdns.c b/src/apps/mdns/mdns.c index 12e078dc..8597103b 100644 --- a/src/apps/mdns/mdns.c +++ b/src/apps/mdns/mdns.c @@ -13,7 +13,7 @@ * Things left to implement: * ------------------------- * - * - Probing/conflict resolution + * - Tiebreaking for simultaneous probing * - Sending goodbye messages (zero ttl) - shutdown, DHCP lease about to expire, DHCP turned off... * - Checking that source address of unicast requests are on the same network * - Limiting multicast responses to 1 per second per resource record @@ -63,6 +63,7 @@ #include "lwip/mem.h" #include "lwip/prot/dns.h" #include "lwip/prot/iana.h" +#include "lwip/timeouts.h" #include @@ -102,6 +103,7 @@ static const ip_addr_t v6group = DNS_MQUERY_IPV6_GROUP_INIT; static u8_t mdns_netif_client_id; static struct udp_pcb *mdns_pcb; NETIF_DECLARE_EXT_CALLBACK(netif_callback) +static mdns_name_result_cb_t mdns_name_result_cb = NULL; #define NETIF_TO_HOST(netif) (struct mdns_host*)(netif_get_client_data(netif, mdns_netif_client_id)) @@ -135,6 +137,12 @@ NETIF_DECLARE_EXT_CALLBACK(netif_callback) /* Lookup for text info on service instance */ #define REPLY_SERVICE_TXT 0x80 +typedef enum { + MDNS_PROBING_NOT_STARTED, + MDNS_PROBING_ONGOING, + MDNS_PROBING_COMPLETE, +} mdns_probing_state; + static const char *dnssd_protos[] = { "_udp", /* DNSSD_PROTO_UDP */ "_tcp", /* DNSSD_PROTO_TCP */ @@ -168,6 +176,10 @@ struct mdns_host { struct mdns_service *services[MDNS_MAX_SERVICES]; /** TTL in seconds of A/AAAA/PTR replies */ u32_t dns_ttl; + /** Number of probes sent for the current name */ + u8_t probes_sent; + /** State in probing sequence */ + mdns_probing_state probing_state; }; /** Information about received packet */ @@ -191,7 +203,7 @@ struct mdns_packet { /** Number of unparsed questions */ u16_t questions_left; /** Number of answers in packet, - * (sum of normal, authorative and additional answers) + * (sum of normal, authoritative and additional answers) * read from packet header */ u16_t answers; /** Number of unparsed answers */ @@ -215,6 +227,8 @@ struct mdns_outpacket { u16_t questions; /** Number of normal answers written */ u16_t answers; + /** Number of authoritative answers written */ + u16_t authoritative; /** Number of additional answers written */ u16_t additional; /** Offsets for written domain names in packet. @@ -261,6 +275,9 @@ struct mdns_answer { u16_t rd_offset; }; +static err_t mdns_send_outpacket(struct mdns_outpacket *outpkt, u8_t flags); +static void mdns_probe(void* arg); + static err_t mdns_domain_add_label_base(struct mdns_domain *domain, u8_t len) { @@ -1279,13 +1296,14 @@ mdns_init_outpacket(struct mdns_outpacket *out, struct mdns_packet *in) * Add additional answers based on the selected answers * Send the packet */ -static void -mdns_send_outpacket(struct mdns_outpacket *outpkt) +static err_t +mdns_send_outpacket(struct mdns_outpacket *outpkt, u8_t flags) { struct mdns_service *service; - err_t res; + err_t res = ERR_ARG; int i; struct mdns_host *mdns = NETIF_TO_HOST(outpkt->netif); + u16_t answers = 0; /* Write answers to host questions */ #if LWIP_IPV4 @@ -1294,14 +1312,14 @@ mdns_send_outpacket(struct mdns_outpacket *outpkt) if (res != ERR_OK) { goto cleanup; } - outpkt->answers++; + answers++; } if (outpkt->host_replies & REPLY_HOST_PTR_V4) { res = mdns_add_hostv4_ptr_answer(outpkt, outpkt->cache_flush, outpkt->netif); if (res != ERR_OK) { goto cleanup; } - outpkt->answers++; + answers++; } #endif #if LWIP_IPV6 @@ -1313,7 +1331,7 @@ mdns_send_outpacket(struct mdns_outpacket *outpkt) if (res != ERR_OK) { goto cleanup; } - outpkt->answers++; + answers++; } } } @@ -1326,7 +1344,7 @@ mdns_send_outpacket(struct mdns_outpacket *outpkt) if (res != ERR_OK) { goto cleanup; } - outpkt->answers++; + answers++; } addrindex++; rev_addrs >>= 1; @@ -1346,7 +1364,7 @@ mdns_send_outpacket(struct mdns_outpacket *outpkt) if (res != ERR_OK) { goto cleanup; } - outpkt->answers++; + answers++; } if (outpkt->serv_replies[i] & REPLY_SERVICE_NAME_PTR) { @@ -1354,7 +1372,7 @@ mdns_send_outpacket(struct mdns_outpacket *outpkt) if (res != ERR_OK) { goto cleanup; } - outpkt->answers++; + answers++; } if (outpkt->serv_replies[i] & REPLY_SERVICE_SRV) { @@ -1362,7 +1380,7 @@ mdns_send_outpacket(struct mdns_outpacket *outpkt) if (res != ERR_OK) { goto cleanup; } - outpkt->answers++; + answers++; } if (outpkt->serv_replies[i] & REPLY_SERVICE_TXT) { @@ -1370,10 +1388,17 @@ mdns_send_outpacket(struct mdns_outpacket *outpkt) if (res != ERR_OK) { goto cleanup; } - outpkt->answers++; + answers++; } } + /* if this is a response, the data above is anwers, else this is a probe and the answers above goes into auth section */ + if ( flags & DNS_FLAG1_RESPONSE ) { + outpkt->answers += answers; + } else { + outpkt->authoritative += answers; + } + /* All answers written, add additional RRs */ for (i = 0; i < MDNS_MAX_SERVICES; ++i) { service = mdns->services[i]; @@ -1439,13 +1464,12 @@ mdns_send_outpacket(struct mdns_outpacket *outpkt) /* Write header */ memset(&hdr, 0, sizeof(hdr)); - hdr.flags1 = DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE; + hdr.flags1 = flags; + hdr.numquestions = lwip_htons(outpkt->questions); hdr.numanswers = lwip_htons(outpkt->answers); + hdr.numauthrr = lwip_htons(outpkt->authoritative); hdr.numextrarr = lwip_htons(outpkt->additional); - if (outpkt->legacy_query) { - hdr.numquestions = lwip_htons(1); - hdr.id = lwip_htons(outpkt->tx_id); - } + hdr.id = lwip_htons(outpkt->tx_id); pbuf_take(outpkt->pbuf, &hdr, sizeof(hdr)); /* Shrink packet */ @@ -1463,9 +1487,9 @@ mdns_send_outpacket(struct mdns_outpacket *outpkt) /* Send created packet */ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Sending packet, len=%d, unicast=%d\n", outpkt->write_offset, outpkt->unicast_reply)); if (outpkt->unicast_reply) { - udp_sendto_if(mdns_pcb, outpkt->pbuf, &outpkt->dest_addr, outpkt->dest_port, outpkt->netif); + res = udp_sendto_if(mdns_pcb, outpkt->pbuf, &outpkt->dest_addr, outpkt->dest_port, outpkt->netif); } else { - udp_sendto_if(mdns_pcb, outpkt->pbuf, mcast_destaddr, LWIP_IANA_PORT_MDNS, outpkt->netif); + res = udp_sendto_if(mdns_pcb, outpkt->pbuf, mcast_destaddr, LWIP_IANA_PORT_MDNS, outpkt->netif); } } @@ -1474,8 +1498,10 @@ cleanup: pbuf_free(outpkt->pbuf); outpkt->pbuf = NULL; } + return res; } + /** * Send unsolicited answer containing all our known data * @param netif The network interface to send on @@ -1515,7 +1541,7 @@ mdns_announce(struct netif *netif, const ip_addr_t *destination) announce.dest_port = LWIP_IANA_PORT_MDNS; SMEMCPY(&announce.dest_addr, destination, sizeof(announce.dest_addr)); - mdns_send_outpacket(&announce); + mdns_send_outpacket(&announce, DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE); } /** @@ -1534,6 +1560,12 @@ mdns_handle_question(struct mdns_packet *pkt) err_t res; struct mdns_host *mdns = NETIF_TO_HOST(pkt->netif); + if (mdns->probing_state != MDNS_PROBING_COMPLETE) { + /* Don't answer questions until we've verified our domains via probing */ + /* todo we should check incoming questions during probing for tiebreaking */ + return; + } + mdns_init_outpacket(&reply, pkt); while (pkt->questions_left) { @@ -1569,6 +1601,7 @@ mdns_handle_question(struct mdns_packet *pkt) if (replies && reply.legacy_query) { /* Add question to reply packet (legacy packet only has 1 question) */ res = mdns_add_question(&reply, &q.info.domain, q.info.type, q.info.klass, 0); + reply.questions = 1; if (res != ERR_OK) { goto cleanup; } @@ -1724,7 +1757,7 @@ mdns_handle_question(struct mdns_packet *pkt) } } - mdns_send_outpacket(&reply); + mdns_send_outpacket(&reply, DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE); cleanup: if (reply.pbuf) { @@ -1741,6 +1774,8 @@ cleanup: static void mdns_handle_response(struct mdns_packet *pkt) { + struct mdns_host* mdns = NETIF_TO_HOST(pkt->netif); + /* Ignore all questions */ while (pkt->questions_left) { struct mdns_question q; @@ -1766,6 +1801,39 @@ mdns_handle_response(struct mdns_packet *pkt) LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Answer for domain ")); mdns_domain_debug_print(&ans.info.domain); LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", ans.info.type, ans.info.klass)); + + /*"Apparently 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*/ + if (mdns->probing_state == MDNS_PROBING_ONGOING && mdns->probes_sent > 0) { + struct mdns_domain domain; + u8_t i; + u8_t conflict = 0; + + res = mdns_build_host_domain(&domain, mdns); + if (res == ERR_OK && mdns_domain_eq(&ans.info.domain, &domain)) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Probe response matches host domain!")); + conflict = 1; + } + + 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: Probe response matches service domain!")); + conflict = 1; + } + } + + if (conflict != 0) { + sys_untimeout(mdns_probe, pkt->netif); + if (mdns_name_result_cb != NULL) { + mdns_name_result_cb(pkt->netif, 0); + } + } + } } } @@ -1851,13 +1919,13 @@ 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_announce(netif); + mdns_resp_restart(netif); } /* TODO: send goodbye message */ } if (reason & LWIP_NSC_LINK_CHANGED) { if (args->link_changed.state != 0) { - mdns_resp_announce(netif); + mdns_resp_restart(netif); } } if (reason & (LWIP_NSC_IPV4_ADDRESS_CHANGED | LWIP_NSC_IPV4_GATEWAY_CHANGED | @@ -1868,10 +1936,111 @@ mdns_netif_ext_status_callback(struct netif *netif, netif_nsc_reason_t reason, c } #endif +static err_t +mdns_send_probe(struct netif* netif, const ip_addr_t *destination) +{ + struct mdns_host* mdns; + struct mdns_outpacket pkt; + struct mdns_domain domain; + u8_t i; + err_t res; + + mdns = NETIF_TO_HOST(netif); + + memset(&pkt, 0, sizeof(pkt)); + pkt.netif = netif; + + /* Add unicast questions with rtype ANY for all our desired records */ + mdns_build_host_domain(&domain, mdns); + res = mdns_add_question(&pkt, &domain, DNS_RRTYPE_ANY, DNS_RRCLASS_IN, 1); + if (res != ERR_OK) { + goto cleanup; + } + pkt.questions++; + for (i = 0; i < MDNS_MAX_SERVICES; ++i) { + struct mdns_service* service = mdns->services[i]; + if (!service) { + continue; + } + mdns_build_service_domain(&domain, service, 1); + res = mdns_add_question(&pkt, &domain, DNS_RRTYPE_ANY, DNS_RRCLASS_IN, 1); + if (res != ERR_OK) { + goto cleanup; + } + pkt.questions++; + } + + /* Add answers to the questions above into the authority section for tiebreaking */ +#if LWIP_IPV4 + if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) { + pkt.host_replies = REPLY_HOST_A; + } +#endif +#if LWIP_IPV6 + for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) { + pkt.host_replies |= REPLY_HOST_AAAA; + } + } +#endif + + for (i = 0; i < MDNS_MAX_SERVICES; i++) { + struct mdns_service *serv = mdns->services[i]; + if (serv) { + pkt.serv_replies[i] = REPLY_SERVICE_SRV | REPLY_SERVICE_TXT; + } + } + + pkt.tx_id = 0; + pkt.dest_port = LWIP_IANA_PORT_MDNS; + SMEMCPY(&pkt.dest_addr, destination, sizeof(pkt.dest_addr)); + res = mdns_send_outpacket(&pkt, 0); + +cleanup: + if (pkt.pbuf) { + pbuf_free(pkt.pbuf); + pkt.pbuf = NULL; + } + return res; +} + +/** + * Timer callback for probing network. + */ +static void +mdns_probe(void* arg) +{ + struct netif *netif = (struct netif *)arg; + struct mdns_host* mdns = NETIF_TO_HOST(netif); + + if(mdns->probes_sent >= 3) { + /* probing successful, announce the new name */ + mdns->probing_state = MDNS_PROBING_COMPLETE; + mdns_resp_announce(netif); + if (mdns_name_result_cb != NULL) { + mdns_name_result_cb(netif, 1); + } + } else { +#if LWIP_IPV4 + /*if ipv4 wait with probing until address is set*/ + if (!ip4_addr_isany_val(*netif_ip4_addr(netif)) && + mdns_send_probe(netif, IP4_ADDR_ANY) == ERR_OK) +#endif + { +#if LWIP_IPV6 + if (mdns_send_probe(netif, IP6_ADDR_ANY) == ERR_OK) +#endif + { + mdns->probes_sent++; + } + } + sys_timeout(250, mdns_probe, netif); + } +} + /** * @ingroup mdns - * Activate MDNS responder for a network interface and send announce packets. - * Don't forget to call mdns_resp_announce() after you added all netifs and services. + * Activate MDNS responder for a network interface. * @param netif The network interface to activate. * @param hostname Name to use. Queries for <hostname>.local will be answered * with the IP addresses of the netif. The hostname will be copied, the @@ -1897,6 +2066,8 @@ mdns_resp_add_netif(struct netif *netif, const char *hostname, u32_t dns_ttl) MEMCPY(&mdns->name, hostname, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(hostname))); mdns->dns_ttl = dns_ttl; + mdns->probes_sent = 0; + mdns->probing_state = MDNS_PROBING_NOT_STARTED; /* Join multicast groups */ #if LWIP_IPV4 @@ -1912,6 +2083,8 @@ mdns_resp_add_netif(struct netif *netif, const char *hostname, u32_t dns_ttl) } #endif + mdns_resp_restart(netif); + return ERR_OK; cleanup: @@ -1938,6 +2111,10 @@ mdns_resp_remove_netif(struct netif *netif) mdns = NETIF_TO_HOST(netif); LWIP_ERROR("mdns_resp_remove_netif: Not an active netif", (mdns != NULL), return ERR_VAL); + if (mdns->probing_state == MDNS_PROBING_ONGOING) { + sys_untimeout(mdns_probe, netif); + } + for (i = 0; i < MDNS_MAX_SERVICES; i++) { struct mdns_service *service = mdns->services[i]; if (service) { @@ -1958,10 +2135,39 @@ mdns_resp_remove_netif(struct netif *netif) return ERR_OK; } +/** + * @ingroup mdns + * Update MDNS hostname for a network interface. + * @param netif The network interface to activate. + * @param hostname Name to use. Queries for <hostname>.local will be answered + * with the IP addresses of the netif. The hostname will be copied, the + * given pointer can be on the stack. + * @return ERR_OK if name could be set on netif, an err_t otherwise + */ +err_t +mdns_resp_rename_netif(struct netif *netif, const char *hostname) +{ + struct mdns_host *mdns; + u8_t len; + + LWIP_ASSERT_CORE_LOCKED(); + len = strlen(hostname); + LWIP_ERROR("mdns_resp_rename_netif: netif != NULL", (netif != NULL), return ERR_VAL); + LWIP_ERROR("mdns_resp_rename_netif: Hostname too long", (len <= MDNS_LABEL_MAXLEN), return ERR_VAL); + mdns = NETIF_TO_HOST(netif); + LWIP_ERROR("mdns_resp_rename_netif: Not an mdns netif", (mdns != NULL), return ERR_VAL); + + MEMCPY(&mdns->name, hostname, len); + mdns->name[len] = '\0'; /* null termination in case new name is shorter than previous */ + + mdns_resp_restart(netif); + + return ERR_OK; +} + /** * @ingroup mdns * Add a service to the selected network interface. - * Don't forget to call mdns_resp_announce() after you added all services. * @param netif The network interface to publish this service on * @param name The name of the service * @param service The service type, like "_http" @@ -2012,6 +2218,8 @@ mdns_resp_add_service(struct netif *netif, const char *name, const char *service mdns->services[slot] = srv; + mdns_resp_restart(netif); + return slot; } @@ -2039,6 +2247,40 @@ mdns_resp_del_service(struct netif *netif, s8_t slot) return ERR_OK; } +/** + * @ingroup mdns + * Update name for an MDNS service. + * @param netif The network interface to activate. + * @param slot The service slot number returned by mdns_resp_add_service + * @param name The new name for the service + * @return ERR_OK if name could be set on service, an err_t otherwise + */ +err_t +mdns_resp_rename_service(struct netif *netif, s8_t slot, const char *name) +{ + struct mdns_service *srv; + struct mdns_host *mdns; + u8_t len; + + LWIP_ASSERT_CORE_LOCKED(); + len = strlen(name); + LWIP_ASSERT("mdns_resp_rename_service: netif != NULL", netif); + mdns = NETIF_TO_HOST(netif); + LWIP_ERROR("mdns_resp_rename_service: Not an mdns netif", (mdns != NULL), return ERR_VAL); + LWIP_ERROR("mdns_resp_rename_service: Name too long", (len <= MDNS_LABEL_MAXLEN), return ERR_VAL); + LWIP_ERROR("mdns_resp_rename_service: Invalid Service ID", (slot >= 0) && (slot < MDNS_MAX_SERVICES), return ERR_VAL); + LWIP_ERROR("mdns_resp_rename_service: Invalid Service ID", (mdns->services[slot] != NULL), return ERR_VAL); + + srv = mdns->services[slot]; + + MEMCPY(&srv->name, name, len); + srv->name[len] = '\0'; /* null termination in case new name is shorter than previous */ + + mdns_resp_restart(netif); + + return ERR_OK; +} + /** * @ingroup mdns * Call this function from inside the service_get_txt_fn_t callback to add text data. @@ -2066,22 +2308,61 @@ mdns_resp_add_service_txtitem(struct mdns_service *service, const char *txt, u8_ void mdns_resp_announce(struct netif *netif) { + struct mdns_host* mdns; LWIP_ASSERT_CORE_LOCKED(); LWIP_ERROR("mdns_resp_announce: netif != NULL", (netif != NULL), return); - if (NETIF_TO_HOST(netif) == NULL) { + mdns = NETIF_TO_HOST(netif); + if (mdns == NULL) { return; } - /* Announce on IPv6 and IPv4 */ + if (mdns->probing_state == MDNS_PROBING_COMPLETE) + { + /* Announce on IPv6 and IPv4 */ #if LWIP_IPV6 - mdns_announce(netif, IP6_ADDR_ANY); + mdns_announce(netif, IP6_ADDR_ANY); #endif #if LWIP_IPV4 - if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) { - mdns_announce(netif, IP4_ADDR_ANY); - } + if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) { + mdns_announce(netif, IP4_ADDR_ANY); + } #endif + } /* else: ip address changed while probing was ongoing? todo reset counter to restart? */ +} + +void +mdns_resp_register_name_result_cb(mdns_name_result_cb_t cb) +{ + mdns_name_result_cb = cb; +} + +/** + * @ingroup mdns + * Restart mdns responder. Call this when cable is connected after being disconnected or + * administrative interface is set up after being down + * @param netif The network interface to send on + */ +void +mdns_resp_restart(struct netif *netif) +{ + struct mdns_host* mdns; + LWIP_ASSERT_CORE_LOCKED(); + LWIP_ERROR("mdns_resp_restart: netif != NULL", (netif != NULL), return); + + mdns = NETIF_TO_HOST(netif); + if (mdns == NULL) { + return; + } + + if (mdns->probing_state == MDNS_PROBING_ONGOING) { + sys_untimeout(mdns_probe, netif); + } + /*todo first probe timeout SHOULD be random 0-250 ms*/ + /*todo if we've failed 15 times within a 10 second period we MUST wait 5 seconds (or wait 5 seconds every time except first)*/ + mdns->probes_sent = 0; + mdns->probing_state = MDNS_PROBING_ONGOING; + sys_timeout(250, mdns_probe, netif); } /** diff --git a/src/include/lwip/apps/mdns.h b/src/include/lwip/apps/mdns.h index 4318eb3b..b36b81f3 100644 --- a/src/include/lwip/apps/mdns.h +++ b/src/include/lwip/apps/mdns.h @@ -56,16 +56,27 @@ struct mdns_service; /** Callback function to add text to a reply, called when generating the reply */ typedef void (*service_get_txt_fn_t)(struct mdns_service *service, void *txt_userdata); +/** Callback function to let application know the result of probing network for name + * uniqueness, called with result 1 if no other node claimed use for the name for the + * netif or a service and is safe to use, or 0 if another node is already using it and + * mdns is disabled on this interface */ +typedef void (*mdns_name_result_cb_t)(struct netif* netif, u8_t result); + void mdns_resp_init(void); +void mdns_resp_register_name_result_cb(mdns_name_result_cb_t cb); + err_t mdns_resp_add_netif(struct netif *netif, const char *hostname, u32_t dns_ttl); err_t mdns_resp_remove_netif(struct netif *netif); +err_t mdns_resp_rename_netif(struct netif *netif, const char *hostname); s8_t mdns_resp_add_service(struct netif *netif, const char *name, const char *service, enum mdns_sd_proto proto, u16_t port, u32_t dns_ttl, service_get_txt_fn_t txt_fn, void *txt_userdata); err_t mdns_resp_del_service(struct netif *netif, s8_t slot); +err_t mdns_resp_rename_service(struct netif *netif, s8_t slot, const char *name); err_t mdns_resp_add_service_txtitem(struct mdns_service *service, const char *txt, u8_t txt_len); +void mdns_resp_restart(struct netif *netif); void mdns_resp_announce(struct netif *netif); /**