From b57860ddddbf2ce2a86cff22015ad017d0faa68a Mon Sep 17 00:00:00 2001 From: Liu Han Date: Mon, 4 Jun 2018 16:33:26 +0800 Subject: [PATCH 1/5] feat(mdns): Sync mDNS function from ESP-IDF --- components/mdns/component.mk | 2 + components/mdns/include/mdns.h | 362 ++ components/mdns/include/mdns_console.h | 22 + components/mdns/mdns.c | 4761 +++++++++++++++++ components/mdns/mdns_console.c | 1060 ++++ components/mdns/mdns_networking.c | 284 + .../mdns/private_include/mdns_networking.h | 52 + .../mdns/private_include/mdns_private.h | 388 ++ components/mdns/test_afl_fuzz_host/Makefile | 43 + components/mdns/test_afl_fuzz_host/README.md | 58 + .../mdns/test_afl_fuzz_host/esp32_compat.h | 239 + .../mdns/test_afl_fuzz_host/esp32_mock.c | 84 + .../mdns/test_afl_fuzz_host/esp32_mock.h | 45 + .../mdns/test_afl_fuzz_host/in/test-14.bin | Bin 0 -> 568 bytes .../mdns/test_afl_fuzz_host/in/test-15.bin | Bin 0 -> 524 bytes .../mdns/test_afl_fuzz_host/in/test-16.bin | Bin 0 -> 254 bytes .../mdns/test_afl_fuzz_host/in/test-28.bin | Bin 0 -> 62 bytes .../mdns/test_afl_fuzz_host/in/test-29.bin | Bin 0 -> 39 bytes .../mdns/test_afl_fuzz_host/in/test-31.bin | Bin 0 -> 91 bytes .../mdns/test_afl_fuzz_host/in/test-53.bin | Bin 0 -> 140 bytes .../mdns/test_afl_fuzz_host/in/test-56.bin | Bin 0 -> 262 bytes .../mdns/test_afl_fuzz_host/in/test-63.bin | Bin 0 -> 147 bytes .../mdns/test_afl_fuzz_host/in/test-83.bin | Bin 0 -> 105 bytes .../mdns/test_afl_fuzz_host/in/test-88.bin | Bin 0 -> 48 bytes .../mdns/test_afl_fuzz_host/in/test-89.bin | Bin 0 -> 459 bytes .../mdns/test_afl_fuzz_host/in/test-95.bin | Bin 0 -> 286 bytes .../mdns/test_afl_fuzz_host/in/test-96.bin | Bin 0 -> 319 bytes .../mdns/test_afl_fuzz_host/input_packets.txt | 166 + components/mdns/test_afl_fuzz_host/mdns_di.h | 53 + components/mdns/test_afl_fuzz_host/test.c | 228 + 30 files changed, 7847 insertions(+) create mode 100644 components/mdns/component.mk create mode 100644 components/mdns/include/mdns.h create mode 100644 components/mdns/include/mdns_console.h create mode 100644 components/mdns/mdns.c create mode 100644 components/mdns/mdns_console.c create mode 100644 components/mdns/mdns_networking.c create mode 100644 components/mdns/private_include/mdns_networking.h create mode 100644 components/mdns/private_include/mdns_private.h create mode 100644 components/mdns/test_afl_fuzz_host/Makefile create mode 100644 components/mdns/test_afl_fuzz_host/README.md create mode 100644 components/mdns/test_afl_fuzz_host/esp32_compat.h create mode 100644 components/mdns/test_afl_fuzz_host/esp32_mock.c create mode 100644 components/mdns/test_afl_fuzz_host/esp32_mock.h create mode 100644 components/mdns/test_afl_fuzz_host/in/test-14.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-15.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-16.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-28.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-29.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-31.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-53.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-56.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-63.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-83.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-88.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-89.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-95.bin create mode 100644 components/mdns/test_afl_fuzz_host/in/test-96.bin create mode 100644 components/mdns/test_afl_fuzz_host/input_packets.txt create mode 100644 components/mdns/test_afl_fuzz_host/mdns_di.h create mode 100644 components/mdns/test_afl_fuzz_host/test.c diff --git a/components/mdns/component.mk b/components/mdns/component.mk new file mode 100644 index 00000000..064cc060 --- /dev/null +++ b/components/mdns/component.mk @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_PRIV_INCLUDEDIRS := private_include diff --git a/components/mdns/include/mdns.h b/components/mdns/include/mdns.h new file mode 100644 index 00000000..a5ebb809 --- /dev/null +++ b/components/mdns/include/mdns.h @@ -0,0 +1,362 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef ESP_MDNS_H_ +#define ESP_MDNS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "esp_event.h" + +#define MDNS_TYPE_A 0x0001 +#define MDNS_TYPE_PTR 0x000C +#define MDNS_TYPE_TXT 0x0010 +#define MDNS_TYPE_AAAA 0x001C +#define MDNS_TYPE_SRV 0x0021 +#define MDNS_TYPE_OPT 0x0029 +#define MDNS_TYPE_NSEC 0x002F +#define MDNS_TYPE_ANY 0x00FF + +/** + * @brief mDNS enum to specify the ip_protocol type + */ +typedef enum { + MDNS_IP_PROTOCOL_V4, + MDNS_IP_PROTOCOL_V6, + MDNS_IP_PROTOCOL_MAX +} mdns_ip_protocol_t; + +/** + * @brief mDNS basic text item structure + * Used in mdns_service_add() + */ +typedef struct { + char * key; /*!< item key name */ + char * value; /*!< item value string */ +} mdns_txt_item_t; + +/** + * @brief mDNS query linked list IP item + */ +typedef struct mdns_ip_addr_s { + ip_addr_t addr; /*!< IP address */ + struct mdns_ip_addr_s * next; /*!< next IP, or NULL for the last IP in the list */ +} mdns_ip_addr_t; + +/** + * @brief mDNS query result structure + */ +typedef struct mdns_result_s { + struct mdns_result_s * next; /*!< next result, or NULL for the last result in the list */ + + tcpip_adapter_if_t tcpip_if; /*!< interface on which the result came (AP/STA/ETH) */ + mdns_ip_protocol_t ip_protocol; /*!< ip_protocol type of the interface (v4/v6) */ + // PTR + char * instance_name; /*!< instance name */ + // SRV + char * hostname; /*!< hostname */ + uint16_t port; /*!< service port */ + // TXT + mdns_txt_item_t * txt; /*!< txt record */ + size_t txt_count; /*!< number of txt items */ + // A and AAAA + mdns_ip_addr_t * addr; /*!< linked list of IP addreses found */ +} mdns_result_t; + +/** + * @brief Initialize mDNS on given interface + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG when bad tcpip_if is given + * - ESP_ERR_INVALID_STATE when the network returned error + * - ESP_ERR_NO_MEM on memory error + * - ESP_ERR_WIFI_NOT_INIT when WiFi is not initialized by eps_wifi_init + */ +esp_err_t mdns_init(); + +/** + * @brief Stop and free mDNS server + * + */ +void mdns_free(); + +/** + * @brief Set the hostname for mDNS server + * required if you want to advertise services + * + * @param hostname Hostname to set + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NO_MEM memory error + */ +esp_err_t mdns_hostname_set(const char * hostname); + +/** + * @brief Set the default instance name for mDNS server + * + * @param instance_name Instance name to set + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NO_MEM memory error + */ +esp_err_t mdns_instance_name_set(const char * instance_name); + +/** + * @brief Add service to mDNS server + * + * @param instance_name instance name to set. If NULL, + * global instance name or hostname will be used + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param port service port + * @param num_items number of items in TXT data + * @param txt string array of TXT data (eg. {{"var","val"},{"other","2"}}) + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NO_MEM memory error + */ +esp_err_t mdns_service_add(const char * instance_name, const char * service_type, const char * proto, uint16_t port, mdns_txt_item_t txt[], size_t num_items); + +/** + * @brief Remove service from mDNS server + * + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NOT_FOUND Service not found + * - ESP_FAIL unknown error + */ +esp_err_t mdns_service_remove(const char * service_type, const char * proto); + +/** + * @brief Set instance name for service + * + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param instance_name instance name to set + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NOT_FOUND Service not found + * - ESP_ERR_NO_MEM memory error + */ +esp_err_t mdns_service_instance_name_set(const char * service_type, const char * proto, const char * instance_name); + +/** + * @brief Set service port + * + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param port service port + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NOT_FOUND Service not found + */ +esp_err_t mdns_service_port_set(const char * service_type, const char * proto, uint16_t port); + +/** + * @brief Replace all TXT items for service + * + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param num_items number of items in TXT data + * @param txt array of TXT data (eg. {{"var","val"},{"other","2"}}) + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NOT_FOUND Service not found + * - ESP_ERR_NO_MEM memory error + */ +esp_err_t mdns_service_txt_set(const char * service_type, const char * proto, mdns_txt_item_t txt[], uint8_t num_items); + +/** + * @brief Set/Add TXT item for service TXT record + * + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param key the key that you want to add/update + * @param value the new value of the key + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NOT_FOUND Service not found + * - ESP_ERR_NO_MEM memory error + */ +esp_err_t mdns_service_txt_item_set(const char * service_type, const char * proto, const char * key, const char * value); + +/** + * @brief Remove TXT item for service TXT record + * + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param key the key that you want to remove + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NOT_FOUND Service not found + * - ESP_ERR_NO_MEM memory error + */ +esp_err_t mdns_service_txt_item_remove(const char * service_type, const char * proto, const char * key); + +/** + * @brief Remove and free all services from mDNS server + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t mdns_service_remove_all(); + +/** + * @brief Query mDNS for host or service + * All following query methods are derived from this one + * + * @param name service instance or host name (NULL for PTR queries) + * @param service_type service type (_http, _arduino, _ftp etc.) (NULL for host queries) + * @param proto service protocol (_tcp, _udp, etc.) (NULL for host queries) + * @param type type of query (MDNS_TYPE_*) + * @param timeout time in milliseconds to wait for answers. + * @param max_results maximum results to be collected + * @param results pointer to the results of the query + * results must be freed using mdns_query_results_free below + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG timeout was not given + */ +esp_err_t mdns_query(const char * name, const char * service_type, const char * proto, uint16_t type, uint32_t timeout, size_t max_results, mdns_result_t ** results); + +/** + * @brief Free query results + * + * @param results linked list of results to be freed + */ +void mdns_query_results_free(mdns_result_t * results); + +/** + * @brief Query mDNS for service + * + * @param service_type service type (_http, _arduino, _ftp etc.) + * @param proto service protocol (_tcp, _udp, etc.) + * @param timeout time in milliseconds to wait for answer. + * @param max_results maximum results to be collected + * @param results pointer to the results of the query + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG parameter error + */ +esp_err_t mdns_query_ptr(const char * service_type, const char * proto, uint32_t timeout, size_t max_results, mdns_result_t ** results); + +/** + * @brief Query mDNS for SRV record + * + * @param instance_name service instance name + * @param service_type service type (_http, _arduino, _ftp etc.) + * @param proto service protocol (_tcp, _udp, etc.) + * @param timeout time in milliseconds to wait for answer. + * @param result pointer to the result of the query + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG parameter error + */ +esp_err_t mdns_query_srv(const char * instance_name, const char * service_type, const char * proto, uint32_t timeout, mdns_result_t ** result); + +/** + * @brief Query mDNS for TXT record + * + * @param instance_name service instance name + * @param service_type service type (_http, _arduino, _ftp etc.) + * @param proto service protocol (_tcp, _udp, etc.) + * @param timeout time in milliseconds to wait for answer. + * @param result pointer to the result of the query + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG parameter error + */ +esp_err_t mdns_query_txt(const char * instance_name, const char * service_type, const char * proto, uint32_t timeout, mdns_result_t ** result); + +/** + * @brief Query mDNS for A record + * + * @param host_name host name to look for + * @param timeout time in milliseconds to wait for answer. + * @param addr pointer to the resulting IP4 address + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG parameter error + */ +esp_err_t mdns_query_a(const char * host_name, uint32_t timeout, ip4_addr_t * addr); + +/** + * @brief Query mDNS for A record + * + * @param host_name host name to look for + * @param timeout time in milliseconds to wait for answer. If 0, max_results needs to be defined + * @param addr pointer to the resulting IP6 address + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG parameter error + */ +esp_err_t mdns_query_aaaa(const char * host_name, uint32_t timeout, ip6_addr_t * addr); + +/** + * @brief System event handler + * This method controls the service state on all active interfaces and applications are required + * to call it from the system event handler for normal operation of mDNS service. + * + * @param ctx The system event context + * @param event The system event + */ +esp_err_t mdns_handle_system_event(void *ctx, system_event_t *event); + +#ifdef __cplusplus +} +#endif + +#endif /* ESP_MDNS_H_ */ diff --git a/components/mdns/include/mdns_console.h b/components/mdns/include/mdns_console.h new file mode 100644 index 00000000..5c8b0b5a --- /dev/null +++ b/components/mdns/include/mdns_console.h @@ -0,0 +1,22 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _MDNS_CONSOLE_H_ +#define _MDNS_CONSOLE_H_ + +/** + * @brief Register MDNS functions with the console component + */ +void mdns_console_register(); + +#endif /* _MDNS_CONSOLE_H_ */ diff --git a/components/mdns/mdns.c b/components/mdns/mdns.c new file mode 100644 index 00000000..0cc5865a --- /dev/null +++ b/components/mdns/mdns.c @@ -0,0 +1,4761 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "mdns.h" +#include "mdns_private.h" +#include "mdns_networking.h" +#include +#include + +#ifdef MDNS_ENABLE_DEBUG +void mdns_debug_packet(const uint8_t * data, size_t len); +#endif + +static const char * MDNS_DEFAULT_DOMAIN = "local"; +static const char * MDNS_SUB_STR = "_sub"; + +mdns_server_t * _mdns_server = NULL; + +static volatile TaskHandle_t _mdns_service_task_handle = NULL; +static SemaphoreHandle_t _mdns_service_semaphore = NULL; + +static void _mdns_search_finish_done(); +static mdns_search_once_t * _mdns_search_find_from(mdns_search_once_t * search, mdns_name_t * name, uint16_t type, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +static void _mdns_search_result_add_ip(mdns_search_once_t * search, const char * hostname, ip_addr_t * ip, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +static void _mdns_search_result_add_srv(mdns_search_once_t * search, const char * hostname, uint16_t port, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +static void _mdns_search_result_add_txt(mdns_search_once_t * search, mdns_txt_item_t * txt, size_t txt_count, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +static mdns_result_t * _mdns_search_result_add_ptr(mdns_search_once_t * search, const char * instance, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); + +static inline bool _str_null_or_empty(const char * str){ + return (str == NULL || *str == 0); +} + +/* + * @brief Appends/increments a number to name/instance in case of collision + * */ +static char * _mdns_mangle_name(char* in) { + char *p = strrchr(in, '-'); + int suffix = 0; + if (p == NULL) { + //No - in ``in`` + suffix = 2; + } else { + char *endp = NULL; + suffix = strtol(p + 1, &endp, 10); + if (*endp != 0) { + //suffix is not numerical + suffix = 2; + p = NULL; //so we append -suffix to the entire string + } + } + char *ret; + if (p == NULL) { + //need to add -2 to string + ret = malloc(strlen(in) + 3); + if (ret == NULL) { + return NULL; + } + sprintf(ret, "%s-2", in); + } else { + ret = malloc(strlen(in) + 2); //one extra byte in case 9-10 or 99-100 etc + strcpy(ret, in); + int baseLen = p - in; //length of 'bla' in 'bla-123' + //overwrite suffix with new suffix + sprintf(ret + baseLen, "-%d", suffix + 1); + } + return ret; +} + +/** + * @brief finds service from given service type + * @param server the server + * @param service service type to match + * @param proto proto to match + * + * @return the service item if found or NULL on error + */ +static mdns_srv_item_t * _mdns_get_service_item(const char * service, const char * proto) +{ + mdns_srv_item_t * s = _mdns_server->services; + while (s) { + if (!strcasecmp(s->service->service, service) && !strcasecmp(s->service->proto, proto)) { + return s; + } + s = s->next; + } + return NULL; +} + +esp_err_t _mdns_send_rx_action(mdns_rx_packet_t * packet) +{ + mdns_action_t * action = NULL; + + action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + + action->type = ACTION_RX_HANDLE; + action->data.rx_handle.packet = packet; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +/** + * @brief Get the service name of a service + */ +static const char * _mdns_get_service_instance_name(mdns_service_t * service) +{ + if (service && !_str_null_or_empty(service->instance)) { + return service->instance; + } + + if (_mdns_server && !_str_null_or_empty(_mdns_server->instance)) { + return _mdns_server->instance; + } + + if (_mdns_server && !_str_null_or_empty(_mdns_server->hostname)) { + return _mdns_server->hostname; + } + + return NULL; +} + +/** + * @brief reads MDNS FQDN into mdns_name_t structure + * FQDN is in format: [hostname.|[instance.]_service._proto.]local. + * + * @param packet MDNS packet + * @param start Starting point of FQDN + * @param name mdns_name_t structure to populate + * @param buf temporary char buffer + * + * @return the address after the parsed FQDN in the packet or NULL on error + */ +static const uint8_t * _mdns_read_fqdn(const uint8_t * packet, const uint8_t * start, mdns_name_t * name, char * buf) +{ + size_t index = 0; + while (start[index]) { + if (name->parts == 4) { + return NULL; + } + uint8_t len = start[index++]; + if (len < 0xC0) { + if (len > 63) { + //length can not be more than 63 + return NULL; + } + uint8_t i; + for (i=0; iparts == 1 && buf[0] != '_' + && (strcasecmp(buf, MDNS_DEFAULT_DOMAIN) != 0) + && (strcasecmp(buf, "ip6") != 0) + && (strcasecmp(buf, "in-addr") != 0)) { + strlcat(name->host, ".", sizeof(name->host)); + strlcat(name->host, buf, sizeof(name->host)); + } else if (strcasecmp(buf, MDNS_SUB_STR) == 0) { + name->sub = 1; + } else { + char* mdns_name_ptrs[]={name->host, name->service, name->proto, name->domain}; + memcpy(mdns_name_ptrs[name->parts++], buf, len+1); + } + } else { + size_t address = (((uint16_t)len & 0x3F) << 8) | start[index++]; + if ((packet + address) >= start) { + //reference address can not be after where we are + return NULL; + } + if (_mdns_read_fqdn(packet, packet + address, name, buf)) { + return start + index; + } + return NULL; + } + } + return start + index + 1; +} + +/** + * @brief sets uint16_t value in a packet + * + * @param packet MDNS packet + * @param index offset of uint16_t value + * @param value the value to set + */ +static inline void _mdns_set_u16(uint8_t * packet, uint16_t index, uint16_t value) +{ + if ((index + 1) >= MDNS_MAX_PACKET_SIZE) { + return; + } + packet[index] = (value >> 8) & 0xFF; + packet[index+1] = value & 0xFF; +} + +/** + * @brief appends byte in a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param value the value to set + * + * @return length of added data: 0 on error or 1 on success + */ +static inline uint8_t _mdns_append_u8(uint8_t * packet, uint16_t * index, uint8_t value) +{ + if (*index >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + packet[*index] = value; + *index += 1; + return 1; +} + +/** + * @brief appends uint16_t in a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param value the value to set + * + * @return length of added data: 0 on error or 2 on success + */ +static inline uint8_t _mdns_append_u16(uint8_t * packet, uint16_t * index, uint16_t value) +{ + if ((*index + 1) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + _mdns_append_u8(packet, index, (value >> 8) & 0xFF); + _mdns_append_u8(packet, index, value & 0xFF); + return 2; +} + +/** + * @brief appends uint32_t in a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param value the value to set + * + * @return length of added data: 0 on error or 4 on success + */ +static inline uint8_t _mdns_append_u32(uint8_t * packet, uint16_t * index, uint32_t value) +{ + if ((*index + 3) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + _mdns_append_u8(packet, index, (value >> 24) & 0xFF); + _mdns_append_u8(packet, index, (value >> 16) & 0xFF); + _mdns_append_u8(packet, index, (value >> 8) & 0xFF); + _mdns_append_u8(packet, index, value & 0xFF); + return 4; +} + +/** + * @brief appends answer type, class, ttl and data length to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param type answer type + * @param ttl answer ttl + * + * @return length of added data: 0 on error or 10 on success + */ +static inline uint8_t _mdns_append_type(uint8_t * packet, uint16_t * index, uint8_t type, bool flush, uint32_t ttl) +{ + if ((*index + 10) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + uint16_t clas = MDNS_CLASS_IN; + if (flush) { + clas = MDNS_CLASS_IN_FLUSH_CACHE; + } + if (type == MDNS_ANSWER_PTR) { + _mdns_append_u16(packet, index, MDNS_TYPE_PTR); + _mdns_append_u16(packet, index, clas); + } else if (type == MDNS_ANSWER_TXT) { + _mdns_append_u16(packet, index, MDNS_TYPE_TXT); + _mdns_append_u16(packet, index, clas); + } else if (type == MDNS_ANSWER_SRV) { + _mdns_append_u16(packet, index, MDNS_TYPE_SRV); + _mdns_append_u16(packet, index, clas); + } else if (type == MDNS_ANSWER_A) { + _mdns_append_u16(packet, index, MDNS_TYPE_A); + _mdns_append_u16(packet, index, clas); + } else if (type == MDNS_ANSWER_AAAA) { + _mdns_append_u16(packet, index, MDNS_TYPE_AAAA); + _mdns_append_u16(packet, index, clas); + } else { + return 0; + } + _mdns_append_u32(packet, index, ttl); + _mdns_append_u16(packet, index, 0); + return 10; +} + +/** + * @brief appends single string to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param string the string to append + * + * @return length of added data: 0 on error or length of the string + 1 on success + */ +static inline uint8_t _mdns_append_string(uint8_t * packet, uint16_t * index, const char * string) +{ + uint8_t len = strlen(string); + if ((*index + len + 1) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + _mdns_append_u8(packet, index, len); + memcpy(packet + *index, string, len); + *index += len; + return len + 1; +} + +/** + * @brief appends FQDN to a packet, incrementing the index and + * compressing the output if previous occurrence of the string (or part of it) has been found + * + * @param packet MDNS packet + * @param index offset in the packet + * @param strings string array containing the parts of the FQDN + * @param count number of strings in the array + * + * @return length of added data: 0 on error or length on success + */ +static uint16_t _mdns_append_fqdn(uint8_t * packet, uint16_t * index, const char * strings[], uint8_t count) +{ + if (!count) { + //empty string so terminate + return _mdns_append_u8(packet, index, 0); + } + mdns_name_t name; + static char buf[MDNS_NAME_BUF_LEN]; + uint8_t len = strlen(strings[0]); + //try to find first the string length in the packet (if it exists) + uint8_t * len_location = (uint8_t *)memchr(packet, (char)len, *index); + while (len_location) { + //check if the string after len_location is the string that we are looking for + if (memcmp(len_location+1, strings[0], len)) { //not continuing with our string +search_next: + //try and find the length byte further in the packet + len_location = (uint8_t *)memchr(len_location+1, (char)len, *index - (len_location+1 - packet)); + continue; + } + //seems that we might have found the string that we are looking for + //read the destination into name and compare + name.parts = 0; + name.sub = 0; + name.host[0] = 0; + name.service[0] = 0; + name.proto[0] = 0; + name.domain[0] = 0; + const uint8_t * content = _mdns_read_fqdn(packet, len_location, &name, buf); + if (!content) { + //not a readable fqdn? + return 0; + } + if (name.parts == count) { + uint8_t i; + for (i=0; iservice; + str[1] = service->proto; + str[2] = MDNS_DEFAULT_DOMAIN; + + part_length = _mdns_append_fqdn(packet, index, sd_str, 4); + + record_length += part_length; + + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_PTR, flush, MDNS_ANSWER_PTR_TTL); + if (!part_length) { + return 0; + } + record_length += part_length; + + uint16_t data_len_location = *index - 2; + part_length = _mdns_append_fqdn(packet, index, str, 3); + if (!part_length) { + return 0; + } + _mdns_set_u16(packet, data_len_location, part_length); + record_length += part_length; + return record_length; +} + +/** + * @brief appends TXT record for service to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param server the server that is hosting the service + * @param service the service to add record for + * + * @return length of added data: 0 on error or length on success + */ +static uint16_t _mdns_append_txt_record(uint8_t * packet, uint16_t * index, mdns_service_t * service, bool flush, bool bye) +{ + const char * str[4]; + uint16_t record_length = 0; + uint8_t part_length; + + str[0] = _mdns_get_service_instance_name(service); + str[1] = service->service; + str[2] = service->proto; + str[3] = MDNS_DEFAULT_DOMAIN; + + if (!str[0]) { + return 0; + } + + part_length = _mdns_append_fqdn(packet, index, str, 4); + if (!part_length) { + return 0; + } + record_length += part_length; + + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_TXT, flush, bye?0:MDNS_ANSWER_TXT_TTL); + if (!part_length) { + return 0; + } + record_length += part_length; + + uint16_t data_len_location = *index - 2; + uint16_t data_len = 0; + + char * tmp; + mdns_txt_linked_item_t * txt = service->txt; + while (txt) { + tmp = (char *)malloc(2 + strlen(txt->key) + strlen(txt->value)); + if (tmp) { + sprintf(tmp, "%s=%s", txt->key, txt->value); + uint8_t l = _mdns_append_string(packet, index, tmp); + free(tmp); + if (!l) { + return 0; + } + data_len += l; + } + txt = txt->next; + } + if (!data_len) { + data_len = 1; + packet[*index] = 0; + *index = *index + 1; + } + _mdns_set_u16(packet, data_len_location, data_len); + record_length += data_len; + return record_length; +} + +/** + * @brief appends SRV record for service to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param server the server that is hosting the service + * @param service the service to add record for + * + * @return length of added data: 0 on error or length on success + */ +static uint16_t _mdns_append_srv_record(uint8_t * packet, uint16_t * index, mdns_service_t * service, bool flush, bool bye) +{ + const char * str[4]; + uint16_t record_length = 0; + uint8_t part_length; + + str[0] = _mdns_get_service_instance_name(service); + str[1] = service->service; + str[2] = service->proto; + str[3] = MDNS_DEFAULT_DOMAIN; + + if (!str[0]) { + return 0; + } + + part_length = _mdns_append_fqdn(packet, index, str, 4); + if (!part_length) { + return 0; + } + record_length += part_length; + + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_SRV, flush, bye?0:MDNS_ANSWER_SRV_TTL); + if (!part_length) { + return 0; + } + record_length += part_length; + + uint16_t data_len_location = *index - 2; + + part_length = 0; + part_length += _mdns_append_u16(packet, index, service->priority); + part_length += _mdns_append_u16(packet, index, service->weight); + part_length += _mdns_append_u16(packet, index, service->port); + if (part_length != 6) { + return 0; + } + + str[0] = _mdns_server->hostname; + str[1] = MDNS_DEFAULT_DOMAIN; + + if (_str_null_or_empty(str[0])) { + return 0; + } + + part_length = _mdns_append_fqdn(packet, index, str, 2); + if (!part_length) { + return 0; + } + _mdns_set_u16(packet, data_len_location, part_length + 6); + + record_length += part_length + 6; + return record_length; +} + +/** + * @brief appends A record to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param server the server + * @param ip the IP address to add + * + * @return length of added data: 0 on error or length on success + */ +static uint16_t _mdns_append_a_record(uint8_t * packet, uint16_t * index, uint32_t ip, bool flush, bool bye) +{ + const char * str[2]; + uint16_t record_length = 0; + uint8_t part_length; + + str[0] = _mdns_server->hostname; + str[1] = MDNS_DEFAULT_DOMAIN; + + if (_str_null_or_empty(str[0])) { + return 0; + } + + + part_length = _mdns_append_fqdn(packet, index, str, 2); + if (!part_length) { + return 0; + } + record_length += part_length; + + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_A, flush, bye?0:MDNS_ANSWER_A_TTL); + if (!part_length) { + return 0; + } + record_length += part_length; + + uint16_t data_len_location = *index - 2; + + if ((*index + 3) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + _mdns_append_u8(packet, index, ip & 0xFF); + _mdns_append_u8(packet, index, (ip >> 8) & 0xFF); + _mdns_append_u8(packet, index, (ip >> 16) & 0xFF); + _mdns_append_u8(packet, index, (ip >> 24) & 0xFF); + _mdns_set_u16(packet, data_len_location, 4); + + record_length += 4; + return record_length; +} + +/** + * @brief appends AAAA record to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param ipv6 the IPv6 address to add + * + * @return length of added data: 0 on error or length on success + */ +static uint16_t _mdns_append_aaaa_record(uint8_t * packet, uint16_t * index, uint8_t * ipv6, bool flush, bool bye) +{ + const char * str[2]; + uint16_t record_length = 0; + uint8_t part_length; + + str[0] = _mdns_server->hostname; + str[1] = MDNS_DEFAULT_DOMAIN; + + if (_str_null_or_empty(str[0])) { + return 0; + } + + + part_length = _mdns_append_fqdn(packet, index, str, 2); + if (!part_length) { + return 0; + } + record_length += part_length; + + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_AAAA, flush, bye?0:MDNS_ANSWER_AAAA_TTL); + if (!part_length) { + return 0; + } + record_length += part_length; + + uint16_t data_len_location = *index - 2; + + if ((*index + 15) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + + part_length = sizeof(ip6_addr_t); + memcpy(packet + *index, ipv6, part_length); + *index += part_length; + _mdns_set_u16(packet, data_len_location, part_length); + record_length += part_length; + return record_length; +} + +/** + * @brief Append question to packet + */ +static uint16_t _mdns_append_question(uint8_t * packet, uint16_t * index, mdns_out_question_t * q) +{ + const char * str[4]; + uint8_t str_index = 0; + uint8_t part_length; + if (q->host) { + str[str_index++] = q->host; + } + if (q->service) { + str[str_index++] = q->service; + } + if (q->proto) { + str[str_index++] = q->proto; + } + if (q->domain) { + str[str_index++] = q->domain; + } + + part_length = _mdns_append_fqdn(packet, index, str, str_index); + if (!part_length) { + return 0; + } + + part_length += _mdns_append_u16(packet, index, q->type); + part_length += _mdns_append_u16(packet, index, q->unicast?0x8001:0x0001); + return part_length; +} + +/** + * @brief Helper to get either ETH or STA if the other is provided + * Used when two interfaces are on the same subnet + */ +static tcpip_adapter_if_t _mdns_get_other_if (tcpip_adapter_if_t tcpip_if) +{ + if (tcpip_if == TCPIP_ADAPTER_IF_STA) { + return TCPIP_ADAPTER_IF_ETH; + } else if (tcpip_if == TCPIP_ADAPTER_IF_ETH) { + return TCPIP_ADAPTER_IF_STA; + } + return TCPIP_ADAPTER_IF_MAX; +} + +/** + * @brief Check if interface is duplicate (two interfaces on the same subnet) + */ +static bool _mdns_if_is_dup(tcpip_adapter_if_t tcpip_if) +{ + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (other_if == TCPIP_ADAPTER_IF_MAX) { + return false; + } + if (_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].state == PCB_DUP + || _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].state == PCB_DUP + || _mdns_server->interfaces[other_if].pcbs[MDNS_IP_PROTOCOL_V4].state == PCB_DUP + || _mdns_server->interfaces[other_if].pcbs[MDNS_IP_PROTOCOL_V6].state == PCB_DUP + ) { + return true; + } + return false; +} + +/** + * @brief Check if IPv6 address is NULL + */ +static bool _ipv6_address_is_zero(ip6_addr_t ip6) +{ + uint8_t i; + uint8_t * data = (uint8_t *)ip6.addr; + for (i=0; i<16; i++) { + if (data[i]) { + return false; + } + } + return true; +} + +/** + * @brief Append answer to packet + * + * @return number of answers added to the packet + */ +static uint8_t _mdns_append_answer(uint8_t * packet, uint16_t * index, mdns_out_answer_t * answer, tcpip_adapter_if_t tcpip_if) +{ + if (answer->type == MDNS_TYPE_PTR) { + + if (answer->service) { + return _mdns_append_ptr_record(packet, index, + _mdns_get_service_instance_name(answer->service), + answer->service->service, answer->service->proto, + answer->flush, answer->bye) > 0; + } else { + return _mdns_append_ptr_record(packet, index, + answer->custom_instance, answer->custom_service, answer->custom_proto, + answer->flush, answer->bye) > 0; + } + } else if (answer->type == MDNS_TYPE_SRV) { + return _mdns_append_srv_record(packet, index, answer->service, answer->flush, answer->bye) > 0; + } else if (answer->type == MDNS_TYPE_TXT) { + return _mdns_append_txt_record(packet, index, answer->service, answer->flush, answer->bye) > 0; + } else if (answer->type == MDNS_TYPE_SDPTR) { + return _mdns_append_sdptr_record(packet, index, answer->service, answer->flush, answer->bye) > 0; + } else if (answer->type == MDNS_TYPE_A) { + tcpip_adapter_ip_info_t if_ip_info; + if (!_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb && _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].state != PCB_DUP) { + return 0; + } + if (tcpip_adapter_get_ip_info(tcpip_if, &if_ip_info)) { + return 0; + } + if (_mdns_append_a_record(packet, index, if_ip_info.ip.addr, answer->flush, answer->bye) <= 0) { + return 0; + } + if (!_mdns_if_is_dup(tcpip_if)) { + return 1; + } + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (tcpip_adapter_get_ip_info(other_if, &if_ip_info)) { + return 1; + } + if (_mdns_append_a_record(packet, index, if_ip_info.ip.addr, answer->flush, answer->bye) > 0) { + return 2; + } + return 1; + } else if (answer->type == MDNS_TYPE_AAAA) { + struct ip6_addr if_ip6; + if (!_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb && _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].state != PCB_DUP) { + return 0; + } + if (tcpip_adapter_get_ip6_linklocal(tcpip_if, &if_ip6)) { + return 0; + } + if (_ipv6_address_is_zero(if_ip6)) { + return 0; + } + if (_mdns_append_aaaa_record(packet, index, (uint8_t*)if_ip6.addr, answer->flush, answer->bye) <= 0) { + return 0; + } + if (!_mdns_if_is_dup(tcpip_if)) { + return 1; + } + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (tcpip_adapter_get_ip6_linklocal(other_if, &if_ip6)) { + return 1; + } + if (_mdns_append_aaaa_record(packet, index, (uint8_t*)if_ip6.addr, answer->flush, answer->bye) > 0) { + return 2; + } + return 1; + } + return 0; +} + +/** + * @brief sends a packet + * + * @param p the packet + */ +static void _mdns_dispatch_tx_packet(mdns_tx_packet_t * p) +{ + static uint8_t packet[MDNS_MAX_PACKET_SIZE]; + uint16_t index = MDNS_HEAD_LEN; + memset(packet, 0, MDNS_HEAD_LEN); + mdns_out_question_t * q; + mdns_out_answer_t * a; + uint8_t count; + + _mdns_set_u16(packet, MDNS_HEAD_FLAGS_OFFSET, p->flags); + + count = 0; + q = p->questions; + while (q) { + if (_mdns_append_question(packet, &index, q)) { + count++; + } + q = q->next; + } + _mdns_set_u16(packet, MDNS_HEAD_QUESTIONS_OFFSET, count); + + count = 0; + a = p->answers; + while (a) { + count += _mdns_append_answer(packet, &index, a, p->tcpip_if); + a = a->next; + } + _mdns_set_u16(packet, MDNS_HEAD_ANSWERS_OFFSET, count); + + count = 0; + a = p->servers; + while (a) { + count += _mdns_append_answer(packet, &index, a, p->tcpip_if); + a = a->next; + } + _mdns_set_u16(packet, MDNS_HEAD_SERVERS_OFFSET, count); + + count = 0; + a = p->additional; + while (a) { + count += _mdns_append_answer(packet, &index, a, p->tcpip_if); + a = a->next; + } + _mdns_set_u16(packet, MDNS_HEAD_ADDITIONAL_OFFSET, count); + +#ifdef MDNS_ENABLE_DEBUG + _mdns_dbg_printf("\nTX[%u][%u]: ", p->tcpip_if, p->ip_protocol); + if (p->dst.type == IPADDR_TYPE_V4) { + _mdns_dbg_printf("To: " IPSTR ":%u, ", IP2STR(&p->dst.u_addr.ip4), p->port); + } else { + _mdns_dbg_printf("To: " IPV6STR ":%u, ", IPV62STR(p->dst.u_addr.ip6), p->port); + } + mdns_debug_packet(packet, index); +#endif + + _mdns_udp_pcb_write(p->tcpip_if, p->ip_protocol, &p->dst, p->port, packet, index); +} + +/** + * @brief frees a packet + * + * @param packet the packet + */ +static void _mdns_free_tx_packet(mdns_tx_packet_t * packet) +{ + if (!packet) { + return; + } + queueFree(mdns_out_question_t, packet->questions); + queueFree(mdns_out_answer_t, packet->answers); + queueFree(mdns_out_answer_t, packet->servers); + queueFree(mdns_out_answer_t, packet->additional); + free(packet); +} + +/** + * @brief schedules a packet to be sent after given milliseconds + * + * @param packet the packet + * @param ms_after number of milliseconds after which the packet should be dispatched + */ +static void _mdns_schedule_tx_packet(mdns_tx_packet_t * packet, uint32_t ms_after) +{ + if (!packet) { + return; + } + packet->send_at = (xTaskGetTickCount() * portTICK_PERIOD_MS) + ms_after; + packet->next = NULL; + if (!_mdns_server->tx_queue_head || _mdns_server->tx_queue_head->send_at > packet->send_at) { + packet->next = _mdns_server->tx_queue_head; + _mdns_server->tx_queue_head = packet; + return; + } + mdns_tx_packet_t * q = _mdns_server->tx_queue_head; + while (q->next && q->next->send_at <= packet->send_at) { + q = q->next; + } + packet->next = q->next; + q->next = packet; +} + +/** + * @brief free all packets scheduled for sending + */ +static void _mdns_clear_tx_queue_head() +{ + mdns_tx_packet_t * q; + while (_mdns_server->tx_queue_head) { + q = _mdns_server->tx_queue_head; + _mdns_server->tx_queue_head = _mdns_server->tx_queue_head->next; + _mdns_free_tx_packet(q); + } +} + +/** + * @brief clear packets scheduled for sending on a specific interface + * + * @param tcpip_if the interface + * @param ip_protocol pcb type V4/V6 + */ +static void _mdns_clear_pcb_tx_queue_head(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_tx_packet_t * q, * p; + while (_mdns_server->tx_queue_head && _mdns_server->tx_queue_head->tcpip_if == tcpip_if && _mdns_server->tx_queue_head->ip_protocol == ip_protocol) { + q = _mdns_server->tx_queue_head; + _mdns_server->tx_queue_head = _mdns_server->tx_queue_head->next; + _mdns_free_tx_packet(q); + } + if (_mdns_server->tx_queue_head) { + q = _mdns_server->tx_queue_head; + while (q->next) { + if (q->next->tcpip_if == tcpip_if && q->next->ip_protocol == ip_protocol) { + p = q->next; + q->next = p->next; + _mdns_free_tx_packet(p); + } else { + q = q->next; + } + } + } +} + +/** + * @brief get the next packet scheduled for sending on a specific interface + * + * @param tcpip_if the interface + * @param ip_protocol pcb type V4/V6 + */ +static mdns_tx_packet_t * _mdns_get_next_pcb_packet(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_tx_packet_t * q = _mdns_server->tx_queue_head; + while (q) { + if (q->tcpip_if == tcpip_if && q->ip_protocol == ip_protocol) { + return q; + } + q = q->next; + } + return NULL; +} + +/** + * @brief Find, remove and free answer from the scheduled packets + */ +static void _mdns_remove_scheduled_answer(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint16_t type, mdns_srv_item_t * service) +{ + mdns_srv_item_t s = {NULL, NULL}; + if (!service) { + service = &s; + } + mdns_tx_packet_t * q = _mdns_server->tx_queue_head; + while (q) { + if (q->tcpip_if == tcpip_if && q->ip_protocol == ip_protocol && q->distributed) { + mdns_out_answer_t * a = q->answers; + if (a->type == type && a->service == service->service) { + q->answers = q->answers->next; + free(a); + } else { + while (a->next) { + if (a->next->type == type && a->next->service == service->service) { + mdns_out_answer_t * b = a->next; + a->next = b->next; + free(b); + break; + } + a = a->next; + } + } + } + q = q->next; + } +} + +/** + * @brief Remove and free answer from answer list (destination) + */ +static void _mdns_dealloc_answer(mdns_out_answer_t ** destnation, uint16_t type, mdns_srv_item_t * service) +{ + mdns_out_answer_t * d = *destnation; + if (!d) { + return; + } + mdns_srv_item_t s = {NULL, NULL}; + if (!service) { + service = &s; + } + if (d->type == type && d->service == service->service) { + *destnation = d->next; + free(d); + return; + } + while (d->next) { + mdns_out_answer_t * a = d->next; + if (a->type == type && a->service == service->service) { + d->next = a->next; + free(a); + return; + } + d = d->next; + } +} + +/** + * @brief Allocate new answer and add it to answer list (destination) + */ +static bool _mdns_alloc_answer(mdns_out_answer_t ** destnation, uint16_t type, mdns_service_t * service, bool flush, bool bye) +{ + mdns_out_answer_t * d = *destnation; + while (d) { + if (d->type == type && d->service == service) { + return true; + } + d = d->next; + } + + mdns_out_answer_t * a = (mdns_out_answer_t *)malloc(sizeof(mdns_out_answer_t)); + if (!a) { + return false; + } + a->type = type; + a->service = service; + a->bye = bye; + a->flush = flush; + a->next = NULL; + queueToEnd(mdns_out_answer_t, *destnation, a); + return true; +} + +/** + * @brief Allocate new packet for sending + */ +static mdns_tx_packet_t * _mdns_alloc_packet_default(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_tx_packet_t * packet = (mdns_tx_packet_t*)malloc(sizeof(mdns_tx_packet_t)); + if (!packet) { + return NULL; + } + memset((uint8_t*)packet, 0, sizeof(mdns_tx_packet_t)); + packet->tcpip_if = tcpip_if; + packet->ip_protocol = ip_protocol; + packet->port = MDNS_SERVICE_PORT; + if (ip_protocol == MDNS_IP_PROTOCOL_V4) { + IP_ADDR4(&packet->dst, 224, 0, 0, 251); + } else { + ip_addr_t addr = IPADDR6_INIT(0x000002ff, 0, 0, 0xfb000000); + memcpy(&packet->dst, &addr, sizeof(ip_addr_t)); + } + return packet; +} + +/** + * @brief Create answer packet to questions from parsed packet + */ +static void _mdns_create_answer_from_parsed_packet(mdns_parsed_packet_t * parsed_packet) +{ + if (!parsed_packet->questions) { + return; + } + bool send_flush = parsed_packet->src_port == MDNS_SERVICE_PORT; + bool unicast = false; + bool shared = false; + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(parsed_packet->tcpip_if, parsed_packet->ip_protocol); + if (!packet) { + return; + } + packet->flags = MDNS_FLAGS_AUTHORITATIVE; + packet->distributed = parsed_packet->distributed; + + mdns_parsed_question_t * q = parsed_packet->questions; + while (q) { + mdns_srv_item_t * service = NULL; + if (q->service && q->proto) { + service = _mdns_get_service_item(q->service, q->proto); + if (!service) { + continue; + } + } + if (q->unicast) { + unicast = true; + } + if (service) { + if (q->type == MDNS_TYPE_PTR || q->type == MDNS_TYPE_ANY) { + if (q->type == MDNS_TYPE_PTR) { + shared = true; + } + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, service->service, false, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, service->service, send_flush, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, service->service, send_flush, false) + || !_mdns_alloc_answer(shared?&packet->additional:&packet->answers, MDNS_TYPE_A, NULL, send_flush, false) + || !_mdns_alloc_answer(shared?&packet->additional:&packet->answers, MDNS_TYPE_AAAA, NULL, send_flush, false)) { + _mdns_free_tx_packet(packet); + return; + } + } else if (q->type == MDNS_TYPE_SRV) { + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, service->service, send_flush, false) + || !_mdns_alloc_answer(&packet->additional, MDNS_TYPE_A, NULL, send_flush, false) + || !_mdns_alloc_answer(&packet->additional, MDNS_TYPE_AAAA, NULL, send_flush, false)) { + _mdns_free_tx_packet(packet); + return; + } + } else if (q->type == MDNS_TYPE_TXT) { + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, service->service, send_flush, false)) { + _mdns_free_tx_packet(packet); + return; + } + } else if (q->type == MDNS_TYPE_SDPTR) { + shared = true; + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, service->service, false, false)) { + _mdns_free_tx_packet(packet); + return; + } + } + } else { + if (q->type == MDNS_TYPE_ANY || q->type == MDNS_TYPE_A || q->type == MDNS_TYPE_AAAA) { + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_A, NULL, send_flush, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_AAAA, NULL, send_flush, false)) { + _mdns_free_tx_packet(packet); + return; + } + } else if (!_mdns_alloc_answer(&packet->answers, q->type, NULL, send_flush, false)) { + _mdns_free_tx_packet(packet); + return; + } + } + q = q->next; + } + if (unicast || !send_flush) { + memcpy(&packet->dst, &parsed_packet->src, sizeof(ip_addr_t)); + packet->port = parsed_packet->src_port; + } + + static uint8_t share_step = 0; + if (shared) { + _mdns_schedule_tx_packet(packet, 25 + (share_step * 25)); + share_step = (share_step + 1) & 0x03; + } else { + _mdns_dispatch_tx_packet(packet); + _mdns_free_tx_packet(packet); + } +} + +/** + * @brief Check if question is already in the list + */ +static bool _mdns_question_exists(mdns_out_question_t * needle, mdns_out_question_t * haystack) +{ + while (haystack) { + if (haystack->type == needle->type + && haystack->host == needle->host + && haystack->service == needle->service + && haystack->proto == needle->proto) { + return true; + } + haystack = haystack->next; + } + return false; +} + +/** + * @brief Create probe packet for particular services on particular PCB + */ +static mdns_tx_packet_t * _mdns_create_probe_packet(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t * services[], size_t len, bool first, bool include_ip) +{ + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol); + if (!packet) { + return NULL; + } + + size_t i; + for (i=0; inext = NULL; + q->unicast = first; + q->type = MDNS_TYPE_ANY; + q->host = _mdns_get_service_instance_name(services[i]->service); + q->service = services[i]->service->service; + q->proto = services[i]->service->proto; + q->domain = MDNS_DEFAULT_DOMAIN; + if (!q->host || _mdns_question_exists(q, packet->questions)) { + free(q); + } else { + queueToEnd(mdns_out_question_t, packet->questions, q); + } + + if (!q->host || !_mdns_alloc_answer(&packet->servers, MDNS_TYPE_SRV, services[i]->service, false, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + + if (include_ip && !_str_null_or_empty(_mdns_server->hostname)) { + mdns_out_question_t * q = (mdns_out_question_t *)malloc(sizeof(mdns_out_question_t)); + if (!q) { + _mdns_free_tx_packet(packet); + return NULL; + } + q->next = NULL; + q->unicast = first; + q->type = MDNS_TYPE_ANY; + q->host = _mdns_server->hostname; + q->service = NULL; + q->proto = NULL; + q->domain = MDNS_DEFAULT_DOMAIN; + if (_mdns_question_exists(q, packet->questions)) { + free(q); + } else { + queueToEnd(mdns_out_question_t, packet->questions, q); + } + + if (_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb) { + if (!_mdns_alloc_answer(&packet->servers, MDNS_TYPE_A, NULL, false, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + + if (_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb) { + if (!_mdns_alloc_answer(&packet->servers, MDNS_TYPE_AAAA, NULL, false, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + } + + return packet; +} + +/** + * @brief Create announce packet for particular services on particular PCB + */ +static mdns_tx_packet_t * _mdns_create_announce_packet(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t * services[], size_t len, bool include_ip) +{ + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol); + if (!packet) { + return NULL; + } + packet->flags = MDNS_FLAGS_AUTHORITATIVE; + + uint8_t i; + for (i=0; ianswers, MDNS_TYPE_SDPTR, services[i]->service, false, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, services[i]->service, false, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, services[i]->service, true, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, services[i]->service, true, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + if (include_ip) { + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_A, NULL, true, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_AAAA, NULL, true, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + return packet; +} + +/** + * @brief Convert probe packet to announce + */ +static mdns_tx_packet_t * _mdns_create_announce_from_probe(mdns_tx_packet_t * probe) +{ + + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(probe->tcpip_if, probe->ip_protocol); + if (!packet) { + return NULL; + } + packet->flags = MDNS_FLAGS_AUTHORITATIVE; + + mdns_out_answer_t * s = probe->servers; + while (s) { + if (s->type == MDNS_TYPE_SRV) { + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SDPTR, s->service, false, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, s->service, false, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, s->service, true, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, s->service, true, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + + } else if (s->type == MDNS_TYPE_A || s->type == MDNS_TYPE_AAAA) { + if (!_mdns_alloc_answer(&packet->answers, s->type, NULL, true, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + + s = s->next; + } + return packet; +} + +/** + * @brief Send by for particular services on particular PCB + */ +static void _mdns_pcb_send_bye(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t ** services, size_t len, bool include_ip) +{ + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol); + if (!packet) { + return; + } + size_t i; + for (i=0; ianswers, MDNS_TYPE_PTR, services[i]->service, true, true)) { + _mdns_free_tx_packet(packet); + return; + } + } + if (include_ip && (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_A, NULL, true, true) || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_AAAA, NULL, true, true))) { + _mdns_free_tx_packet(packet); + return; + } + _mdns_dispatch_tx_packet(packet); + _mdns_free_tx_packet(packet); +} + +/** + * @brief Send probe for particular services on particular PCB + */ +static void _mdns_init_pcb_probe(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t ** services, size_t len, bool probe_ip) +{ + mdns_pcb_t * pcb = &_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol]; + size_t services_final_len = len; + + _mdns_clear_pcb_tx_queue_head(tcpip_if, ip_protocol); + + if (_str_null_or_empty(_mdns_server->hostname)) { + pcb->state = PCB_RUNNING; + return; + } + + if (PCB_STATE_IS_PROBING(pcb)) { + services_final_len += pcb->probe_services_len; + } + mdns_srv_item_t ** _services = NULL; + if (services_final_len) { + _services = (mdns_srv_item_t **)malloc(sizeof(mdns_srv_item_t *) * services_final_len); + if (!_services) { + return; + } + + size_t i; + for (i=0; iprobe_services) { + for (i=0; iprobe_services_len; i++) { + _services[len+i] = pcb->probe_services[i]; + } + free(pcb->probe_services); + } + } + + probe_ip = pcb->probe_ip || probe_ip; + + pcb->probe_ip = false; + pcb->probe_services = NULL; + pcb->probe_services_len = 0; + pcb->probe_running = false; + + mdns_tx_packet_t * packet = _mdns_create_probe_packet(tcpip_if, ip_protocol, _services, services_final_len, true, probe_ip); + if (!packet) { + free(_services); + return; + } + + pcb->probe_ip = probe_ip; + pcb->probe_services = _services; + pcb->probe_services_len = services_final_len; + pcb->probe_running = true; + _mdns_schedule_tx_packet(packet, ((pcb->failed_probes > 5)?1000:120) + (esp_random() & 0x7F)); + pcb->state = PCB_PROBE_1; +} + +/** + * @brief Restart the responder on particular PCB + */ +static void _mdns_restart_pcb(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + size_t srv_count = 0; + mdns_srv_item_t * a = _mdns_server->services; + while (a) { + srv_count++; + a = a->next; + } + mdns_srv_item_t * services[srv_count]; + size_t i = 0; + a = _mdns_server->services; + while (a) { + services[i++] = a; + a = a->next; + } + _mdns_init_pcb_probe(tcpip_if, ip_protocol, services, srv_count, true); +} + +/** + * @brief Send by for particular services + */ +static void _mdns_send_bye(mdns_srv_item_t ** services, size_t len, bool include_ip) +{ + uint8_t i, j; + if (_str_null_or_empty(_mdns_server->hostname)) { + return; + } + + for (i=0; iinterfaces[i].pcbs[j].pcb && _mdns_server->interfaces[i].pcbs[j].state == PCB_RUNNING) { + _mdns_pcb_send_bye((tcpip_adapter_if_t)i, (mdns_ip_protocol_t)j, services, len, include_ip); + } + } + } +} + +/** + * @brief Send announcement on particular PCB + */ +static void _mdns_announce_pcb(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t ** services, size_t len, bool include_ip) +{ + mdns_pcb_t * _pcb = &_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol]; + size_t i; + if (_pcb->pcb) { + if (PCB_STATE_IS_PROBING(_pcb)) { + _mdns_init_pcb_probe(tcpip_if, ip_protocol, services, len, include_ip); + } else if (PCB_STATE_IS_ANNOUNCING(_pcb)) { + mdns_tx_packet_t * p = _mdns_get_next_pcb_packet(tcpip_if, ip_protocol); + if (p) { + for (i=0; ianswers, MDNS_TYPE_SDPTR, services[i]->service, false, false) + || !_mdns_alloc_answer(&p->answers, MDNS_TYPE_PTR, services[i]->service, false, false) + || !_mdns_alloc_answer(&p->answers, MDNS_TYPE_SRV, services[i]->service, true, false) + || !_mdns_alloc_answer(&p->answers, MDNS_TYPE_TXT, services[i]->service, true, false)) { + break; + } + } + if (include_ip) { + _mdns_dealloc_answer(&p->additional, MDNS_TYPE_A, NULL); + _mdns_dealloc_answer(&p->additional, MDNS_TYPE_AAAA, NULL); + _mdns_alloc_answer(&p->answers, MDNS_TYPE_A, NULL, true, false); + _mdns_alloc_answer(&p->answers, MDNS_TYPE_AAAA, NULL, true, false); + } + _pcb->state = PCB_ANNOUNCE_1; + } + } else if (_pcb->state == PCB_RUNNING) { + + if (_str_null_or_empty(_mdns_server->hostname)) { + return; + } + + _pcb->state = PCB_ANNOUNCE_1; + mdns_tx_packet_t * p = _mdns_create_announce_packet(tcpip_if, ip_protocol, services, len, include_ip); + if (p) { + _mdns_schedule_tx_packet(p, 0); + } + } + } +} + +/** + * @brief Send probe on all active PCBs + */ +static void _mdns_probe_all_pcbs(mdns_srv_item_t ** services, size_t len, bool probe_ip, bool clear_old_probe) +{ + uint8_t i, j; + for (i=0; iinterfaces[i].pcbs[j].pcb) { + mdns_pcb_t * _pcb = &_mdns_server->interfaces[i].pcbs[j]; + if (clear_old_probe) { + free(_pcb->probe_services); + _pcb->probe_services = NULL; + _pcb->probe_services_len = 0; + _pcb->probe_running = false; + } + _mdns_init_pcb_probe((tcpip_adapter_if_t)i, (mdns_ip_protocol_t)j, services, len, probe_ip); + } + } + } +} + +/** + * @brief Send announcement on all active PCBs + */ +static void _mdns_announce_all_pcbs(mdns_srv_item_t ** services, size_t len, bool include_ip) +{ + uint8_t i, j; + for (i=0; iservices; + while (a) { + if (!a->service->instance) { + srv_count++; + } + a = a->next; + } + if (!srv_count) { + return; + } + mdns_srv_item_t * services[srv_count]; + size_t i = 0; + a = _mdns_server->services; + while (a) { + if (!a->service->instance) { + services[i++] = a; + } + a = a->next; + } + _mdns_send_bye(services, srv_count, include_ip); +} + +/** + * @brief Stop the responder on all services without instance + */ +static void _mdns_send_bye_all_pcbs_no_instance() +{ + size_t srv_count = 0; + mdns_srv_item_t * a = _mdns_server->services; + while (a) { + if (!a->service->instance) { + srv_count++; + } + a = a->next; + } + if (!srv_count) { + return; + } + mdns_srv_item_t * services[srv_count]; + size_t i = 0; + a = _mdns_server->services; + while (a) { + if (!a->service->instance) { + services[i++] = a; + } + a = a->next; + } + _mdns_send_bye(services, srv_count, false); +} + +/** + * @brief Restart the responder on all services without instance + */ +static void _mdns_restart_all_pcbs_no_instance() +{ + size_t srv_count = 0; + mdns_srv_item_t * a = _mdns_server->services; + while (a) { + if (!a->service->instance) { + srv_count++; + } + a = a->next; + } + if (!srv_count) { + return; + } + mdns_srv_item_t * services[srv_count]; + size_t i = 0; + a = _mdns_server->services; + while (a) { + if (!a->service->instance) { + services[i++] = a; + } + a = a->next; + } + _mdns_probe_all_pcbs(services, srv_count, false, true); +} + +/** + * @brief Restart the responder on all active PCBs + */ +static void _mdns_restart_all_pcbs() +{ + _mdns_clear_tx_queue_head(); + size_t srv_count = 0; + mdns_srv_item_t * a = _mdns_server->services; + while (a) { + srv_count++; + a = a->next; + } + mdns_srv_item_t * services[srv_count]; + size_t l = 0; + a = _mdns_server->services; + while (a) { + services[l++] = a; + a = a->next; + } + + _mdns_probe_all_pcbs(services, srv_count, true, true); +} + + + +/** + * @brief creates/allocates new text item list + * @param num_items service number of txt items or 0 + * @param txt service txt items array or NULL + * + * @return pointer to the linked txt item list or NULL + */ +static mdns_txt_linked_item_t * _mdns_allocate_txt(size_t num_items, mdns_txt_item_t txt[]) +{ + mdns_txt_linked_item_t * new_txt = NULL; + size_t i = 0; + if (num_items) { + for (i=0; ikey = strdup(txt[i].key); + if (!new_item->key) { + free(new_item); + break; + } + new_item->value = strdup(txt[i].value); + if (!new_item->value) { + free((char *)new_item->key); + free(new_item); + break; + } + new_item->next = new_txt; + new_txt = new_item; + } + } + return new_txt; +} + +/** + * @brief creates/allocates new service + * @param service service type + * @param proto service proto + * @param port service port + * @param instance service instance + * @param num_items service number of txt items or 0 + * @param txt service txt items array or NULL + * + * @return pointer to the service or NULL on error + */ +static mdns_service_t * _mdns_create_service(const char * service, const char * proto, uint16_t port, const char * instance, size_t num_items, mdns_txt_item_t txt[]) +{ + mdns_service_t * s = (mdns_service_t *)malloc(sizeof(mdns_service_t)); + if (!s) { + return NULL; + } + + mdns_txt_linked_item_t * new_txt = _mdns_allocate_txt(num_items, txt); + if (num_items && new_txt == NULL) { + free(s); + return NULL; + } + + s->priority = 0; + s->weight = 0; + s->instance = instance?strndup(instance, MDNS_NAME_BUF_LEN - 1):NULL; + s->txt = new_txt; + s->port = port; + + s->service = strndup(service, MDNS_NAME_BUF_LEN - 1); + if (!s->service) { + free(s); + return NULL; + } + + s->proto = strndup(proto, MDNS_NAME_BUF_LEN - 1); + if (!s->proto) { + free((char *)s->service); + free(s); + return NULL; + } + + return s; +} + +/** + * @brief Remove and free service answer from answer list (destination) + */ +static void _mdns_dealloc_scheduled_service_answers(mdns_out_answer_t ** destination, mdns_service_t * service) +{ + mdns_out_answer_t * d = *destination; + if (!d) { + return; + } + while (d && d->service == service) { + *destination = d->next; + free(d); + d = *destination; + } + while (d && d->next) { + mdns_out_answer_t * a = d->next; + if (a->service == service) { + d->next = a->next; + free(a); + } else { + d = d->next; + } + } +} + +/** + * @brief Find, remove and free answers and scheduled packets for service + */ +static void _mdns_remove_scheduled_service_packets(mdns_service_t * service) +{ + mdns_tx_packet_t * p = NULL; + mdns_tx_packet_t * q = _mdns_server->tx_queue_head; + while (q) { + bool had_answers = (q->answers != NULL); + + _mdns_dealloc_scheduled_service_answers(&(q->answers), service); + _mdns_dealloc_scheduled_service_answers(&(q->additional), service); + _mdns_dealloc_scheduled_service_answers(&(q->servers), service); + + + mdns_pcb_t * _pcb = &_mdns_server->interfaces[q->tcpip_if].pcbs[q->ip_protocol]; + if(_pcb->pcb) { + if (PCB_STATE_IS_PROBING(_pcb)) { + uint8_t i; + //check if we are probing this service + for (i=0; i<_pcb->probe_services_len; i++) { + mdns_srv_item_t * s = _pcb->probe_services[i]; + if (s->service == service){ + break; + } + } + if (i < _pcb->probe_services_len) { + if (_pcb->probe_services_len > 1) { + uint8_t n; + for (n=(i+1); n<_pcb->probe_services_len; n++) { + _pcb->probe_services[n-1] = _pcb->probe_services[n]; + } + _pcb->probe_services_len--; + } else { + _pcb->probe_services_len = 0; + free(_pcb->probe_services); + _pcb->probe_services = NULL; + if (!_pcb->probe_ip) { + _pcb->probe_running = false; + _pcb->state = PCB_RUNNING; + } + } + + if (q->questions) { + mdns_out_question_t * qsn = NULL; + mdns_out_question_t * qs = q->questions; + if (qs->type == MDNS_TYPE_ANY + && qs->service && strcmp(qs->service, service->service) == 0 + && qs->proto && strcmp(qs->proto, service->proto) == 0) + { + q->questions = q->questions->next; + free(qs); + } else while (qs->next) { + qsn = qs->next; + if (qsn->type == MDNS_TYPE_ANY + && qsn->service && strcmp(qsn->service, service->service) == 0 + && qsn->proto && strcmp(qsn->proto, service->proto) == 0) + { + qs->next = qsn->next; + free(qsn); + break; + } + qs = qs->next; + } + } + } + } else if (PCB_STATE_IS_ANNOUNCING(_pcb)) { + //if answers were cleared, set to running + if (had_answers && q->answers == NULL) { + _pcb->state = PCB_RUNNING; + } + } + } + + p = q; + q = q->next; + if(!p->questions && !p->answers && !p->additional && !p->servers){ + queueDetach(mdns_tx_packet_t, _mdns_server->tx_queue_head, p); + _mdns_free_tx_packet(p); + } + } +} + +/** + * @brief free service memory + * + * @param service the service + */ +static void _mdns_free_service(mdns_service_t * service) +{ + if (!service) { + return; + } + _mdns_remove_scheduled_service_packets(service); + free((char *)service->instance); + free((char *)service->service); + free((char *)service->proto); + while (service->txt) { + mdns_txt_linked_item_t * s = service->txt; + service->txt = service->txt->next; + free((char *)s->key); + free((char *)s->value); + free(s); + } + free(service->txt); + free(service); +} + + +/* + * Received Packet Handling + * */ + +/** + * @brief Detect SRV collision + */ +static int _mdns_check_srv_collision(mdns_service_t * service, uint16_t priority, uint16_t weight, uint16_t port, const char * host, const char * domain) +{ + if (_str_null_or_empty(_mdns_server->hostname)) { + return 0; + } + + size_t our_host_len = strlen(_mdns_server->hostname); + size_t our_len = 14 + our_host_len; + + size_t their_host_len = strlen(host); + size_t their_domain_len = strlen(domain); + size_t their_len = 9 + their_host_len + their_domain_len; + + if (their_len > our_len) { + return 1;//they win + } else if (their_len < our_len) { + return -1;//we win + } + + uint16_t our_index = 0; + uint8_t our_data[our_len]; + _mdns_append_u16(our_data, &our_index, service->priority); + _mdns_append_u16(our_data, &our_index, service->weight); + _mdns_append_u16(our_data, &our_index, service->port); + our_data[our_index++] = our_host_len; + memcpy(our_data + our_index, _mdns_server->hostname, our_host_len); + our_index += our_host_len; + our_data[our_index++] = 5; + memcpy(our_data + our_index, MDNS_DEFAULT_DOMAIN, 5); + our_index += 5; + our_data[our_index++] = 0; + + uint16_t their_index = 0; + uint8_t their_data[their_len]; + _mdns_append_u16(their_data, &their_index, priority); + _mdns_append_u16(their_data, &their_index, weight); + _mdns_append_u16(their_data, &their_index, port); + their_data[their_index++] = their_host_len; + memcpy(their_data + their_index, host, their_host_len); + their_index += their_host_len; + their_data[their_index++] = their_domain_len; + memcpy(their_data + their_index, domain, their_domain_len); + their_index += their_domain_len; + their_data[their_index++] = 0; + + int ret = memcmp(our_data, their_data, our_len); + if (ret > 0) { + return -1;//we win + } else if (ret < 0) { + return 1;//they win + } + return 0;//same +} + +/** + * @brief Detect TXT collision + */ +static int _mdns_check_txt_collision(mdns_service_t * service, const uint8_t * data, size_t len) +{ + size_t data_len = 1; + if (len == 1 && service->txt) { + return -1;//we win + } else if (len > 1 && !service->txt) { + return 1;//they win + } else if (len == 1 && !service->txt) { + return 0;//same + } + + mdns_txt_linked_item_t * txt = service->txt; + while (txt) { + data_len += 2 + strlen(service->txt->key) + strlen(service->txt->value); + txt = txt->next; + } + + if (len > data_len) { + return 1;//they win + } else if (len < data_len) { + return -1;//we win + } + + uint8_t ours[len]; + uint16_t index = 0; + char * tmp; + + txt = service->txt; + while (txt) { + tmp = (char *)malloc(2 + strlen(txt->key) + strlen(txt->value)); + if (tmp) { + sprintf(tmp, "%s=%s", txt->key, txt->value); + _mdns_append_string(ours, &index, tmp); + free(tmp); + } + txt = txt->next; + } + + int ret = memcmp(ours, data, len); + if (ret > 0) { + return -1;//we win + } else if (ret < 0) { + return 1;//they win + } + return 0;//same +} + +/** + * @brief Set interface as duplicate if another is found on the same subnet + */ +static void _mdns_dup_interface(tcpip_adapter_if_t tcpip_if) +{ + uint8_t i; + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + for (i=0; iinterfaces[other_if].pcbs[i].pcb) { + //stop this interface and mark as dup + if (_mdns_server->interfaces[tcpip_if].pcbs[i].pcb) { + _mdns_clear_pcb_tx_queue_head(tcpip_if, i); + _mdns_pcb_deinit(tcpip_if, i); + } + _mdns_server->interfaces[tcpip_if].pcbs[i].state = PCB_DUP; + _mdns_announce_pcb(other_if, i, NULL, 0, true); + } + } +} + +/** + * @brief Detect IPv4 address collision + */ +static int _mdns_check_a_collision(ip4_addr_t * ip, tcpip_adapter_if_t tcpip_if) +{ + tcpip_adapter_ip_info_t if_ip_info; + tcpip_adapter_ip_info_t other_ip_info; + if (!ip->addr) { + return 1;//denial! they win + } + if (tcpip_adapter_get_ip_info(tcpip_if, &if_ip_info)) { + return 1;//they win + } + + int ret = memcmp((uint8_t*)&if_ip_info.ip.addr, (uint8_t*)&ip->addr, sizeof(ip4_addr_t)); + if (ret > 0) { + return -1;//we win + } else if (ret < 0) { + //is it the other interface? + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (other_if == TCPIP_ADAPTER_IF_MAX) { + return 1;//AP interface! They win + } + if (tcpip_adapter_get_ip_info(other_if, &other_ip_info)) { + return 1;//IPv4 not active! They win + } + if (ip->addr != other_ip_info.ip.addr) { + return 1;//IPv4 not ours! They win + } + _mdns_dup_interface(tcpip_if); + return 2;//they win + } + return 0;//same +} + +/** + * @brief Detect IPv6 address collision + */ +static int _mdns_check_aaaa_collision(ip6_addr_t * ip, tcpip_adapter_if_t tcpip_if) +{ + struct ip6_addr if_ip6; + struct ip6_addr other_ip6; + if (_ipv6_address_is_zero(*ip)) { + return 1;//denial! they win + } + if (tcpip_adapter_get_ip6_linklocal(tcpip_if, &if_ip6)) { + return 1;//they win + } + int ret = memcmp((uint8_t*)&if_ip6.addr, (uint8_t*)ip->addr, sizeof(ip6_addr_t)); + if (ret > 0) { + return -1;//we win + } else if (ret < 0) { + //is it the other interface? + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (other_if == TCPIP_ADAPTER_IF_MAX) { + return 1;//AP interface! They win + } + if (tcpip_adapter_get_ip6_linklocal(other_if, &other_ip6)) { + return 1;//IPv6 not active! They win + } + if (memcmp((uint8_t*)&other_ip6.addr, (uint8_t*)ip->addr, sizeof(ip6_addr_t))) { + return 1;//IPv6 not ours! They win + } + _mdns_dup_interface(tcpip_if); + return 2;//they win + } + return 0;//same +} + +/** + * @brief Check if parsed name is discovery + */ +static bool _mdns_name_is_discovery(mdns_name_t * name, uint16_t type) +{ + return ( + (name->host && name->host[0] && !strcasecmp(name->host, "_services")) + && (name->service && name->service[0] && !strcasecmp(name->service, "_dns-sd")) + && (name->proto && name->proto[0] && !strcasecmp(name->proto, "_udp")) + && (name->domain && name->domain[0] && !strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN)) + && type == MDNS_TYPE_PTR + ); +} + +/** + * @brief Check if the parsed name is ours (matches service or host name) + */ +static bool _mdns_name_is_ours(mdns_name_t * name) +{ + //domain have to be "local" + if (_str_null_or_empty(name->domain) || strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN)) { + return false; + } + + //if service and proto are empty, host must match out hostname + if (_str_null_or_empty(name->service) && _str_null_or_empty(name->proto)) { + if (!_str_null_or_empty(name->host) + && !_str_null_or_empty(_mdns_server->hostname) + && strcasecmp(name->host, _mdns_server->hostname) == 0) + { + return true; + } + return false; + } + + //if service or proto is empty, name is invalid + if (_str_null_or_empty(name->service) || _str_null_or_empty(name->proto)) { + return false; + } + + //find the service + mdns_srv_item_t * service = _mdns_get_service_item(name->service, name->proto); + if (!service) { + return false; + } + + //if host is empty and we have service, we have success + if (_str_null_or_empty(name->host)) { + return true; + } + + //OK we have host in the name. find what is the instance of the service + const char * instance = _mdns_get_service_instance_name(service->service); + if (instance == NULL) { + return false; + } + + //compare the instance against the name + if (strcasecmp(name->host, instance) == 0) { + return true; + } + + return false; +} + +/** + * @brief read uint16_t from a packet + * @param packet the packet + * @param index index in the packet where the value starts + * + * @return the value + */ +static inline uint16_t _mdns_read_u16(const uint8_t * packet, uint16_t index) +{ + return (uint16_t)(packet[index]) << 8 | packet[index+1]; +} + +/** + * @brief read uint32_t from a packet + * @param packet the packet + * @param index index in the packet where the value starts + * + * @return the value + */ +static inline uint32_t _mdns_read_u32(const uint8_t * packet, uint16_t index) +{ + return (uint32_t)(packet[index]) << 24 | (uint32_t)(packet[index+1]) << 16 | (uint32_t)(packet[index+2]) << 8 | packet[index+3]; +} + +/** + * @brief reads and formats MDNS FQDN into mdns_name_t structure + * + * @param packet MDNS packet + * @param start Starting point of FQDN + * @param name mdns_name_t structure to populate + * + * @return the address after the parsed FQDN in the packet or NULL on error + */ +static const uint8_t * _mdns_parse_fqdn(const uint8_t * packet, const uint8_t * start, mdns_name_t * name) +{ + name->parts = 0; + name->sub = 0; + name->host[0] = 0; + name->service[0] = 0; + name->proto[0] = 0; + name->domain[0] = 0; + + static char buf[MDNS_NAME_BUF_LEN]; + + const uint8_t * next_data = (uint8_t*)_mdns_read_fqdn(packet, start, name, buf); + if (!next_data) { + return 0; + } + if (!name->parts) { + return next_data; + } + if (name->parts == 3) { + memmove((uint8_t*)name + (MDNS_NAME_BUF_LEN), (uint8_t*)name, 3*(MDNS_NAME_BUF_LEN)); + name->host[0] = 0; + } else if (name->parts == 2) { + memmove((uint8_t*)(name->domain), (uint8_t*)(name->service), (MDNS_NAME_BUF_LEN)); + name->service[0] = 0; + name->proto[0] = 0; + } + if (strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN) == 0 || strcasecmp(name->domain, "arpa") == 0) { + return next_data; + } + return 0; +} + +/** + * @brief Called from parser to check if question matches particular service + */ +static bool _mdns_question_matches(mdns_parsed_question_t * question, uint16_t type, mdns_srv_item_t * service) +{ + if (question->type != type) { + return false; + } + if (type == MDNS_TYPE_A || type == MDNS_TYPE_AAAA) { + return true; + } else if (type == MDNS_TYPE_PTR || type == MDNS_TYPE_SDPTR) { + if (!strcasecmp(service->service->service, question->service) + && !strcasecmp(service->service->proto, question->proto) + && !strcasecmp(MDNS_DEFAULT_DOMAIN, question->domain)) { + return true; + } + } else if (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT) { + const char * name = _mdns_get_service_instance_name(service->service); + if (name && question->host && !strcasecmp(name, question->host) + && !strcasecmp(service->service->service, question->service) + && !strcasecmp(service->service->proto, question->proto) + && !strcasecmp(MDNS_DEFAULT_DOMAIN, question->domain)) { + return true; + } + } + + return false; +} + +/** + * @brief Removes saved question from parsed data + */ +static void _mdns_remove_parsed_question(mdns_parsed_packet_t * parsed_packet, uint16_t type, mdns_srv_item_t * service) +{ + mdns_parsed_question_t * q = parsed_packet->questions; + + if (_mdns_question_matches(q, type, service)) { + parsed_packet->questions = q->next; + free(q->host); + free(q->service); + free(q->proto); + free(q->domain); + free(q); + return; + } + + while (q->next) { + mdns_parsed_question_t * p = q->next; + if (_mdns_question_matches(p, type, service)) { + q->next = p->next; + free(p->host); + free(p->service); + free(p->proto); + free(p->domain); + free(p); + return; + } + q = q->next; + } +} + +/** + * @brief Get number of items in TXT parsed data + */ +static int _mdns_txt_items_count_get(const uint8_t * data, size_t len) +{ + if (len == 1) { + return 0; + } + + int num_items = 0; + uint16_t i=0; + size_t partLen = 0; + + while (i < len) { + partLen = data[i++]; + if (!partLen) { + break; + } + if ((i+partLen) > len) { + return -1;//error + } + i+=partLen; + num_items++; + } + return num_items; +} + +/** + * @brief Get the length of TXT item's key name + */ +static int _mdns_txt_item_name_get_len(const uint8_t * data, size_t len) +{ + int i; + if (*data == '=') { + return -1; + } + for (i = 0; i < len; i++) { + if (data[i] == '=') { + return i; + } + } + return len; +} + +/** + * @brief Create TXT result array from parsed TXT data + */ +static void _mdns_result_txt_create(const uint8_t * data, size_t len, mdns_txt_item_t ** out_txt, size_t * out_count) +{ + *out_txt = NULL; + *out_count = 0; + uint16_t i=0, y; + size_t partLen = 0; + int num_items = _mdns_txt_items_count_get(data, len); + if (num_items < 0) { + return;//error + } + + if (!num_items) { + return; + } + + mdns_txt_item_t * txt = (mdns_txt_item_t *)malloc(sizeof(mdns_txt_item_t) * num_items); + memset(txt, 0, sizeof(mdns_txt_item_t) * num_items); + size_t txt_num = 0; + + while (i < len) { + partLen = data[i++]; + if (!partLen) { + break; + } + + if ((i+partLen) > len) { + goto handle_error;//error + } + + int name_len = _mdns_txt_item_name_get_len(data+i, partLen); + if (name_len < 0) {//invalid item (no name) + i += partLen; + continue; + } + char * key = (char *)malloc(name_len + 1); + if (!key) { + goto handle_error;//error + } + + mdns_txt_item_t * t = &txt[txt_num++]; + + memcpy(key, data + i, name_len); + key[name_len] = 0; + i += name_len + 1; + t->key = key; + + int value_len = partLen - name_len - 1; + if (value_len > 0) { + char * value = (char *)malloc(value_len + 1); + memcpy(value, data + i, value_len); + value[value_len] = 0; + i += value_len; + t->value = value; + } + } + + *out_txt = txt; + *out_count = txt_num; + return; + +handle_error : + for (y=0; ykey); + free((char *)t->value); + } + free(txt); +} + +/** + * @brief Duplicate string or return error + */ +static esp_err_t _mdns_strdup_check(char ** out, char * in) +{ + if (in && in[0]) { + *out = strdup(in); + if (!*out) { + return ESP_FAIL; + } + return ESP_OK; + } + *out = NULL; + return ESP_OK; +} + +/** + * @brief main packet parser + * + * @param packet the packet + */ +void mdns_parse_packet(mdns_rx_packet_t * packet) +{ + static mdns_name_t n; + mdns_header_t header; + const uint8_t * data = (const uint8_t*)packet->pb->payload; + size_t len = packet->pb->len; + const uint8_t * content = data + MDNS_HEAD_LEN; + bool do_not_reply = false; + mdns_search_once_t * search_result = NULL; + +#ifdef MDNS_ENABLE_DEBUG + _mdns_dbg_printf("\nRX[%u][%u]: ", packet->tcpip_if, (uint32_t)packet->ip_protocol); + if (packet->src.type == IPADDR_TYPE_V4) { + _mdns_dbg_printf("From: " IPSTR ":%u, To: " IPSTR ", ", IP2STR(&packet->src.u_addr.ip4), packet->src_port, IP2STR(&packet->dest.u_addr.ip4)); + } else { + _mdns_dbg_printf("From: " IPV6STR ":%u, To: " IPV6STR ", ", IPV62STR(packet->src.u_addr.ip6), packet->src_port, IPV62STR(packet->dest.u_addr.ip6)); + } + mdns_debug_packet(data, len); +#endif + + mdns_parsed_packet_t * parsed_packet = (mdns_parsed_packet_t *)malloc(sizeof(mdns_parsed_packet_t)); + if (!parsed_packet) { + return; + } + memset(parsed_packet, 0, sizeof(mdns_parsed_packet_t)); + + mdns_name_t * name = &n; + memset(name, 0, sizeof(mdns_name_t)); + + header.id = _mdns_read_u16(data, MDNS_HEAD_ID_OFFSET); + header.flags.value = _mdns_read_u16(data, MDNS_HEAD_FLAGS_OFFSET); + header.questions = _mdns_read_u16(data, MDNS_HEAD_QUESTIONS_OFFSET); + header.answers = _mdns_read_u16(data, MDNS_HEAD_ANSWERS_OFFSET); + header.servers = _mdns_read_u16(data, MDNS_HEAD_SERVERS_OFFSET); + header.additional = _mdns_read_u16(data, MDNS_HEAD_ADDITIONAL_OFFSET); + + if (header.flags.value == MDNS_FLAGS_AUTHORITATIVE && packet->src_port != MDNS_SERVICE_PORT) { + free(parsed_packet); + return; + } + + //if we have not set the hostname, we can not answer questions + if (header.questions && _str_null_or_empty(_mdns_server->hostname)) { + free(parsed_packet); + return; + } + + parsed_packet->tcpip_if = packet->tcpip_if; + parsed_packet->ip_protocol = packet->ip_protocol; + parsed_packet->multicast = packet->multicast; + parsed_packet->authoritative = header.flags.value == MDNS_FLAGS_AUTHORITATIVE; + parsed_packet->distributed = header.flags.value == MDNS_FLAGS_DISTRIBUTED; + ip_addr_copy(parsed_packet->src, packet->src); + parsed_packet->src_port = packet->src_port; + + if (header.questions) { + uint8_t qs = header.questions; + + while (qs--) { + content = _mdns_parse_fqdn(data, content, name); + if (!content) { + header.answers = 0; + header.additional = 0; + header.servers = 0; + goto clear_rx_packet;//error + } + + uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET); + uint16_t clas = _mdns_read_u16(content, MDNS_CLASS_OFFSET); + bool unicast = !!(clas & 0x8000); + clas &= 0x7FFF; + content = content + 4; + + if (clas != 0x0001) {//bad class + continue; + } + + if (_mdns_name_is_discovery(name, type)) { + //service discovery + parsed_packet->discovery = true; + mdns_srv_item_t * a = _mdns_server->services; + while (a) { + mdns_parsed_question_t * question = (mdns_parsed_question_t *)malloc(sizeof(mdns_parsed_question_t)); + if (!question) { + goto clear_rx_packet; + } + question->next = parsed_packet->questions; + parsed_packet->questions = question; + + question->unicast = unicast; + question->type = MDNS_TYPE_SDPTR; + question->host = NULL; + question->service = strdup(a->service->service); + question->proto = strdup(a->service->proto); + question->domain = strdup(MDNS_DEFAULT_DOMAIN); + if (!question->service || !question->proto || !question->domain) { + goto clear_rx_packet; + } + a = a->next; + } + continue; + } else if (name->sub || !_mdns_name_is_ours(name)) { + continue; + } + + if (type == MDNS_TYPE_ANY) { + parsed_packet->probe = true; + } + + mdns_parsed_question_t * question = (mdns_parsed_question_t *)malloc(sizeof(mdns_parsed_question_t)); + if (!question) { + goto clear_rx_packet; + } + question->next = parsed_packet->questions; + parsed_packet->questions = question; + + question->unicast = unicast; + question->type = type; + if (_mdns_strdup_check(&(question->host), name->host) + || _mdns_strdup_check(&(question->service), name->service) + || _mdns_strdup_check(&(question->proto), name->proto) + || _mdns_strdup_check(&(question->domain), name->domain)) { + goto clear_rx_packet; + } + } + } + + if (header.questions && !parsed_packet->questions && !parsed_packet->discovery) { + goto clear_rx_packet; + } else if (header.answers || header.servers || header.additional) { + uint16_t recordIndex = 0; + + while (content < (data + len)) { + + content = _mdns_parse_fqdn(data, content, name); + if (!content) { + goto clear_rx_packet;//error + } + + uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET); + uint16_t clas = _mdns_read_u16(content, MDNS_CLASS_OFFSET); + uint32_t ttl = _mdns_read_u32(content, MDNS_TTL_OFFSET); + uint16_t data_len = _mdns_read_u16(content, MDNS_LEN_OFFSET); + const uint8_t * data_ptr = content + MDNS_DATA_OFFSET; + clas &= 0x7FFF; + + content = data_ptr + data_len; + if (content > (data + len)) { + goto clear_rx_packet; + } + + bool discovery = false; + bool ours = false; + mdns_srv_item_t * service = NULL; + mdns_parsed_recort_type_t record_type = MDNS_ANSWER; + + if (recordIndex >= (header.answers + header.servers)) { + record_type = MDNS_EXTRA; + } else if (recordIndex >= (header.answers)) { + record_type = MDNS_NS; + } + recordIndex++; + + if (type == MDNS_TYPE_NSEC || type == MDNS_TYPE_OPT) { + //skip NSEC and OPT + continue; + } + + if (parsed_packet->discovery && _mdns_name_is_discovery(name, type)) { + discovery = true; + } else if (!name->sub && _mdns_name_is_ours(name)) { + ours = true; + if (name->service && name->service[0] && name->proto && name->proto[0]) { + service = _mdns_get_service_item(name->service, name->proto); + } + } else { + if (header.questions || !parsed_packet->authoritative || record_type == MDNS_NS) { + //skip this record + continue; + } + search_result = _mdns_search_find_from(_mdns_server->search_once, name, type, packet->tcpip_if, packet->ip_protocol); + } + + if (type == MDNS_TYPE_PTR) { + if (!_mdns_parse_fqdn(data, data_ptr, name)) { + continue;//error + } + if (search_result) { + _mdns_search_result_add_ptr(search_result, name->host, packet->tcpip_if, packet->ip_protocol); + } else if ((discovery || ours) && !name->sub && _mdns_name_is_ours(name)) { + if (discovery) { + service = _mdns_get_service_item(name->service, name->proto); + _mdns_remove_parsed_question(parsed_packet, MDNS_TYPE_SDPTR, service); + } else if (parsed_packet->questions && !parsed_packet->probe) { + _mdns_remove_parsed_question(parsed_packet, type, service); + } else { + //check if TTL is more than half of the full TTL value (4500) + if (ttl > 2250) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service); + } + } + } + } else if (type == MDNS_TYPE_SRV) { + mdns_result_t * result = NULL; + if (search_result && search_result->type == MDNS_TYPE_PTR) { + result = search_result->result; + while (result) { + if (packet->tcpip_if == result->tcpip_if + && packet->ip_protocol == result->ip_protocol + && result->instance_name && !strcmp(name->host, result->instance_name)) { + break; + } + result = result->next; + } + if (!result) { + result = _mdns_search_result_add_ptr(search_result, name->host, packet->tcpip_if, packet->ip_protocol); + if (!result) { + continue;//error + } + } + } + + if (!_mdns_parse_fqdn(data, data_ptr + MDNS_SRV_FQDN_OFFSET, name)) { + continue;//error + } + uint16_t priority = _mdns_read_u16(data_ptr, MDNS_SRV_PRIORITY_OFFSET); + uint16_t weight = _mdns_read_u16(data_ptr, MDNS_SRV_WEIGHT_OFFSET); + uint16_t port = _mdns_read_u16(data_ptr, MDNS_SRV_PORT_OFFSET); + + if (search_result) { + if (search_result->type == MDNS_TYPE_PTR) { + result->port = port; + result->hostname = strdup(name->host); + } else { + _mdns_search_result_add_srv(search_result, name->host, port, packet->tcpip_if, packet->ip_protocol); + } + } else if (ours) { + if (parsed_packet->questions && !parsed_packet->probe) { + _mdns_remove_parsed_question(parsed_packet, type, service); + continue; + } else if (parsed_packet->distributed) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service); + continue; + } + //detect collision (-1=won, 0=none, 1=lost) + int col = 0; + if (clas > 1) { + col = 1; + } else if (!clas) { + col = -1; + } else { + col = _mdns_check_srv_collision(service->service, priority, weight, port, name->host, name->domain); + } + if (col && (parsed_packet->probe || parsed_packet->authoritative)) { + if (col > 0 || !port) { + do_not_reply = true; + if (_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + _mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].failed_probes++; + if (!_str_null_or_empty(service->service->instance)) { + char * new_instance = _mdns_mangle_name((char *)service->service->instance); + if (new_instance) { + free((char *)service->service->instance); + service->service->instance = new_instance; + } + _mdns_probe_all_pcbs(&service, 1, false, false); + } else if (!_str_null_or_empty(_mdns_server->instance)) { + char * new_instance = _mdns_mangle_name((char *)_mdns_server->instance); + if (new_instance) { + free((char *)_mdns_server->instance); + _mdns_server->instance = new_instance; + } + _mdns_restart_all_pcbs_no_instance(); + } else { + char * new_host = _mdns_mangle_name((char *)_mdns_server->hostname); + if (new_host) { + free((char *)_mdns_server->hostname); + _mdns_server->hostname = new_host; + } + _mdns_restart_all_pcbs(); + } + } else { + _mdns_pcb_send_bye(packet->tcpip_if, packet->ip_protocol, &service, 1, false); + _mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, &service, 1, false); + } + } + } else if (ttl > 60 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service); + } + } + } else if (type == MDNS_TYPE_TXT) { + if (search_result) { + mdns_txt_item_t * txt = NULL; + size_t txt_count = 0; + _mdns_result_txt_create(data_ptr, data_len, &txt, &txt_count); + if (!txt_count) { + continue; + } + + mdns_result_t * result = NULL; + if (search_result->type == MDNS_TYPE_PTR) { + result = search_result->result; + while (result) { + if (packet->tcpip_if == result->tcpip_if + && packet->ip_protocol == result->ip_protocol + && result->instance_name && !strcmp(name->host, result->instance_name)) { + break; + } + result = result->next; + } + if (!result) { + result = _mdns_search_result_add_ptr(search_result, name->host, packet->tcpip_if, packet->ip_protocol); + if (!result) { + continue;//error + } + } + if (!result->txt) { + result->txt = txt; + result->txt_count = txt_count; + } + } else { + _mdns_search_result_add_txt(search_result, txt, txt_count, packet->tcpip_if, packet->ip_protocol); + } + } else if (ours) { + if (parsed_packet->questions && !parsed_packet->probe) { + _mdns_remove_parsed_question(parsed_packet, type, service); + continue; + } + //detect collision (-1=won, 0=none, 1=lost) + int col = 0; + if (clas > 1) { + col = 1; + } else if (!clas) { + col = -1; + } else { + col = _mdns_check_txt_collision(service->service, data_ptr, data_len); + } + if (col && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + do_not_reply = true; + _mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, &service, 1, true); + } else if (ttl > 2250 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service); + } + } + + } else if (type == MDNS_TYPE_AAAA) {//ipv6 + ip_addr_t ip6; + ip6.type = IPADDR_TYPE_V6; + memcpy(ip6.u_addr.ip6.addr, data_ptr, 16); + if (search_result) { + //check for more applicable searches (PTR & A/AAAA at the same time) + while (search_result) { + _mdns_search_result_add_ip(search_result, name->host, &ip6, packet->tcpip_if, packet->ip_protocol); + search_result = _mdns_search_find_from(search_result->next, name, type, packet->tcpip_if, packet->ip_protocol); + } + } else if (ours) { + if (parsed_packet->questions && !parsed_packet->probe) { + _mdns_remove_parsed_question(parsed_packet, type, NULL); + continue; + } + //detect collision (-1=won, 0=none, 1=lost) + int col = 0; + if (clas > 1) { + col = 1; + } else if (!clas) { + col = -1; + } else { + col = _mdns_check_aaaa_collision(&(ip6.u_addr.ip6), packet->tcpip_if); + } + if (col == 2) { + goto clear_rx_packet; + } else if (col == 1) { + do_not_reply = true; + if (_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + if (col && (parsed_packet->probe || parsed_packet->authoritative)) { + _mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].failed_probes++; + char * new_host = _mdns_mangle_name((char *)_mdns_server->hostname); + if (new_host) { + free((char *)_mdns_server->hostname); + _mdns_server->hostname = new_host; + } + _mdns_restart_all_pcbs(); + } + } else { + _mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, NULL, 0, true); + } + } else if (ttl > 60 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, NULL); + } + } + + } else if (type == MDNS_TYPE_A) { + ip_addr_t ip; + ip.type = IPADDR_TYPE_V4; + memcpy(&(ip.u_addr.ip4.addr), data_ptr, 4); + if (search_result) { + //check for more applicable searches (PTR & A/AAAA at the same time) + while (search_result) { + _mdns_search_result_add_ip(search_result, name->host, &ip, packet->tcpip_if, packet->ip_protocol); + search_result = _mdns_search_find_from(search_result->next, name, type, packet->tcpip_if, packet->ip_protocol); + } + } else if (ours) { + if (parsed_packet->questions && !parsed_packet->probe) { + _mdns_remove_parsed_question(parsed_packet, type, NULL); + continue; + } + //detect collision (-1=won, 0=none, 1=lost) + int col = 0; + if (clas > 1) { + col = 1; + } else if (!clas) { + col = -1; + } else { + col = _mdns_check_a_collision(&(ip.u_addr.ip4), packet->tcpip_if); + } + if (col == 2) { + goto clear_rx_packet; + } else if (col == 1) { + do_not_reply = true; + if (_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + if (col && (parsed_packet->probe || parsed_packet->authoritative)) { + _mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].failed_probes++; + char * new_host = _mdns_mangle_name((char *)_mdns_server->hostname); + if (new_host) { + free((char *)_mdns_server->hostname); + _mdns_server->hostname = new_host; + } + _mdns_restart_all_pcbs(); + } + } else { + _mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, NULL, 0, true); + } + } else if (ttl > 60 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, NULL); + } + } + + } + } + //end while + if (parsed_packet->authoritative) { + _mdns_search_finish_done(); + } + } + + if (!do_not_reply && _mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].state > PCB_PROBE_3 && (parsed_packet->questions || parsed_packet->discovery)) { + _mdns_create_answer_from_parsed_packet(parsed_packet); + } + + +clear_rx_packet: + while (parsed_packet->questions) { + mdns_parsed_question_t * question = parsed_packet->questions; + parsed_packet->questions = parsed_packet->questions->next; + free(question->host); + free(question->service); + free(question->proto); + free(question->domain); + free(question); + } + free(parsed_packet); +} + +/** + * @brief Enable mDNS interface + */ +void _mdns_enable_pcb(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + if (!_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb) { + if (_mdns_pcb_init(tcpip_if, ip_protocol)) { + return; + } + } + _mdns_restart_pcb(tcpip_if, ip_protocol); +} + +/** + * @brief Disable mDNS interface + */ +void _mdns_disable_pcb(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + if (_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb) { + _mdns_clear_pcb_tx_queue_head(tcpip_if, ip_protocol); + _mdns_pcb_deinit(tcpip_if, ip_protocol); + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (other_if != TCPIP_ADAPTER_IF_MAX && _mdns_server->interfaces[other_if].pcbs[ip_protocol].state == PCB_DUP) { + _mdns_server->interfaces[other_if].pcbs[ip_protocol].state = PCB_OFF; + _mdns_enable_pcb(other_if, ip_protocol); + } + } + _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].state = PCB_OFF; +} + +/** + * @brief Dispatch interface changes based on system events + */ +void _mdns_handle_system_event(system_event_id_t event, tcpip_adapter_if_t interface) +{ + if (!_mdns_server) { + return; + } + + tcpip_adapter_dhcp_status_t dcst; + switch(event) { + case SYSTEM_EVENT_STA_CONNECTED: + if (!tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dcst)) { + if (dcst != TCPIP_ADAPTER_DHCP_STARTED) { + _mdns_enable_pcb(TCPIP_ADAPTER_IF_STA, MDNS_IP_PROTOCOL_V4); + } + } + break; + case SYSTEM_EVENT_STA_GOT_IP: + _mdns_enable_pcb(TCPIP_ADAPTER_IF_STA, MDNS_IP_PROTOCOL_V4); + _mdns_announce_pcb(TCPIP_ADAPTER_IF_STA, MDNS_IP_PROTOCOL_V6, NULL, 0, true); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + _mdns_disable_pcb(TCPIP_ADAPTER_IF_STA, MDNS_IP_PROTOCOL_V4); + _mdns_disable_pcb(TCPIP_ADAPTER_IF_STA, MDNS_IP_PROTOCOL_V6); + break; + case SYSTEM_EVENT_AP_START: + _mdns_enable_pcb(TCPIP_ADAPTER_IF_AP, MDNS_IP_PROTOCOL_V4); + break; + case SYSTEM_EVENT_AP_STOP: + _mdns_disable_pcb(TCPIP_ADAPTER_IF_AP, MDNS_IP_PROTOCOL_V4); + _mdns_disable_pcb(TCPIP_ADAPTER_IF_AP, MDNS_IP_PROTOCOL_V6); + break; + case SYSTEM_EVENT_GOT_IP6: + _mdns_enable_pcb(interface, MDNS_IP_PROTOCOL_V6); + _mdns_announce_pcb(interface, MDNS_IP_PROTOCOL_V4, NULL, 0, true); + break; + case SYSTEM_EVENT_ETH_CONNECTED: + if (!tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_ETH, &dcst)) { + if (dcst != TCPIP_ADAPTER_DHCP_STARTED) { + _mdns_enable_pcb(TCPIP_ADAPTER_IF_ETH, MDNS_IP_PROTOCOL_V4); + } + } + break; + case SYSTEM_EVENT_ETH_GOT_IP: + _mdns_enable_pcb(TCPIP_ADAPTER_IF_ETH, MDNS_IP_PROTOCOL_V4); + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + _mdns_disable_pcb(TCPIP_ADAPTER_IF_ETH, MDNS_IP_PROTOCOL_V4); + _mdns_disable_pcb(TCPIP_ADAPTER_IF_ETH, MDNS_IP_PROTOCOL_V6); + break; + default: + break; + } +} + +/* + * MDNS Search + * */ + +/** + * @brief Free search structure (except the results) + */ +static void _mdns_search_free(mdns_search_once_t * search) +{ + free(search->instance); + free(search->service); + free(search->proto); + vSemaphoreDelete(search->lock); + free(search); +} + +/** + * @brief Allocate new search structure + */ +static mdns_search_once_t * _mdns_search_init(const char * name, const char * service, const char * proto, uint16_t type, uint32_t timeout, uint8_t max_results) +{ + mdns_search_once_t * search = (mdns_search_once_t *)malloc(sizeof(mdns_search_once_t)); + if (!search) { + return NULL; + } + memset(search, 0, sizeof(mdns_search_once_t)); + + search->lock = xSemaphoreCreateMutex(); + if (!search->lock) { + free(search); + return NULL; + } + + if (!_str_null_or_empty(name)) { + search->instance = strndup(name, MDNS_NAME_BUF_LEN-1); + if (!search->instance) { + _mdns_search_free(search); + return NULL; + } + } + + if (!_str_null_or_empty(service)) { + search->service = strndup(service, MDNS_NAME_BUF_LEN-1); + if (!search->service) { + _mdns_search_free(search); + return NULL; + } + } + + if (!_str_null_or_empty(proto)) { + search->proto = strndup(proto, MDNS_NAME_BUF_LEN-1); + if (!search->proto) { + _mdns_search_free(search); + return NULL; + } + } + + search->type = type; + search->timeout = timeout; + search->num_results = 0; + search->max_results = max_results; + search->result = NULL; + search->state = SEARCH_INIT; + search->sent_at = 0; + search->started_at = xTaskGetTickCount() * portTICK_PERIOD_MS; + search->next = NULL; + + xSemaphoreTake(search->lock, 0); + + return search; +} + +/** + * @brief Mark search as finished and remove it from search chain + */ +static void _mdns_search_finish(mdns_search_once_t * search) +{ + search->state = SEARCH_OFF; + queueDetach(mdns_search_once_t, _mdns_server->search_once, search); + xSemaphoreGive(search->lock); +} + +/** + * @brief Add new search to the search chain + */ +static void _mdns_search_add(mdns_search_once_t * search) +{ + search->next = _mdns_server->search_once; + _mdns_server->search_once = search; +} + +/** + * @brief Called from parser to finish any searches that have reached maximum results + */ +static void _mdns_search_finish_done() +{ + mdns_search_once_t * search = _mdns_server->search_once; + mdns_search_once_t * s = NULL; + while (search) { + s = search; + search = search->next; + if (s->max_results && s->num_results >= s->max_results) { + _mdns_search_finish(s); + } + } +} + +/** + * @brief Create linked IP (copy) from parsed one + */ +static mdns_ip_addr_t * _mdns_result_addr_create_ip(ip_addr_t * ip) +{ + mdns_ip_addr_t * a = (mdns_ip_addr_t *)malloc(sizeof(mdns_ip_addr_t)); + if (!a) { + return NULL; + } + memset(a, 0 , sizeof(mdns_ip_addr_t)); + a->addr.type = ip->type; + if (ip->type == IPADDR_TYPE_V6) { + memcpy(a->addr.u_addr.ip6.addr, ip->u_addr.ip6.addr, 16); + } else { + a->addr.u_addr.ip4.addr = ip->u_addr.ip4.addr; + } + return a; +} + +/** + * @brief Chain new IP to search result + */ +static void _mdns_result_add_ip(mdns_result_t * r, ip_addr_t * ip) +{ + mdns_ip_addr_t * a = r->addr; + while (a) { + if (a->addr.type == ip->type) { + if (a->addr.type == IPADDR_TYPE_V4 && a->addr.u_addr.ip4.addr == ip->u_addr.ip4.addr) { + return; + } + if (a->addr.type == IPADDR_TYPE_V6 && !memcmp(a->addr.u_addr.ip6.addr, ip->u_addr.ip6.addr, 16)) { + return; + } + } + a = a->next; + } + a = _mdns_result_addr_create_ip(ip); + if (!a) { + return; + } + a->next = r->addr; + r->addr = a; +} + +/** + * @brief Called from parser to add A/AAAA data to search result + */ +static void _mdns_search_result_add_ip(mdns_search_once_t * search, const char * hostname, ip_addr_t * ip, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_result_t * r = NULL; + mdns_ip_addr_t * a = NULL; + + if ((search->type == MDNS_TYPE_A && ip->type == IPADDR_TYPE_V4) + || (search->type == MDNS_TYPE_AAAA && ip->type == IPADDR_TYPE_V6) + || search->type == MDNS_TYPE_ANY) { + r = search->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol) { + _mdns_result_add_ip(r, ip); + return; + } + r = r->next; + } + if (!search->max_results || search->num_results < search->max_results) { + r = (mdns_result_t *)malloc(sizeof(mdns_result_t)); + if (!r) { + return; + } + + memset(r, 0 , sizeof(mdns_result_t)); + + a = _mdns_result_addr_create_ip(ip); + if (!a) { + free(r); + return; + } + a->next = r->addr; + r->addr = a; + r->tcpip_if = tcpip_if; + r->ip_protocol = ip_protocol; + r->next = search->result; + search->result = r; + search->num_results++; + } + } else if (search->type == MDNS_TYPE_PTR) { + r = search->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->hostname) && !strcasecmp(hostname, r->hostname)) { + _mdns_result_add_ip(r, ip); + break; + } + r = r->next; + } + } +} + +/** + * @brief Called from parser to add PTR data to search result + */ +static mdns_result_t * _mdns_search_result_add_ptr(mdns_search_once_t * search, const char * instance, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_result_t * r = search->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->instance_name) && !strcasecmp(instance, r->instance_name)) { + return r; + } + r = r->next; + } + if (!search->max_results || search->num_results < search->max_results) { + r = (mdns_result_t *)malloc(sizeof(mdns_result_t)); + if (!r) { + return NULL; + } + + memset(r, 0 , sizeof(mdns_result_t)); + r->instance_name = strdup(instance); + if (!r->instance_name) { + free(r); + return NULL; + } + + r->tcpip_if = tcpip_if; + r->ip_protocol = ip_protocol; + r->next = search->result; + search->result = r; + search->num_results++; + return r; + } + return NULL; +} + +/** + * @brief Called from parser to add SRV data to search result + */ +static void _mdns_search_result_add_srv(mdns_search_once_t * search, const char * hostname, uint16_t port, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_result_t * r = search->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->hostname) && !strcasecmp(hostname, r->hostname)) { + return; + } + r = r->next; + } + if (!search->max_results || search->num_results < search->max_results) { + r = (mdns_result_t *)malloc(sizeof(mdns_result_t)); + if (!r) { + return; + } + + memset(r, 0 , sizeof(mdns_result_t)); + r->hostname = strdup(hostname); + if (!r->hostname) { + free(r); + return; + } + r->port = port; + r->tcpip_if = tcpip_if; + r->ip_protocol = ip_protocol; + r->next = search->result; + search->result = r; + search->num_results++; + } +} + +/** + * @brief Called from parser to add TXT data to search result + */ +static void _mdns_search_result_add_txt(mdns_search_once_t * search, mdns_txt_item_t * txt, size_t txt_count, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + int i; + mdns_result_t * r = search->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol) { + if (r->txt) { + return; + } + r->txt = txt; + r->txt_count = txt_count; + return; + } + r = r->next; + } + if (!search->max_results || search->num_results < search->max_results) { + r = (mdns_result_t *)malloc(sizeof(mdns_result_t)); + if (!r) { + for (i=0; itxt = txt; + r->txt_count = txt_count; + r->tcpip_if = tcpip_if; + r->ip_protocol = ip_protocol; + r->next = search->result; + search->result = r; + search->num_results++; + } +} + +/** + * @brief Called from packet parser to find matching running search + */ +static mdns_search_once_t * _mdns_search_find_from(mdns_search_once_t * s, mdns_name_t * name, uint16_t type, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_result_t * r = NULL; + while (s) { + if (s->state == SEARCH_OFF) { + s = s->next; + continue; + } + + if (type == MDNS_TYPE_A || type == MDNS_TYPE_AAAA) { + if ((s->type == MDNS_TYPE_ANY && s->service != NULL) + || (s->type != MDNS_TYPE_ANY && s->type != type && s->type != MDNS_TYPE_PTR)) + { + s = s->next; + continue; + } + if (s->type != MDNS_TYPE_PTR) { + if (!strcasecmp(name->host, s->instance)) { + return s; + } + s = s->next; + continue; + } + r = s->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->hostname) && !strcasecmp(name->host, r->hostname)) { + return s; + } + r = r->next; + } + s = s->next; + continue; + } + + if (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT) { + if ((s->type == MDNS_TYPE_ANY && s->service == NULL) + || (s->type != MDNS_TYPE_ANY && s->type != type && s->type != MDNS_TYPE_PTR)) + { + s = s->next; + continue; + } + if (strcasecmp(name->service, s->service) + || strcasecmp(name->proto, s->proto)) + { + s = s->next; + continue; + } + if (s->type != MDNS_TYPE_PTR) { + if (!strcasecmp(name->host, s->instance)) { + return s; + } + s = s->next; + continue; + } + return s; + } + + if (type == MDNS_TYPE_PTR && type == s->type && !strcasecmp(name->service, s->service) && !strcasecmp(name->proto, s->proto)) { + return s; + } + + s = s->next; + } + + return NULL; +} + +/** + * @brief Create search packet for partidular interface + */ +static mdns_tx_packet_t * _mdns_create_search_packet(mdns_search_once_t * search, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_result_t * r = NULL; + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol); + if (!packet) { + return NULL; + } + + mdns_out_question_t * q = (mdns_out_question_t *)malloc(sizeof(mdns_out_question_t)); + if (!q) { + _mdns_free_tx_packet(packet); + return NULL; + } + q->next = NULL; + q->unicast = search->type != MDNS_TYPE_PTR; + q->type = search->type; + q->host = search->instance; + q->service = search->service; + q->proto = search->proto; + q->domain = MDNS_DEFAULT_DOMAIN; + queueToEnd(mdns_out_question_t, packet->questions, q); + + if (search->type == MDNS_TYPE_PTR) { + r = search->result; + while (r) { + //full record on the same interface is available + if (r->tcpip_if != tcpip_if || r->ip_protocol != ip_protocol || r->instance_name == NULL || r->hostname == NULL || r->addr == NULL) { + r = r->next; + continue; + } + mdns_out_answer_t * a = (mdns_out_answer_t *)malloc(sizeof(mdns_out_answer_t)); + if (!a) { + _mdns_free_tx_packet(packet); + return NULL; + } + a->type = MDNS_TYPE_PTR; + a->service = NULL; + a->custom_instance = r->instance_name; + a->custom_service = search->service; + a->custom_proto = search->proto; + a->bye = false; + a->flush = false; + a->next = NULL; + queueToEnd(mdns_out_answer_t, packet->answers, a); + r = r->next; + } + } + + return packet; +} + +/** + * @brief Send search packet to particular interface + */ +static void _mdns_search_send_pcb(mdns_search_once_t * search, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_tx_packet_t * packet = NULL; + if (_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb && _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].state == PCB_RUNNING) { + packet = _mdns_create_search_packet(search, tcpip_if, ip_protocol); + if (!packet) { + return; + } + _mdns_dispatch_tx_packet(packet); + _mdns_free_tx_packet(packet); + } +} + +/** + * @brief Send search packet to all available interfaces + */ +static void _mdns_search_send(mdns_search_once_t * search) +{ + uint8_t i, j; + for (i=0; iinterfaces[p->tcpip_if].pcbs[p->ip_protocol]; + uint32_t send_after = 1000; + + if (pcb->state == PCB_OFF) { + _mdns_free_tx_packet(p); + return; + } + _mdns_dispatch_tx_packet(p); + + switch(pcb->state) { + case PCB_PROBE_1: + q = p->questions; + while (q) { + q->unicast = false; + q = q->next; + } + //fallthrough + case PCB_PROBE_2: + _mdns_schedule_tx_packet(p, 250); + pcb->state = (mdns_pcb_state_t)((uint8_t)(pcb->state) + 1); + break; + case PCB_PROBE_3: + a = _mdns_create_announce_from_probe(p); + if (!a) { + _mdns_schedule_tx_packet(p, 250); + break; + } + pcb->probe_running = false; + pcb->probe_ip = false; + pcb->probe_services_len = 0; + pcb->failed_probes = 0; + free(pcb->probe_services); + pcb->probe_services = NULL; + _mdns_free_tx_packet(p); + p = a; + send_after = 250; + //fallthrough + case PCB_ANNOUNCE_1: + //fallthrough + case PCB_ANNOUNCE_2: + _mdns_schedule_tx_packet(p, send_after); + pcb->state = (mdns_pcb_state_t)((uint8_t)(pcb->state) + 1); + break; + case PCB_ANNOUNCE_3: + pcb->state = PCB_RUNNING; + _mdns_free_tx_packet(p); + break; + default: + _mdns_free_tx_packet(p); + break; + } +} + +/** + * @brief Free action data + */ +static void _mdns_free_action(mdns_action_t * action) +{ + switch(action->type) { + case ACTION_HOSTNAME_SET: + free(action->data.hostname); + break; + case ACTION_INSTANCE_SET: + free(action->data.instance); + break; + case ACTION_SERVICE_ADD: + _mdns_free_service(action->data.srv_add.service->service); + free(action->data.srv_add.service); + break; + case ACTION_SERVICE_INSTANCE_SET: + free(action->data.srv_instance.instance); + break; + case ACTION_SERVICE_TXT_REPLACE: + free(action->data.srv_txt_replace.txt); + break; + case ACTION_SERVICE_TXT_SET: + free(action->data.srv_txt_set.key); + free(action->data.srv_txt_set.value); + break; + case ACTION_SERVICE_TXT_DEL: + free(action->data.srv_txt_del.key); + break; + case ACTION_SEARCH_ADD: + //fallthrough + case ACTION_SEARCH_SEND: + //fallthrough + case ACTION_SEARCH_END: + _mdns_search_free(action->data.search_add.search); + break; + case ACTION_TX_HANDLE: + _mdns_free_tx_packet(action->data.tx_handle.packet); + break; + case ACTION_RX_HANDLE: + pbuf_free(action->data.rx_handle.packet->pb); + free(action->data.rx_handle.packet); + break; + default: + break; + } + free(action); +} + +/** + * @brief Called from service thread to execute given action + */ +static void _mdns_execute_action(mdns_action_t * action) +{ + mdns_srv_item_t * a = NULL; + mdns_service_t * service; + char * key; + char * value; + mdns_txt_linked_item_t * txt, * t; + + switch(action->type) { + case ACTION_SYSTEM_EVENT: + _mdns_handle_system_event(action->data.sys_event.event_id, action->data.sys_event.interface); + break; + case ACTION_HOSTNAME_SET: + _mdns_send_final_bye(true); + free((char*)_mdns_server->hostname); + _mdns_server->hostname = action->data.hostname; + _mdns_restart_all_pcbs(); + + break; + case ACTION_INSTANCE_SET: + _mdns_send_bye_all_pcbs_no_instance(); + free((char*)_mdns_server->instance); + _mdns_server->instance = action->data.instance; + _mdns_restart_all_pcbs_no_instance(); + + break; + case ACTION_SERVICE_ADD: + action->data.srv_add.service->next = _mdns_server->services; + _mdns_server->services = action->data.srv_add.service; + _mdns_probe_all_pcbs(&action->data.srv_add.service, 1, false, false); + + break; + case ACTION_SERVICE_INSTANCE_SET: + if (action->data.srv_instance.service->service->instance) { + _mdns_send_bye(&action->data.srv_instance.service, 1, false); + free((char*)action->data.srv_instance.service->service->instance); + } + action->data.srv_instance.service->service->instance = action->data.srv_instance.instance; + _mdns_probe_all_pcbs(&action->data.srv_instance.service, 1, false, false); + + break; + case ACTION_SERVICE_PORT_SET: + action->data.srv_port.service->service->port = action->data.srv_port.port; + _mdns_announce_all_pcbs(&action->data.srv_port.service, 1, true); + + break; + case ACTION_SERVICE_TXT_REPLACE: + service = action->data.srv_txt_replace.service->service; + txt = service->txt; + service->txt = NULL; + while (txt) { + t = txt; + txt = txt->next; + free((char *)t->value); + free((char *)t->key); + free(t); + } + service->txt = _mdns_allocate_txt(action->data.srv_txt_replace.num_items, action->data.srv_txt_replace.txt); + _mdns_announce_all_pcbs(&action->data.srv_txt_replace.service, 1, false); + + break; + case ACTION_SERVICE_TXT_SET: + service = action->data.srv_txt_set.service->service; + key = action->data.srv_txt_set.key; + value = action->data.srv_txt_set.value; + txt = service->txt; + while (txt) { + if (strcmp(txt->key, key) == 0) { + free((char *)txt->value); + free(key); + txt->value = value; + break; + } + txt = txt->next; + } + if (!txt) { + txt = (mdns_txt_linked_item_t *)malloc(sizeof(mdns_txt_linked_item_t)); + if (!txt) { + _mdns_free_action(action); + return; + } + txt->key = key; + txt->value = value; + txt->next = service->txt; + service->txt = txt; + } + + _mdns_announce_all_pcbs(&action->data.srv_txt_set.service, 1, false); + + break; + case ACTION_SERVICE_TXT_DEL: + service = action->data.srv_txt_del.service->service; + key = action->data.srv_txt_del.key; + txt = service->txt; + if (!txt) { + break; + } + if (strcmp(txt->key, key) == 0) { + service->txt = txt->next; + free((char *)txt->key); + free((char *)txt->value); + free(txt); + } else { + while (txt->next) { + if (strcmp(txt->next->key, key) == 0) { + t = txt->next; + txt->next = t->next; + free((char *)t->key); + free((char *)t->value); + free(t); + break; + } else { + txt = txt->next; + } + } + } + free(key); + + _mdns_announce_all_pcbs(&action->data.srv_txt_set.service, 1, false); + + break; + case ACTION_SERVICE_DEL: + a = _mdns_server->services; + if (_mdns_server->services == action->data.srv_del.service) { + _mdns_server->services = a->next; + _mdns_send_bye(&a, 1, false); + _mdns_free_service(a->service); + free(a); + } else { + while (a->next && a->next != action->data.srv_del.service) { + a = a->next; + } + if (a->next == action->data.srv_del.service) { + mdns_srv_item_t * b = a->next; + a->next = a->next->next; + _mdns_send_bye(&b, 1, false); + _mdns_free_service(b->service); + free(b); + } + } + + break; + case ACTION_SERVICES_CLEAR: + _mdns_send_final_bye(false); + a = _mdns_server->services; + _mdns_server->services = NULL; + while (a) { + mdns_srv_item_t * s = a; + a = a->next; + _mdns_free_service(s->service); + free(s); + } + + break; + case ACTION_SEARCH_ADD: + _mdns_search_add(action->data.search_add.search); + break; + case ACTION_SEARCH_SEND: + _mdns_search_send(action->data.search_add.search); + break; + case ACTION_SEARCH_END: + _mdns_search_finish(action->data.search_add.search); + break; + case ACTION_TX_HANDLE: + _mdns_tx_handle_packet(action->data.tx_handle.packet); + break; + case ACTION_RX_HANDLE: + mdns_parse_packet(action->data.rx_handle.packet); + pbuf_free(action->data.rx_handle.packet->pb); + free(action->data.rx_handle.packet); + break; + default: + break; + } + free(action); +} + +/** + * @brief Queue search action + */ +static esp_err_t _mdns_send_search_action(mdns_action_type_t type, mdns_search_once_t * search) +{ + mdns_action_t * action = NULL; + + action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + + action->type = type; + action->data.search_add.search = search; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +/** + * @brief Called from timer task to run mDNS responder + */ +static void _mdns_scheduler_run() +{ + mdns_tx_packet_t * p = _mdns_server->tx_queue_head; + mdns_action_t * action = NULL; + + if (!p) { + return; + } + MDNS_SERVICE_LOCK(); + if ((int32_t)(p->send_at - (xTaskGetTickCount() * portTICK_PERIOD_MS)) < 0) { + action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (action) { + _mdns_server->tx_queue_head = p->next; + action->type = ACTION_TX_HANDLE; + action->data.tx_handle.packet = p; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + _mdns_server->tx_queue_head = p; + } + } + } + MDNS_SERVICE_UNLOCK(); +} + +/** + * @brief Called from timer task to run active searches + */ +static void _mdns_search_run() +{ + mdns_search_once_t * s = _mdns_server->search_once; + uint32_t now = xTaskGetTickCount() * portTICK_PERIOD_MS; + if (!s) { + return; + } + MDNS_SERVICE_LOCK(); + while (s) { + if (s->state != SEARCH_OFF) { + if (now > (s->started_at + s->timeout)) { + s->state = SEARCH_OFF; + if (_mdns_send_search_action(ACTION_SEARCH_END, s) != ESP_OK) { + s->state = SEARCH_RUNNING; + } + } else if (s->state == SEARCH_INIT || (now - s->sent_at) > 1000) { + s->state = SEARCH_RUNNING; + s->sent_at = now; + if (_mdns_send_search_action(ACTION_SEARCH_SEND, s) != ESP_OK) { + s->sent_at -= 1000; + } + } + } + s = s->next; + } + MDNS_SERVICE_UNLOCK(); +} + +/** + * @brief the main MDNS service task. Packets are received and parsed here + */ +static void _mdns_service_task(void *pvParameters) +{ + mdns_action_t * a = NULL; + for (;;) { + if (_mdns_server && _mdns_server->action_queue) { + if (xQueueReceive(_mdns_server->action_queue, &a, portMAX_DELAY) == pdTRUE) { + if (a->type == ACTION_TASK_STOP) { + break; + } + MDNS_SERVICE_LOCK(); + _mdns_execute_action(a); + MDNS_SERVICE_UNLOCK(); + } + } else { + vTaskDelay(500 * portTICK_PERIOD_MS); + } + } + _mdns_service_task_handle = NULL; + vTaskDelete(NULL); +} + +static void _mdns_timer_cb(void * arg) +{ + _mdns_scheduler_run(); + _mdns_search_run(); +} + +static esp_err_t _mdns_start_timer(){ + esp_timer_create_args_t timer_conf = { + .callback = _mdns_timer_cb, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "mdns_timer" + }; + esp_err_t err = esp_timer_create(&timer_conf, &(_mdns_server->timer_handle)); + if (err) { + return err; + } + return esp_timer_start_periodic(_mdns_server->timer_handle, MDNS_TIMER_PERIOD_US); +} + +static esp_err_t _mdns_stop_timer(){ + esp_err_t err = ESP_OK; + if (_mdns_server->timer_handle) { + err = esp_timer_stop(_mdns_server->timer_handle); + if (err) { + return err; + } + err = esp_timer_delete(_mdns_server->timer_handle); + } + return err; +} + +/** + * @brief Start the service thread if not running + * + * @return + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t _mdns_service_task_start() +{ + if (!_mdns_service_semaphore) { + _mdns_service_semaphore = xSemaphoreCreateMutex(); + if (!_mdns_service_semaphore) { + return ESP_FAIL; + } + } + MDNS_SERVICE_LOCK(); + if (_mdns_start_timer()) { + MDNS_SERVICE_UNLOCK(); + return ESP_FAIL; + } + if (!_mdns_service_task_handle) { + xTaskCreatePinnedToCore(_mdns_service_task, "mdns", MDNS_SERVICE_STACK_DEPTH, NULL, 1, (TaskHandle_t * const)(&_mdns_service_task_handle), 0); + if (!_mdns_service_task_handle) { + MDNS_SERVICE_UNLOCK(); + return ESP_FAIL; + } + } + MDNS_SERVICE_UNLOCK(); + return ESP_OK; +} + +/** + * @brief Stop the service thread + * + * @return + * - ESP_OK + */ +static esp_err_t _mdns_service_task_stop() +{ + MDNS_SERVICE_LOCK(); + _mdns_stop_timer(); + if (_mdns_service_task_handle) { + mdns_action_t action; + mdns_action_t * a = &action; + action.type = ACTION_TASK_STOP; + if (xQueueSend(_mdns_server->action_queue, &a, (portTickType)0) != pdPASS) { + vTaskDelete(_mdns_service_task_handle); + _mdns_service_task_handle = NULL; + } + while (_mdns_service_task_handle) { + vTaskDelay(10 / portTICK_PERIOD_MS); + } + } + MDNS_SERVICE_UNLOCK(); + return ESP_OK; +} + +/* + * Public Methods + * */ + +esp_err_t mdns_handle_system_event(void *ctx, system_event_t *event) +{ + if (!_mdns_server) { + return ESP_OK; + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_OK; + } + action->type = ACTION_SYSTEM_EVENT; + action->data.sys_event.event_id = event->event_id; + action->data.sys_event.interface = (event->event_id == SYSTEM_EVENT_GOT_IP6)?event->event_info.got_ip6.if_index:0; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + } + return ESP_OK; +} + +esp_err_t mdns_init() +{ + esp_err_t err = ESP_OK; + + if (_mdns_server) { + return err; + } + + _mdns_server = (mdns_server_t *)malloc(sizeof(mdns_server_t)); + if (!_mdns_server) { + return ESP_ERR_NO_MEM; + } + memset((uint8_t*)_mdns_server, 0, sizeof(mdns_server_t)); + + _mdns_server->lock = xSemaphoreCreateMutex(); + if (!_mdns_server->lock) { + err = ESP_ERR_NO_MEM; + goto free_server; + } + + _mdns_server->action_queue = xQueueCreate(MDNS_ACTION_QUEUE_LEN, sizeof(mdns_action_t *)); + if (!_mdns_server->action_queue) { + err = ESP_ERR_NO_MEM; + goto free_lock; + } + + if (_mdns_service_task_start()) { + //service start failed! + err = ESP_FAIL; + goto free_all; + } + + uint8_t i; + ip6_addr_t tmp_addr6; + tcpip_adapter_ip_info_t if_ip_info; + + for (i=0; iaction_queue); +free_lock: + vSemaphoreDelete(_mdns_server->lock); +free_server: + free(_mdns_server); + _mdns_server = NULL; + return err; +} + +void mdns_free() +{ + uint8_t i, j; + if (!_mdns_server) { + return; + } + _mdns_service_task_stop(); + mdns_service_remove_all(_mdns_server); + for (i=0; ihostname); + free((char*)_mdns_server->instance); + if (_mdns_server->action_queue) { + mdns_action_t * c; + while (xQueueReceive(_mdns_server->action_queue, &c, 0) == pdTRUE) { + _mdns_free_action(c); + } + vQueueDelete(_mdns_server->action_queue); + } + _mdns_clear_tx_queue_head(); + while (_mdns_server->search_once) { + mdns_search_once_t * h = _mdns_server->search_once; + _mdns_server->search_once = h->next; + free(h->instance); + free(h->service); + free(h->proto); + vSemaphoreDelete(h->lock); + if (h->result) { + mdns_query_results_free(h->result); + } + free(h); + } + vSemaphoreDelete(_mdns_server->lock); + free(_mdns_server); + _mdns_server = NULL; +} + +esp_err_t mdns_hostname_set(const char * hostname) +{ + if (!_mdns_server) { + return ESP_ERR_INVALID_ARG; + } + if (_str_null_or_empty(hostname) || strlen(hostname) > (MDNS_NAME_BUF_LEN - 1)) { + return ESP_ERR_INVALID_ARG; + } + char * new_hostname = strndup(hostname, MDNS_NAME_BUF_LEN - 1); + if (!new_hostname) { + return ESP_ERR_NO_MEM; + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + free(new_hostname); + return ESP_ERR_NO_MEM; + } + action->type = ACTION_HOSTNAME_SET; + action->data.hostname = new_hostname; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(new_hostname); + free(action); + return ESP_ERR_NO_MEM; + } + return ERR_OK; +} + +esp_err_t mdns_instance_name_set(const char * instance) +{ + if (!_mdns_server) { + return ESP_ERR_INVALID_ARG; + } + if (_str_null_or_empty(instance) || strlen(instance) > (MDNS_NAME_BUF_LEN - 1)) { + return ESP_ERR_INVALID_ARG; + } + char * new_instance = strndup(instance, MDNS_NAME_BUF_LEN - 1); + if (!new_instance) { + return ESP_ERR_NO_MEM; + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + free(new_instance); + return ESP_ERR_NO_MEM; + } + action->type = ACTION_INSTANCE_SET; + action->data.instance = new_instance; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(new_instance); + free(action); + return ESP_ERR_NO_MEM; + } + return ERR_OK; +} + +/* + * MDNS SERVICES + * */ + +esp_err_t mdns_service_add(const char * instance, const char * service, const char * proto, uint16_t port, mdns_txt_item_t txt[], size_t num_items) +{ + if (!_mdns_server || _str_null_or_empty(service) || _str_null_or_empty(proto) || !port) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * item = _mdns_get_service_item(service, proto); + if (item) { + return ESP_ERR_INVALID_ARG; + } + + mdns_service_t * s = _mdns_create_service(service, proto, port, instance, num_items, txt); + if (!s) { + return ESP_ERR_NO_MEM; + } + + item = (mdns_srv_item_t *)malloc(sizeof(mdns_srv_item_t)); + if (!item) { + _mdns_free_service(s); + return ESP_ERR_NO_MEM; + } + + item->service = s; + item->next = NULL; + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + _mdns_free_service(s); + free(item); + return ESP_ERR_NO_MEM; + } + action->type = ACTION_SERVICE_ADD; + action->data.srv_add.service = item; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + _mdns_free_service(s); + free(item); + free(action); + return ESP_ERR_NO_MEM; + } + + uint8_t i = 0; + while (_mdns_get_service_item(service, proto) == NULL && i++ < 200) { + vTaskDelay(1); + } + if (i >= 200) { + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t mdns_service_port_set(const char * service, const char * proto, uint16_t port) +{ + if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) || !port) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); + if (!s) { + return ESP_ERR_NOT_FOUND; + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + action->type = ACTION_SERVICE_PORT_SET; + action->data.srv_port.service = s; + action->data.srv_port.port = port; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +esp_err_t mdns_service_txt_set(const char * service, const char * proto, mdns_txt_item_t txt[], uint8_t num_items) +{ + if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) || (num_items && txt == NULL)) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); + if (!s) { + return ESP_ERR_NOT_FOUND; + } + + mdns_txt_item_t * txt_copy = NULL; + if (num_items){ + txt_copy = (mdns_txt_item_t *)malloc(num_items * sizeof(mdns_txt_item_t)); + if (!txt_copy) { + return ESP_ERR_NO_MEM; + } + memcpy(txt_copy, txt, num_items * sizeof(mdns_txt_item_t)); + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + free(txt_copy); + return ESP_ERR_NO_MEM; + } + action->type = ACTION_SERVICE_TXT_REPLACE; + action->data.srv_txt_replace.service = s; + action->data.srv_txt_replace.num_items = num_items; + action->data.srv_txt_replace.txt = txt_copy; + + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(txt_copy); + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + + +esp_err_t mdns_service_txt_item_set(const char * service, const char * proto, const char * key, const char * value) +{ + if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) || _str_null_or_empty(key) || !value) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); + if (!s) { + return ESP_ERR_NOT_FOUND; + } + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + + action->type = ACTION_SERVICE_TXT_SET; + action->data.srv_txt_set.service = s; + action->data.srv_txt_set.key = strdup(key); + if (!action->data.srv_txt_set.key) { + free(action); + return ESP_ERR_NO_MEM; + } + action->data.srv_txt_set.value = strdup(value); + if (!action->data.srv_txt_set.value) { + free(action->data.srv_txt_set.key); + free(action); + return ESP_ERR_NO_MEM; + } + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action->data.srv_txt_set.key); + free(action->data.srv_txt_set.value); + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +esp_err_t mdns_service_txt_item_remove(const char * service, const char * proto, const char * key) +{ + if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) || _str_null_or_empty(key)) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); + if (!s) { + return ESP_ERR_NOT_FOUND; + } + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + + action->type = ACTION_SERVICE_TXT_DEL; + action->data.srv_txt_del.service = s; + action->data.srv_txt_del.key = strdup(key); + if (!action->data.srv_txt_del.key) { + free(action); + return ESP_ERR_NO_MEM; + } + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action->data.srv_txt_del.key); + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +esp_err_t mdns_service_instance_name_set(const char * service, const char * proto, const char * instance) +{ + if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto)) { + return ESP_ERR_INVALID_ARG; + } + if (_str_null_or_empty(instance) || strlen(instance) > (MDNS_NAME_BUF_LEN - 1)) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); + if (!s) { + return ESP_ERR_NOT_FOUND; + } + char * new_instance = strndup(instance, MDNS_NAME_BUF_LEN - 1); + if (!new_instance) { + return ESP_ERR_NO_MEM; + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + free(new_instance); + return ESP_ERR_NO_MEM; + } + action->type = ACTION_SERVICE_INSTANCE_SET; + action->data.srv_instance.service = s; + action->data.srv_instance.instance = new_instance; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(new_instance); + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +esp_err_t mdns_service_remove(const char * service, const char * proto) +{ + if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto)) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); + if (!s) { + return ESP_ERR_NOT_FOUND; + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + action->type = ACTION_SERVICE_DEL; + action->data.srv_del.service = s; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +esp_err_t mdns_service_remove_all() +{ + if (!_mdns_server) { + return ESP_ERR_INVALID_ARG; + } + if (!_mdns_server->services) { + return ESP_OK; + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + action->type = ACTION_SERVICES_CLEAR; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +/* + * MDNS QUERY + * */ + +void mdns_query_results_free(mdns_result_t * results) +{ + mdns_result_t * r; + mdns_ip_addr_t * a; + int i; + + while (results) { + r = results; + + free((char *)(r->hostname)); + free((char *)(r->instance_name)); + + for (i=0; itxt_count; i++) { + free((char *)(r->txt[i].key)); + free((char *)(r->txt[i].value)); + } + free(r->txt); + + while (r->addr) { + a = r->addr; + r->addr = r->addr->next; + free(a); + } + + results = results->next; + free(r); + } +} + +esp_err_t mdns_query(const char * name, const char * service, const char * proto, uint16_t type, uint32_t timeout, size_t max_results, mdns_result_t ** results) +{ + mdns_search_once_t * search = NULL; + + *results = NULL; + + if (!_mdns_server) { + return ESP_ERR_INVALID_STATE; + } + + if (!timeout || _str_null_or_empty(service) != _str_null_or_empty(proto)) { + return ESP_ERR_INVALID_ARG; + } + + search = _mdns_search_init(name, service, proto, type, timeout, max_results); + if (!search) { + return ESP_ERR_NO_MEM; + } + + if (_mdns_send_search_action(ACTION_SEARCH_ADD, search)) { + _mdns_search_free(search); + return ESP_ERR_NO_MEM; + } + xSemaphoreTake(search->lock, portMAX_DELAY); + + *results = search->result; + _mdns_search_free(search); + + return ESP_OK; +} + +esp_err_t mdns_query_ptr(const char * service, const char * proto, uint32_t timeout, size_t max_results, mdns_result_t ** results) +{ + if (_str_null_or_empty(service) || _str_null_or_empty(proto)) { + return ESP_ERR_INVALID_ARG; + } + + return mdns_query(NULL, service, proto, MDNS_TYPE_PTR, timeout, max_results, results); +} + +esp_err_t mdns_query_srv(const char * instance, const char * service, const char * proto, uint32_t timeout, mdns_result_t ** result) +{ + if (_str_null_or_empty(instance) || _str_null_or_empty(service) || _str_null_or_empty(proto)) { + return ESP_ERR_INVALID_ARG; + } + + return mdns_query(instance, service, proto, MDNS_TYPE_SRV, timeout, 1, result); +} + +esp_err_t mdns_query_txt(const char * instance, const char * service, const char * proto, uint32_t timeout, mdns_result_t ** result) +{ + if (_str_null_or_empty(instance) || _str_null_or_empty(service) || _str_null_or_empty(proto)) { + return ESP_ERR_INVALID_ARG; + } + + return mdns_query(instance, service, proto, MDNS_TYPE_TXT, timeout, 1, result); +} + +esp_err_t mdns_query_a(const char * name, uint32_t timeout, ip4_addr_t * addr) +{ + mdns_result_t * result = NULL; + esp_err_t err; + + if (_str_null_or_empty(name)) { + return ESP_ERR_INVALID_ARG; + } + + err = mdns_query(name, NULL, NULL, MDNS_TYPE_A, timeout, 1, &result); + + if (err) { + return err; + } + + if (!result) { + return ESP_ERR_NOT_FOUND; + } + + mdns_ip_addr_t * a = result->addr; + while (a) { + if (a->addr.type == IPADDR_TYPE_V4) { + addr->addr = a->addr.u_addr.ip4.addr; + mdns_query_results_free(result); + return ESP_OK; + } + a = a->next; + } + + mdns_query_results_free(result); + return ESP_ERR_NOT_FOUND; +} + +esp_err_t mdns_query_aaaa(const char * name, uint32_t timeout, ip6_addr_t * addr) +{ + mdns_result_t * result = NULL; + esp_err_t err; + + if (_str_null_or_empty(name)) { + return ESP_ERR_INVALID_ARG; + } + + err = mdns_query(name, NULL, NULL, MDNS_TYPE_AAAA, timeout, 1, &result); + + if (err) { + return err; + } + + if (!result) { + return ESP_ERR_NOT_FOUND; + } + + mdns_ip_addr_t * a = result->addr; + while (a) { + if (a->addr.type == IPADDR_TYPE_V6) { + memcpy(addr->addr, a->addr.u_addr.ip6.addr, 16); + mdns_query_results_free(result); + return ESP_OK; + } + a = a->next; + } + + mdns_query_results_free(result); + return ESP_ERR_NOT_FOUND; +} + +#ifdef MDNS_ENABLE_DEBUG + +void mdns_debug_packet(const uint8_t * data, size_t len) +{ + static mdns_name_t n; + mdns_header_t header; + const uint8_t * content = data + MDNS_HEAD_LEN; + uint32_t t = xTaskGetTickCount() * portTICK_PERIOD_MS; + mdns_name_t * name = &n; + memset(name, 0, sizeof(mdns_name_t)); + + _mdns_dbg_printf("Packet[%u]: ", t); + + header.id = _mdns_read_u16(data, MDNS_HEAD_ID_OFFSET); + header.flags.value = _mdns_read_u16(data, MDNS_HEAD_FLAGS_OFFSET); + header.questions = _mdns_read_u16(data, MDNS_HEAD_QUESTIONS_OFFSET); + header.answers = _mdns_read_u16(data, MDNS_HEAD_ANSWERS_OFFSET); + header.servers = _mdns_read_u16(data, MDNS_HEAD_SERVERS_OFFSET); + header.additional = _mdns_read_u16(data, MDNS_HEAD_ADDITIONAL_OFFSET); + + _mdns_dbg_printf("%s", + (header.flags.value == MDNS_FLAGS_AUTHORITATIVE)?"AUTHORITATIVE\n": + (header.flags.value == MDNS_FLAGS_DISTRIBUTED)?"DISTRIBUTED\n": + (header.flags.value == 0)?"\n":" " + ); + if (header.flags.value && header.flags.value != MDNS_FLAGS_AUTHORITATIVE) { + _mdns_dbg_printf("0x%04X\n", header.flags.value); + } + + if (header.questions) { + uint8_t qs = header.questions; + + while (qs--) { + content = _mdns_parse_fqdn(data, content, name); + if (!content) { + header.answers = 0; + header.additional = 0; + header.servers = 0; + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + break; + } + + uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET); + uint16_t clas = _mdns_read_u16(content, MDNS_CLASS_OFFSET); + bool unicast = !!(clas & 0x8000); + clas &= 0x7FFF; + content = content + 4; + + _mdns_dbg_printf(" Q: "); + if (unicast) { + _mdns_dbg_printf("*U* "); + } + if (type == MDNS_TYPE_PTR) { + _mdns_dbg_printf("%s.%s%s.%s.%s. PTR ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_SRV) { + _mdns_dbg_printf("%s.%s%s.%s.%s. SRV ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_TXT) { + _mdns_dbg_printf("%s.%s%s.%s.%s. TXT ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_A) { + _mdns_dbg_printf("%s.%s. A ", name->host, name->domain); + } else if (type == MDNS_TYPE_AAAA) { + _mdns_dbg_printf("%s.%s. AAAA ", name->host, name->domain); + } else if (type == MDNS_TYPE_NSEC) { + _mdns_dbg_printf("%s.%s%s.%s.%s. NSEC ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_ANY) { + _mdns_dbg_printf("%s.%s%s.%s.%s. ANY ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain); + } else { + _mdns_dbg_printf("%s.%s%s.%s.%s. %04X ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain, type); + } + + if (clas == 0x0001) { + _mdns_dbg_printf("IN"); + } else { + _mdns_dbg_printf("%04X", clas); + } + _mdns_dbg_printf("\n"); + } + } + + if (header.answers || header.servers || header.additional) { + uint16_t recordIndex = 0; + + while (content < (data + len)) { + + content = _mdns_parse_fqdn(data, content, name); + if (!content) { + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + break; + } + + uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET); + uint16_t clas = _mdns_read_u16(content, MDNS_CLASS_OFFSET); + uint32_t ttl = _mdns_read_u32(content, MDNS_TTL_OFFSET); + uint16_t data_len = _mdns_read_u16(content, MDNS_LEN_OFFSET); + const uint8_t * data_ptr = content + MDNS_DATA_OFFSET; + bool flush = !!(clas & 0x8000); + clas &= 0x7FFF; + + content = data_ptr + data_len; + if (content > (data + len)) { + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + break; + } + + mdns_parsed_recort_type_t record_type = MDNS_ANSWER; + + if (recordIndex >= (header.answers + header.servers)) { + record_type = MDNS_EXTRA; + } else if (recordIndex >= (header.answers)) { + record_type = MDNS_NS; + } + recordIndex++; + + if (record_type == MDNS_EXTRA) { + _mdns_dbg_printf(" X"); + } else if (record_type == MDNS_NS) { + _mdns_dbg_printf(" S"); + } else { + _mdns_dbg_printf(" A"); + } + + if (type == MDNS_TYPE_PTR) { + _mdns_dbg_printf(": %s%s%s.%s.%s. PTR ", name->host, name->host[0]?".":"", name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_SRV) { + _mdns_dbg_printf(": %s.%s.%s.%s. SRV ", name->host, name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_TXT) { + _mdns_dbg_printf(": %s.%s.%s.%s. TXT ", name->host, name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_A) { + _mdns_dbg_printf(": %s.%s. A ", name->host, name->domain); + } else if (type == MDNS_TYPE_AAAA) { + _mdns_dbg_printf(": %s.%s. AAAA ", name->host, name->domain); + } else if (type == MDNS_TYPE_NSEC) { + _mdns_dbg_printf(": %s.%s.%s.%s. NSEC ", name->host, name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_ANY) { + _mdns_dbg_printf(": %s.%s.%s.%s. ANY ", name->host, name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_OPT) { + _mdns_dbg_printf(": . OPT "); + } else { + _mdns_dbg_printf(": %s.%s.%s.%s. %04X ", name->host, name->service, name->proto, name->domain, type); + } + + if (clas == 0x0001) { + _mdns_dbg_printf("IN "); + } else { + _mdns_dbg_printf("%04X ", clas); + } + if (flush) { + _mdns_dbg_printf("FLUSH "); + } + _mdns_dbg_printf("%u ", ttl); + _mdns_dbg_printf("[%u] ", data_len); + if (type == MDNS_TYPE_PTR) { + if (!_mdns_parse_fqdn(data, data_ptr, name)) { + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + continue; + } + _mdns_dbg_printf("%s.%s.%s.%s.\n", name->host, name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_SRV) { + if (!_mdns_parse_fqdn(data, data_ptr + MDNS_SRV_FQDN_OFFSET, name)) { + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + continue; + } + uint16_t priority = _mdns_read_u16(data_ptr, MDNS_SRV_PRIORITY_OFFSET); + uint16_t weight = _mdns_read_u16(data_ptr, MDNS_SRV_WEIGHT_OFFSET); + uint16_t port = _mdns_read_u16(data_ptr, MDNS_SRV_PORT_OFFSET); + _mdns_dbg_printf("%u %u %u %s.%s.\n", priority, weight, port, name->host, name->domain); + } else if (type == MDNS_TYPE_TXT) { + uint16_t i=0, y; + while (i < data_len) { + uint8_t partLen = data_ptr[i++]; + if ((i+partLen) > data_len) { + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + break; + } + char txt[partLen+1]; + for (y=0; yhost, name->service, name->proto, name->domain); + size_t diff = new_ptr - old_ptr; + data_len -= diff; + data_ptr = new_ptr; + } + size_t i; + for (i=0; i +#include +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "mdns.h" + +static const char * if_str[] = {"STA", "AP", "ETH", "MAX"}; +static const char * ip_protocol_str[] = {"V4", "V6", "MAX"}; + +static void mdns_print_results(mdns_result_t * results) +{ + mdns_result_t * r = results; + mdns_ip_addr_t * a = NULL; + int i = 1, t; + while (r) { + printf("%d: Interface: %s, Type: %s\n", i++, if_str[r->tcpip_if], ip_protocol_str[r->ip_protocol]); + if (r->instance_name) { + printf(" PTR : %s\n", r->instance_name); + } + if (r->hostname) { + printf(" SRV : %s.local:%u\n", r->hostname, r->port); + } + if (r->txt_count) { + printf(" TXT : [%u] ", r->txt_count); + for (t=0; ttxt_count; t++) { + printf("%s=%s; ", r->txt[t].key, r->txt[t].value); + } + printf("\n"); + } + a = r->addr; + while (a) { + if (a->addr.type == IPADDR_TYPE_V6) { + printf(" AAAA: " IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6)); + } else { + printf(" A : " IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4))); + } + a = a->next; + } + r = r->next; + } +} + +static struct { + struct arg_str *hostname; + struct arg_int *timeout; + struct arg_end *end; +} mdns_query_a_args; + +static int cmd_mdns_query_a(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_a_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_a_args.end, argv[0]); + return 1; + } + + const char * hostname = mdns_query_a_args.hostname->sval[0]; + int timeout = mdns_query_a_args.timeout->ival[0]; + + if (!hostname || !hostname[0]) { + printf("ERROR: Hostname not supplied\n"); + return 1; + } + + if (timeout <= 0) { + timeout = 1000; + } + + printf("Query A: %s.local, Timeout: %d\n", hostname, timeout); + + struct ip4_addr addr; + addr.addr = 0; + + esp_err_t err = mdns_query_a(hostname, timeout, &addr); + if (err) { + if (err == ESP_ERR_NOT_FOUND) { + printf("ERROR: Host was not found!\n"); + return 0; + } + printf("ERROR: Query Failed\n"); + return 1; + } + + printf(IPSTR "\n", IP2STR(&addr)); + + return 0; +} + +static void register_mdns_query_a() +{ + mdns_query_a_args.hostname = arg_str1(NULL, NULL, "", "Hostname that is searched for"); + mdns_query_a_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_a_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_a", + .help = "Query MDNS for IPv4", + .hint = NULL, + .func = &cmd_mdns_query_a, + .argtable = &mdns_query_a_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static int cmd_mdns_query_aaaa(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_a_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_a_args.end, argv[0]); + return 1; + } + + const char * hostname = mdns_query_a_args.hostname->sval[0]; + int timeout = mdns_query_a_args.timeout->ival[0]; + + if (!hostname || !hostname[0]) { + printf("ERROR: Hostname not supplied\n"); + return 1; + } + + if (timeout <= 0) { + timeout = 1000; + } + + printf("Query AAAA: %s.local, Timeout: %d\n", hostname, timeout); + + struct ip6_addr addr; + memset(addr.addr, 0, 16); + + esp_err_t err = mdns_query_aaaa(hostname, timeout, &addr); + if (err) { + if (err == ESP_ERR_NOT_FOUND) { + printf("Host was not found!\n"); + return 0; + } + printf("ERROR: Query Failed\n"); + return 1; + } + + printf(IPV6STR "\n", IPV62STR(addr)); + + return 0; +} + +static void register_mdns_query_aaaa() +{ + mdns_query_a_args.hostname = arg_str1(NULL, NULL, "", "Hostname that is searched for"); + mdns_query_a_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_a_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_aaaa", + .help = "Query MDNS for IPv6", + .hint = NULL, + .func = &cmd_mdns_query_aaaa, + .argtable = &mdns_query_a_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *instance; + struct arg_str *service; + struct arg_str *proto; + struct arg_int *timeout; + struct arg_end *end; +} mdns_query_srv_args; + +static int cmd_mdns_query_srv(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_srv_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_srv_args.end, argv[0]); + return 1; + } + + const char * instance = mdns_query_srv_args.instance->sval[0]; + const char * service = mdns_query_srv_args.service->sval[0]; + const char * proto = mdns_query_srv_args.proto->sval[0]; + int timeout = mdns_query_srv_args.timeout->ival[0]; + + if (timeout <= 0) { + timeout = 1000; + } + + printf("Query SRV: %s.%s.%s.local, Timeout: %d\n", instance, service, proto, timeout); + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query_srv(instance, service, proto, timeout, &results); + if (err) { + printf("ERROR: Query Failed\n"); + return 1; + } + if (!results) { + printf("No results found!\n"); + return 0; + } + mdns_print_results(results); + mdns_query_results_free(results); + return 0; +} + +static void register_mdns_query_srv() +{ + mdns_query_srv_args.instance = arg_str1(NULL, NULL, "", "Instance to search for"); + mdns_query_srv_args.service = arg_str1(NULL, NULL, "", "Service to search for (ex. _http, _smb, etc.)"); + mdns_query_srv_args.proto = arg_str1(NULL, NULL, "", "Protocol to search for (_tcp, _udp, etc.)"); + mdns_query_srv_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_srv_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_srv", + .help = "Query MDNS for Service SRV", + .hint = NULL, + .func = &cmd_mdns_query_srv, + .argtable = &mdns_query_srv_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *instance; + struct arg_str *service; + struct arg_str *proto; + struct arg_int *timeout; + struct arg_end *end; +} mdns_query_txt_args; + +static int cmd_mdns_query_txt(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_txt_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_txt_args.end, argv[0]); + return 1; + } + + const char * instance = mdns_query_txt_args.instance->sval[0]; + const char * service = mdns_query_txt_args.service->sval[0]; + const char * proto = mdns_query_txt_args.proto->sval[0]; + int timeout = mdns_query_txt_args.timeout->ival[0]; + + printf("Query TXT: %s.%s.%s.local, Timeout: %d\n", instance, service, proto, timeout); + + if (timeout <= 0) { + timeout = 5000; + } + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query_txt(instance, service, proto, timeout, &results); + if (err) { + printf("ERROR: Query Failed\n"); + return 1; + } + if (!results) { + printf("No results found!\n"); + return 0; + } + + mdns_print_results(results); + mdns_query_results_free(results); + return 0; +} + +static void register_mdns_query_txt() +{ + mdns_query_txt_args.instance = arg_str1(NULL, NULL, "", "Instance to search for"); + mdns_query_txt_args.service = arg_str1(NULL, NULL, "", "Service to search for (ex. _http, _smb, etc.)"); + mdns_query_txt_args.proto = arg_str1(NULL, NULL, "", "Protocol to search for (_tcp, _udp, etc.)"); + mdns_query_txt_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_txt_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_txt", + .help = "Query MDNS for Service TXT", + .hint = NULL, + .func = &cmd_mdns_query_txt, + .argtable = &mdns_query_txt_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_int *timeout; + struct arg_int *max_results; + struct arg_end *end; +} mdns_query_ptr_args; + +static int cmd_mdns_query_ptr(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_ptr_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_ptr_args.end, argv[0]); + return 1; + } + + const char * service = mdns_query_ptr_args.service->sval[0]; + const char * proto = mdns_query_ptr_args.proto->sval[0]; + int timeout = mdns_query_ptr_args.timeout->ival[0]; + int max_results = mdns_query_ptr_args.max_results->ival[0]; + + if (timeout <= 0) { + timeout = 5000; + } + + if (max_results <= 0 || max_results > 255) { + max_results = 255; + } + + printf("Query PTR: %s.%s.local, Timeout: %d, Max Results: %d\n", service, proto, timeout, max_results); + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query_ptr(service, proto, timeout, max_results, &results); + if (err) { + printf("ERROR: Query Failed\n"); + return 1; + } + if (!results) { + printf("No results found!\n"); + return 0; + } + + mdns_print_results(results); + mdns_query_results_free(results); + return 0; +} + +static void register_mdns_query_ptr() +{ + mdns_query_ptr_args.service = arg_str1(NULL, NULL, "", "Service to search for (ex. _http, _smb, etc.)"); + mdns_query_ptr_args.proto = arg_str1(NULL, NULL, "", "Protocol to search for (_tcp, _udp, etc.)"); + mdns_query_ptr_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_ptr_args.max_results = arg_int0("m", "max_results", "", "Maximum results returned"); + mdns_query_ptr_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_ptr", + .help = "Query MDNS for Service", + .hint = NULL, + .func = &cmd_mdns_query_ptr, + .argtable = &mdns_query_ptr_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *hostname; + struct arg_int *timeout; + struct arg_int *max_results; + struct arg_end *end; +} mdns_query_ip_args; + +static int cmd_mdns_query_ip(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_ip_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_ip_args.end, argv[0]); + return 1; + } + + const char * hostname = mdns_query_ip_args.hostname->sval[0]; + int timeout = mdns_query_ip_args.timeout->ival[0]; + int max_results = mdns_query_ip_args.max_results->ival[0]; + + if (!hostname || !hostname[0]) { + printf("ERROR: Hostname not supplied\n"); + return 1; + } + + if (timeout <= 0) { + timeout = 1000; + } + + if (max_results < 0 || max_results > 255) { + max_results = 255; + } + + printf("Query IP: %s.local, Timeout: %d, Max Results: %d\n", hostname, timeout, max_results); + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query(hostname, NULL, NULL, MDNS_TYPE_ANY, timeout, max_results, &results); + if (err) { + printf("ERROR: Query Failed\n"); + return 1; + } + if (!results) { + printf("No results found!\n"); + return 0; + } + mdns_print_results(results); + mdns_query_results_free(results); + + return 0; +} + +static void register_mdns_query_ip() +{ + mdns_query_ip_args.hostname = arg_str1(NULL, NULL, "", "Hostname that is searched for"); + mdns_query_ip_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_ip_args.max_results = arg_int0("m", "max_results", "", "Maximum results returned"); + mdns_query_ip_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_ip", + .help = "Query MDNS for IP", + .hint = NULL, + .func = &cmd_mdns_query_ip, + .argtable = &mdns_query_ip_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *instance; + struct arg_str *service; + struct arg_str *proto; + struct arg_int *timeout; + struct arg_int *max_results; + struct arg_end *end; +} mdns_query_svc_args; + +static int cmd_mdns_query_svc(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_svc_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_svc_args.end, argv[0]); + return 1; + } + + const char * instance = mdns_query_svc_args.instance->sval[0]; + const char * service = mdns_query_svc_args.service->sval[0]; + const char * proto = mdns_query_svc_args.proto->sval[0]; + int timeout = mdns_query_svc_args.timeout->ival[0]; + int max_results = mdns_query_svc_args.max_results->ival[0]; + + if (timeout <= 0) { + timeout = 5000; + } + + if (max_results < 0 || max_results > 255) { + max_results = 255; + } + + printf("Query SVC: %s.%s.%s.local, Timeout: %d, Max Results: %d\n", instance, service, proto, timeout, max_results); + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query(instance, service, proto, MDNS_TYPE_ANY, timeout, max_results, &results); + if (err) { + printf("ERROR: Query Failed\n"); + return 1; + } + if (!results) { + printf("No results found!\n"); + return 0; + } + + mdns_print_results(results); + mdns_query_results_free(results); + return 0; +} + +static void register_mdns_query_svc() +{ + mdns_query_svc_args.instance = arg_str1(NULL, NULL, "", "Instance to search for"); + mdns_query_svc_args.service = arg_str1(NULL, NULL, "", "Service to search for (ex. _http, _smb, etc.)"); + mdns_query_svc_args.proto = arg_str1(NULL, NULL, "", "Protocol to search for (_tcp, _udp, etc.)"); + mdns_query_svc_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_svc_args.max_results = arg_int0("m", "max_results", "", "Maximum results returned"); + mdns_query_svc_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_svc", + .help = "Query MDNS for Service TXT & SRV", + .hint = NULL, + .func = &cmd_mdns_query_svc, + .argtable = &mdns_query_svc_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *hostname; + struct arg_str *instance; + struct arg_end *end; +} mdns_init_args; + +static int cmd_mdns_init(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_init_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_init_args.end, argv[0]); + return 1; + } + + ESP_ERROR_CHECK( mdns_init() ); + + if (mdns_init_args.hostname->sval[0]) { + ESP_ERROR_CHECK( mdns_hostname_set(mdns_init_args.hostname->sval[0]) ); + printf("MDNS: Hostname: %s\n", mdns_init_args.hostname->sval[0]); + } + + if (mdns_init_args.instance->sval[0]) { + ESP_ERROR_CHECK( mdns_instance_name_set(mdns_init_args.instance->sval[0]) ); + printf("MDNS: Instance: %s\n", mdns_init_args.instance->sval[0]); + } + + return 0; +} + +static void register_mdns_init() +{ + mdns_init_args.hostname = arg_str0("h", "hostname", "", "Hostname that the server will advertise"); + mdns_init_args.instance = arg_str0("i", "instance", "", "Default instance name for services"); + mdns_init_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_init", + .help = "Start MDNS Server", + .hint = NULL, + .func = &cmd_mdns_init, + .argtable = &mdns_init_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static int cmd_mdns_free(int argc, char** argv) +{ + mdns_free(); + return 0; +} + +static void register_mdns_free() +{ + const esp_console_cmd_t cmd_free = { + .command = "mdns_free", + .help = "Stop MDNS Server", + .hint = NULL, + .func = &cmd_mdns_free, + .argtable = NULL + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_free) ); +} + +static struct { + struct arg_str *hostname; + struct arg_end *end; +} mdns_set_hostname_args; + +static int cmd_mdns_set_hostname(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_set_hostname_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_set_hostname_args.end, argv[0]); + return 1; + } + + if (mdns_set_hostname_args.hostname->sval[0] == NULL) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_hostname_set(mdns_set_hostname_args.hostname->sval[0]) ); + return 0; +} + +static void register_mdns_set_hostname() +{ + mdns_set_hostname_args.hostname = arg_str1(NULL, NULL, "", "Hostname that the server will advertise"); + mdns_set_hostname_args.end = arg_end(2); + + const esp_console_cmd_t cmd_set_hostname = { + .command = "mdns_set_hostname", + .help = "Set MDNS Server hostname", + .hint = NULL, + .func = &cmd_mdns_set_hostname, + .argtable = &mdns_set_hostname_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_set_hostname) ); +} + +static struct { + struct arg_str *instance; + struct arg_end *end; +} mdns_set_instance_args; + +static int cmd_mdns_set_instance(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_set_instance_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_set_instance_args.end, argv[0]); + return 1; + } + + if (mdns_set_instance_args.instance->sval[0] == NULL) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_instance_name_set(mdns_set_instance_args.instance->sval[0]) ); + return 0; +} + +static void register_mdns_set_instance() +{ + mdns_set_instance_args.instance = arg_str1(NULL, NULL, "", "Default instance name for services"); + mdns_set_instance_args.end = arg_end(2); + + const esp_console_cmd_t cmd_set_instance = { + .command = "mdns_set_instance", + .help = "Set MDNS Server Istance Name", + .hint = NULL, + .func = &cmd_mdns_set_instance, + .argtable = &mdns_set_instance_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_set_instance) ); +} + +static mdns_txt_item_t * _convert_items(const char **values, int count) +{ + int i=0,e; + const char * value = NULL; + mdns_txt_item_t * items = (mdns_txt_item_t*) malloc(sizeof(mdns_txt_item_t) * count); + if (!items) { + printf("ERROR: No Memory!\n"); + goto fail; + + } + memset(items, 0, sizeof(mdns_txt_item_t) * count); + + for (i=0; isval[0] || !mdns_add_args.proto->sval[0] || !mdns_add_args.port->ival[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + const char * instance = NULL; + if (mdns_add_args.instance->sval[0] && mdns_add_args.instance->sval[0][0]) { + instance = mdns_add_args.instance->sval[0]; + printf("MDNS: Service Instance: %s\n", instance); + } + mdns_txt_item_t * items = NULL; + if (mdns_add_args.txt->count) { + items = _convert_items(mdns_add_args.txt->sval, mdns_add_args.txt->count); + if (!items) { + printf("ERROR: No Memory!\n"); + return 1; + + } + } + + ESP_ERROR_CHECK( mdns_service_add(instance, mdns_add_args.service->sval[0], mdns_add_args.proto->sval[0], mdns_add_args.port->ival[0], items, mdns_add_args.txt->count) ); + free(items); + return 0; +} + +static void register_mdns_service_add() +{ + mdns_add_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_add_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_add_args.port = arg_int1(NULL, NULL, "", "Service Port"); + mdns_add_args.instance = arg_str0("i", "instance", "", "Instance name"); + mdns_add_args.txt = arg_strn(NULL, NULL, "item", 0, 30, "TXT Items (name=value)"); + mdns_add_args.end = arg_end(2); + + const esp_console_cmd_t cmd_add = { + .command = "mdns_service_add", + .help = "Add service to MDNS", + .hint = NULL, + .func = &cmd_mdns_service_add, + .argtable = &mdns_add_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_add) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_end *end; +} mdns_remove_args; + +static int cmd_mdns_service_remove(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_remove_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_remove_args.end, argv[0]); + return 1; + } + + if (!mdns_remove_args.service->sval[0] || !mdns_remove_args.proto->sval[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_service_remove(mdns_remove_args.service->sval[0], mdns_remove_args.proto->sval[0]) ); + return 0; +} + +static void register_mdns_service_remove() +{ + mdns_remove_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_remove_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_remove_args.end = arg_end(2); + + const esp_console_cmd_t cmd_remove = { + .command = "mdns_service_remove", + .help = "Remove service from MDNS", + .hint = NULL, + .func = &cmd_mdns_service_remove, + .argtable = &mdns_remove_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_remove) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_str *instance; + struct arg_end *end; +} mdns_service_instance_set_args; + +static int cmd_mdns_service_instance_set(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_service_instance_set_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_service_instance_set_args.end, argv[0]); + return 1; + } + + if (!mdns_service_instance_set_args.service->sval[0] || !mdns_service_instance_set_args.proto->sval[0] || !mdns_service_instance_set_args.instance->sval[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_service_instance_name_set(mdns_service_instance_set_args.service->sval[0], mdns_service_instance_set_args.proto->sval[0], mdns_service_instance_set_args.instance->sval[0]) ); + return 0; +} + +static void register_mdns_service_instance_set() +{ + mdns_service_instance_set_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_service_instance_set_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_service_instance_set_args.instance = arg_str1(NULL, NULL, "", "Instance name"); + mdns_service_instance_set_args.end = arg_end(2); + + const esp_console_cmd_t cmd_add = { + .command = "mdns_service_instance_set", + .help = "Set MDNS Service Instance Name", + .hint = NULL, + .func = &cmd_mdns_service_instance_set, + .argtable = &mdns_service_instance_set_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_add) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_int *port; + struct arg_end *end; +} mdns_service_port_set_args; + +static int cmd_mdns_service_port_set(int argc, char** argv) { + int nerrors = arg_parse(argc, argv, (void**) &mdns_service_port_set_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_service_port_set_args.end, argv[0]); + return 1; + } + + if (!mdns_service_port_set_args.service->sval[0] || !mdns_service_port_set_args.proto->sval[0] || !mdns_service_port_set_args.port->ival[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_service_port_set(mdns_service_port_set_args.service->sval[0], mdns_service_port_set_args.proto->sval[0], mdns_service_port_set_args.port->ival[0]) ); + return 0; +} + +static void register_mdns_service_port_set() +{ + mdns_service_port_set_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_service_port_set_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_service_port_set_args.port = arg_int1(NULL, NULL, "", "Service Port"); + mdns_service_port_set_args.end = arg_end(2); + + const esp_console_cmd_t cmd_add = { + .command = "mdns_service_port_set", + .help = "Set MDNS Service port", + .hint = NULL, + .func = &cmd_mdns_service_port_set, + .argtable = &mdns_service_port_set_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_add) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_str *txt; + struct arg_end *end; +} mdns_txt_replace_args; + +static int cmd_mdns_service_txt_replace(int argc, char** argv) +{ + mdns_txt_item_t * items = NULL; + int nerrors = arg_parse(argc, argv, (void**) &mdns_txt_replace_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_txt_replace_args.end, argv[0]); + return 1; + } + + if (!mdns_txt_replace_args.service->sval[0] || !mdns_txt_replace_args.proto->sval[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + if (mdns_txt_replace_args.txt->count) { + items = _convert_items(mdns_txt_replace_args.txt->sval, mdns_txt_replace_args.txt->count); + if (!items) { + printf("ERROR: No Memory!\n"); + return 1; + + } + } + ESP_ERROR_CHECK( mdns_service_txt_set(mdns_txt_replace_args.service->sval[0], mdns_txt_replace_args.proto->sval[0], items, mdns_txt_replace_args.txt->count) ); + free(items); + return 0; +} + +static void register_mdns_service_txt_replace() +{ + mdns_txt_replace_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_txt_replace_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_txt_replace_args.txt = arg_strn(NULL, NULL, "item", 0, 30, "TXT Items (name=value)"); + mdns_txt_replace_args.end = arg_end(2); + + const esp_console_cmd_t cmd_txt_set = { + .command = "mdns_service_txt_replace", + .help = "Replace MDNS service TXT items", + .hint = NULL, + .func = &cmd_mdns_service_txt_replace, + .argtable = &mdns_txt_replace_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_txt_set) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_str *var; + struct arg_str *value; + struct arg_end *end; +} mdns_txt_set_args; + +static int cmd_mdns_service_txt_set(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_txt_set_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_txt_set_args.end, argv[0]); + return 1; + } + + if (!mdns_txt_set_args.service->sval[0] || !mdns_txt_set_args.proto->sval[0] || !mdns_txt_set_args.var->sval[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_service_txt_item_set(mdns_txt_set_args.service->sval[0], mdns_txt_set_args.proto->sval[0], mdns_txt_set_args.var->sval[0], mdns_txt_set_args.value->sval[0]) ); + return 0; +} + +static void register_mdns_service_txt_set() +{ + mdns_txt_set_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_txt_set_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_txt_set_args.var = arg_str1(NULL, NULL, "", "Item Name"); + mdns_txt_set_args.value = arg_str1(NULL, NULL, "", "Item Value"); + mdns_txt_set_args.end = arg_end(2); + + const esp_console_cmd_t cmd_txt_set = { + .command = "mdns_service_txt_set", + .help = "Add/Set MDNS service TXT item", + .hint = NULL, + .func = &cmd_mdns_service_txt_set, + .argtable = &mdns_txt_set_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_txt_set) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_str *var; + struct arg_end *end; +} mdns_txt_remove_args; + +static int cmd_mdns_service_txt_remove(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_txt_remove_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_txt_remove_args.end, argv[0]); + return 1; + } + + if (!mdns_txt_remove_args.service->sval[0] || !mdns_txt_remove_args.proto->sval[0] || !mdns_txt_remove_args.var->sval[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_service_txt_item_remove(mdns_txt_remove_args.service->sval[0], mdns_txt_remove_args.proto->sval[0], mdns_txt_remove_args.var->sval[0]) ); + return 0; +} + +static void register_mdns_service_txt_remove() +{ + mdns_txt_remove_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_txt_remove_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_txt_remove_args.var = arg_str1(NULL, NULL, "", "Item Name"); + mdns_txt_remove_args.end = arg_end(2); + + const esp_console_cmd_t cmd_txt_remove = { + .command = "mdns_service_txt_remove", + .help = "Remove MDNS service TXT item", + .hint = NULL, + .func = &cmd_mdns_service_txt_remove, + .argtable = &mdns_txt_remove_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_txt_remove) ); +} + +static int cmd_mdns_service_remove_all(int argc, char** argv) +{ + mdns_service_remove_all(); + return 0; +} + +static void register_mdns_service_remove_all() +{ + const esp_console_cmd_t cmd_free = { + .command = "mdns_service_remove_all", + .help = "Remove all MDNS services", + .hint = NULL, + .func = &cmd_mdns_service_remove_all, + .argtable = NULL + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_free) ); +} + +void mdns_console_register() +{ + register_mdns_init(); + register_mdns_free(); + register_mdns_set_hostname(); + register_mdns_set_instance(); + register_mdns_service_add(); + register_mdns_service_remove(); + register_mdns_service_instance_set(); + register_mdns_service_port_set(); + register_mdns_service_txt_replace(); + register_mdns_service_txt_set(); + register_mdns_service_txt_remove(); + register_mdns_service_remove_all(); + + register_mdns_query_a(); + register_mdns_query_aaaa(); + register_mdns_query_txt(); + register_mdns_query_srv(); + register_mdns_query_ptr(); + + register_mdns_query_ip(); + register_mdns_query_svc(); +} + diff --git a/components/mdns/mdns_networking.c b/components/mdns/mdns_networking.c new file mode 100644 index 00000000..e4ab816d --- /dev/null +++ b/components/mdns/mdns_networking.c @@ -0,0 +1,284 @@ + +/* + * MDNS Server Networking + * + */ +#include +#include "mdns_networking.h" + + +extern mdns_server_t * _mdns_server; + +/* + * MDNS Server Networking + * + */ + +/** + * @brief the receive callback of the raw udp api. Packets are received here + * + */ +static void _udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip_addr_t *raddr, uint16_t rport) +{ + + uint8_t i; + while (pb != NULL) { + struct pbuf * this_pb = pb; + pb = pb->next; + this_pb->next = NULL; + + mdns_rx_packet_t * packet = (mdns_rx_packet_t *)malloc(sizeof(mdns_rx_packet_t)); + if (!packet) { + //missed packet - no memory + pbuf_free(this_pb); + continue; + } + + packet->tcpip_if = TCPIP_ADAPTER_IF_MAX; + packet->pb = this_pb; + packet->src_port = rport; + memcpy(&packet->src, raddr, sizeof(ip_addr_t)); + packet->dest.type = packet->src.type; + + if (packet->src.type == IPADDR_TYPE_V4) { + packet->ip_protocol = MDNS_IP_PROTOCOL_V4; + struct ip_hdr * iphdr = (struct ip_hdr *)(((uint8_t *)(packet->pb->payload)) - UDP_HLEN - IP_HLEN); + packet->dest.u_addr.ip4.addr = iphdr->dest.addr; + } else { + packet->ip_protocol = MDNS_IP_PROTOCOL_V6; + struct ip6_hdr * ip6hdr = (struct ip6_hdr *)(((uint8_t *)(packet->pb->payload)) - UDP_HLEN - IP6_HLEN); + memcpy(&packet->dest.u_addr.ip6.addr, (uint8_t *)ip6hdr->dest.addr, 16); + } + packet->multicast = ip_addr_ismulticast(&(packet->dest)); + + //lwip does not return the proper pcb if you have more than one for the same multicast address (but different interfaces) + struct netif * netif = NULL; + void * nif = NULL; + struct udp_pcb * pcb = NULL; + for (i=0; iinterfaces[i].pcbs[packet->ip_protocol].pcb; + tcpip_adapter_get_netif (i, &nif); + netif = (struct netif *)nif; + if (pcb && netif && netif == ip_current_input_netif ()) { + if (packet->src.type == IPADDR_TYPE_V4) { + if ((packet->src.u_addr.ip4.addr & netif->netmask.u_addr.ip4.addr) != (netif->ip_addr.u_addr.ip4.addr & netif->netmask.u_addr.ip4.addr)) { + //packet source is not in the same subnet + pcb = NULL; + break; + } + } + packet->tcpip_if = i; + break; + } + pcb = NULL; + } + + if (!pcb || !_mdns_server || !_mdns_server->action_queue + || _mdns_send_rx_action(packet) != ESP_OK) { + pbuf_free(this_pb); + free(packet); + } + } + +} + +/** + * @brief Stop PCB Main code + */ +static void _udp_pcb_deinit(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + if (!_mdns_server) { + return; + } + mdns_pcb_t * _pcb = &_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol]; + if (_pcb->pcb) { + _pcb->state = PCB_OFF; + udp_recv(_pcb->pcb, NULL, NULL); + udp_disconnect(_pcb->pcb); + udp_remove(_pcb->pcb); + free(_pcb->probe_services); + _pcb->pcb = NULL; + _pcb->probe_ip = false; + _pcb->probe_services = NULL; + _pcb->probe_services_len = 0; + _pcb->probe_running = false; + _pcb->failed_probes = 0; + } +} + +/** + * @brief Start PCB V4 + */ +static esp_err_t _udp_pcb_v4_init(tcpip_adapter_if_t tcpip_if) +{ + tcpip_adapter_ip_info_t if_ip_info; + + if (!_mdns_server || _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb) { + return ESP_ERR_INVALID_STATE; + } + + if (tcpip_adapter_get_ip_info(tcpip_if, &if_ip_info) || if_ip_info.ip.addr == 0) { + return ESP_ERR_INVALID_ARG; + } + + ip_addr_t interface_addr = IPADDR4_INIT(if_ip_info.ip.addr); + + ip_addr_t multicast_addr; + IP_ADDR4(&multicast_addr, 224, 0, 0, 251); + + if (igmp_joingroup((const struct ip4_addr *)&interface_addr.u_addr.ip4, (const struct ip4_addr *)&multicast_addr.u_addr.ip4)) { + return ESP_ERR_INVALID_STATE; + } + + struct udp_pcb * pcb = udp_new_ip_type(IPADDR_TYPE_V4); + if (!pcb) { + return ESP_ERR_NO_MEM; + } + + if (udp_bind(pcb, &interface_addr, MDNS_SERVICE_PORT) != 0) { + udp_remove(pcb); + return ESP_ERR_INVALID_STATE; + } + + pcb->mcast_ttl = 1; + pcb->remote_port = MDNS_SERVICE_PORT; + ip_addr_copy(pcb->multicast_ip, interface_addr); + ip_addr_copy(pcb->remote_ip, multicast_addr); + + _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb = pcb; + _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].failed_probes = 0; + udp_recv(pcb, &_udp_recv, _mdns_server); + + return ESP_OK; +} + +/** + * @brief Start PCB V6 + */ +static esp_err_t _udp_pcb_v6_init(tcpip_adapter_if_t tcpip_if) +{ + ip_addr_t multicast_addr = IPADDR6_INIT(0x000002ff, 0, 0, 0xfb000000); + ip_addr_t interface_addr; + interface_addr.type = IPADDR_TYPE_V6; + + if (!_mdns_server || _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb) { + return ESP_ERR_INVALID_STATE; + } + + if (tcpip_adapter_get_ip6_linklocal(tcpip_if, &interface_addr.u_addr.ip6)) { + return ESP_ERR_INVALID_ARG; + } + + if (mld6_joingroup(&(interface_addr.u_addr.ip6), &(multicast_addr.u_addr.ip6))) { + return ESP_ERR_INVALID_STATE; + } + + struct udp_pcb * pcb = udp_new_ip_type(IPADDR_TYPE_V6); + if (!pcb) { + return ESP_ERR_NO_MEM; + } + + if (udp_bind(pcb, &interface_addr, MDNS_SERVICE_PORT) != 0) { + udp_remove(pcb); + return ESP_ERR_INVALID_STATE; + } + + pcb->remote_port = MDNS_SERVICE_PORT; + ip_addr_copy(pcb->remote_ip, multicast_addr); + + _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb = pcb; + _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].failed_probes = 0; + udp_recv(pcb, &_udp_recv, _mdns_server); + + return ESP_OK; +} + +/** + * @brief Start PCB Main code + */ +static esp_err_t _udp_pcb_init(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + if (ip_protocol == MDNS_IP_PROTOCOL_V4) { + return _udp_pcb_v4_init(tcpip_if); + } else if (ip_protocol == MDNS_IP_PROTOCOL_V6) { + return _udp_pcb_v6_init(tcpip_if); + } + return ESP_ERR_INVALID_ARG; +} + +typedef struct { + struct tcpip_api_call call; + tcpip_adapter_if_t tcpip_if; + mdns_ip_protocol_t ip_protocol; + esp_err_t err; +} mdns_api_call_t; + +/** + * @brief Start PCB from LwIP thread + */ +static err_t _mdns_pcb_init_api(struct tcpip_api_call *api_call_msg) +{ + mdns_api_call_t * msg = (mdns_api_call_t *)api_call_msg; + msg->err = _udp_pcb_init(msg->tcpip_if, msg->ip_protocol); + return msg->err; +} + +/** + * @brief Stop PCB from LwIP thread + */ +static err_t _mdns_pcb_deinit_api(struct tcpip_api_call *api_call_msg) +{ + mdns_api_call_t * msg = (mdns_api_call_t *)api_call_msg; + _udp_pcb_deinit(msg->tcpip_if, msg->ip_protocol); + msg->err = ESP_OK; + return ESP_OK; +} + +/* + * Non-static functions below are + * - _mdns prefixed + * - commented in mdns_networking.h header + */ +esp_err_t _mdns_pcb_init(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_api_call_t msg = { + .tcpip_if = tcpip_if, + .ip_protocol = ip_protocol + }; + tcpip_api_call(_mdns_pcb_init_api, (struct tcpip_api_call*)&msg); + return msg.err; +} + +esp_err_t _mdns_pcb_deinit(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_api_call_t msg = { + .tcpip_if = tcpip_if, + .ip_protocol = ip_protocol + }; + tcpip_api_call(_mdns_pcb_deinit_api, (struct tcpip_api_call*)&msg); + return msg.err; +} + +size_t _mdns_udp_pcb_write(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const ip_addr_t *ip, uint16_t port, uint8_t * data, size_t len) +{ + struct netif * netif = NULL; + void * nif = NULL; + esp_err_t err = tcpip_adapter_get_netif(tcpip_if, &nif); + netif = (struct netif *)nif; + if (err) { + return 0; + } + + struct pbuf* pbt = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + if (pbt == NULL) { + return 0; + } + memcpy((uint8_t *)pbt->payload, data, len); + + err = udp_sendto_if (_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb, pbt, ip, port, netif); + pbuf_free(pbt); + if (err) { + return 0; + } + return len; +} diff --git a/components/mdns/private_include/mdns_networking.h b/components/mdns/private_include/mdns_networking.h new file mode 100644 index 00000000..5b56f806 --- /dev/null +++ b/components/mdns/private_include/mdns_networking.h @@ -0,0 +1,52 @@ +#ifndef ESP_MDNS_NETWORKING_H_ +#define ESP_MDNS_NETWORKING_H_ + +/* + * MDNS Server Networking -- private include + * + */ +#include "mdns.h" +#include "mdns_private.h" +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "lwip/ip_addr.h" +#include "lwip/pbuf.h" +#include "lwip/igmp.h" +#include "lwip/udp.h" +#include "lwip/mld6.h" +#include "lwip/priv/tcpip_priv.h" +#include "esp_wifi.h" +#include "esp_system.h" +#include "esp_timer.h" +#include "esp_event_loop.h" + + +/** + * @brief Queue RX packet action + */ +esp_err_t _mdns_send_rx_action(mdns_rx_packet_t * packet); + +/** + * @brief Start PCB + */ +esp_err_t _mdns_pcb_init(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); + +/** + * @brief Stop PCB + */ +esp_err_t _mdns_pcb_deinit(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); + +/** + * @brief send packet over UDP + * + * @param server The server + * @param data byte array containing the packet data + * @param len length of the packet data + * + * @return length of sent packet or 0 on error + */ +size_t _mdns_udp_pcb_write(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const ip_addr_t *ip, uint16_t port, uint8_t * data, size_t len); + +#endif /* ESP_MDNS_NETWORKING_H_ */ diff --git a/components/mdns/private_include/mdns_private.h b/components/mdns/private_include/mdns_private.h new file mode 100644 index 00000000..a3d9fa27 --- /dev/null +++ b/components/mdns/private_include/mdns_private.h @@ -0,0 +1,388 @@ +// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef MDNS_PRIVATE_H_ +#define MDNS_PRIVATE_H_ + +//#define MDNS_ENABLE_DEBUG + +#ifdef MDNS_ENABLE_DEBUG +#define _mdns_dbg_printf(...) printf(__VA_ARGS__) +#endif + +#define MDNS_ANSWER_PTR_TTL 4500 +#define MDNS_ANSWER_TXT_TTL 4500 +#define MDNS_ANSWER_SRV_TTL 120 +#define MDNS_ANSWER_A_TTL 120 +#define MDNS_ANSWER_AAAA_TTL 120 + +#define MDNS_FLAGS_AUTHORITATIVE 0x8400 +#define MDNS_FLAGS_DISTRIBUTED 0x0200 + +#define MDNS_NAME_REF 0xC000 + +//custom type! only used by this implementation +//to help manage service discovery handling +#define MDNS_TYPE_SDPTR 0x0032 + +#define MDNS_CLASS_IN 0x0001 +#define MDNS_CLASS_ANY 0x00FF +#define MDNS_CLASS_IN_FLUSH_CACHE 0x8001 + +#define MDNS_ANSWER_ALL 0x3F +#define MDNS_ANSWER_PTR 0x08 +#define MDNS_ANSWER_TXT 0x04 +#define MDNS_ANSWER_SRV 0x02 +#define MDNS_ANSWER_A 0x01 +#define MDNS_ANSWER_AAAA 0x10 +#define MDNS_ANSWER_NSEC 0x20 +#define MDNS_ANSWER_SDPTR 0x80 + +#define MDNS_SERVICE_PORT 5353 // UDP port that the server runs on +#define MDNS_SERVICE_STACK_DEPTH 4096 // Stack size for the service thread +#define MDNS_PACKET_QUEUE_LEN 16 // Maximum packets that can be queued for parsing +#define MDNS_ACTION_QUEUE_LEN 16 // Maximum actions pending to the server +#define MDNS_TXT_MAX_LEN 1024 // Maximum string length of text data in TXT record +#define MDNS_NAME_MAX_LEN 64 // Maximum string length of hostname, instance, service and proto +#define MDNS_NAME_BUF_LEN (MDNS_NAME_MAX_LEN+1) // Maximum char buffer size to hold hostname, instance, service or proto +#define MDNS_MAX_PACKET_SIZE 1460 // Maximum size of mDNS outgoing packet + +#define MDNS_HEAD_LEN 12 +#define MDNS_HEAD_ID_OFFSET 0 +#define MDNS_HEAD_FLAGS_OFFSET 2 +#define MDNS_HEAD_QUESTIONS_OFFSET 4 +#define MDNS_HEAD_ANSWERS_OFFSET 6 +#define MDNS_HEAD_SERVERS_OFFSET 8 +#define MDNS_HEAD_ADDITIONAL_OFFSET 10 + +#define MDNS_TYPE_OFFSET 0 +#define MDNS_CLASS_OFFSET 2 +#define MDNS_TTL_OFFSET 4 +#define MDNS_LEN_OFFSET 8 +#define MDNS_DATA_OFFSET 10 + +#define MDNS_SRV_PRIORITY_OFFSET 0 +#define MDNS_SRV_WEIGHT_OFFSET 2 +#define MDNS_SRV_PORT_OFFSET 4 +#define MDNS_SRV_FQDN_OFFSET 6 + +#define MDNS_TIMER_PERIOD_US 100000 + +#define MDNS_SERVICE_LOCK() xSemaphoreTake(_mdns_service_semaphore, portMAX_DELAY) +#define MDNS_SERVICE_UNLOCK() xSemaphoreGive(_mdns_service_semaphore) + +#define queueToEnd(type, queue, item) \ + if (!queue) { \ + queue = item; \ + } else { \ + type * _q = queue; \ + while (_q->next) { _q = _q->next; } \ + _q->next = item; \ + } + +#define queueDetach(type, queue, item) \ + if (queue) { \ + if (queue == item) { \ + queue = queue->next; \ + } else { \ + type * _q = queue; \ + while (_q->next && _q->next != item) { \ + _q = _q->next; \ + } \ + if (_q->next == item) { \ + _q->next = item->next; \ + item->next = NULL; \ + } \ + } \ + } + +#define queueFree(type, queue) while (queue) { type * _q = queue; queue = queue->next; free(_q); } + +#define PCB_STATE_IS_PROBING(s) (s->state > PCB_OFF && s->state < PCB_ANNOUNCE_1) +#define PCB_STATE_IS_ANNOUNCING(s) (s->state > PCB_PROBE_3 && s->state < PCB_RUNNING) +#define PCB_STATE_IS_RUNNING(s) (s->state == PCB_RUNNING) + +#define MDNS_SEARCH_LOCK() xSemaphoreTake(_mdns_server->search.lock, portMAX_DELAY) +#define MDNS_SEARCH_UNLOCK() xSemaphoreGive(_mdns_server->search.lock) + + +typedef enum { + PCB_OFF, PCB_DUP, PCB_INIT, + PCB_PROBE_1, PCB_PROBE_2, PCB_PROBE_3, + PCB_ANNOUNCE_1, PCB_ANNOUNCE_2, PCB_ANNOUNCE_3, + PCB_RUNNING +} mdns_pcb_state_t; + +typedef enum { + MDNS_ANSWER, MDNS_NS, MDNS_EXTRA +} mdns_parsed_recort_type_t; + +typedef enum { + ACTION_SYSTEM_EVENT, + ACTION_HOSTNAME_SET, + ACTION_INSTANCE_SET, + ACTION_SERVICE_ADD, + ACTION_SERVICE_DEL, + ACTION_SERVICE_INSTANCE_SET, + ACTION_SERVICE_PORT_SET, + ACTION_SERVICE_TXT_REPLACE, + ACTION_SERVICE_TXT_SET, + ACTION_SERVICE_TXT_DEL, + ACTION_SERVICES_CLEAR, + ACTION_SEARCH_ADD, + ACTION_SEARCH_SEND, + ACTION_SEARCH_END, + ACTION_TX_HANDLE, + ACTION_RX_HANDLE, + ACTION_TASK_STOP, + ACTION_MAX +} mdns_action_type_t; + + +typedef struct { + uint16_t id; + union { + struct { + uint16_t qr :1; + uint16_t opCode :4; + uint16_t aa :1; + uint16_t tc :1; + uint16_t rd :1; + uint16_t ra :1; + uint16_t z :1; + uint16_t ad :1; + uint16_t cd :1; + uint16_t rCode :4;//response/error code + }; + uint16_t value; + } flags; + uint16_t questions; //QDCOUNT + uint16_t answers; //ANCOUNT + uint16_t servers; //NSCOUNT + uint16_t additional;//ARCOUNT +} mdns_header_t; + +typedef struct { + char host[MDNS_NAME_BUF_LEN]; + char service[MDNS_NAME_BUF_LEN]; + char proto[MDNS_NAME_BUF_LEN]; + char domain[MDNS_NAME_BUF_LEN]; + uint8_t parts; + uint8_t sub; +} mdns_name_t; + +typedef struct mdns_parsed_question_s { + struct mdns_parsed_question_s * next; + uint16_t type; + bool unicast; + char * host; + char * service; + char * proto; + char * domain; +} mdns_parsed_question_t; + +typedef struct mdns_parsed_record_s { + struct mdns_parsed_record_s * next; + mdns_parsed_recort_type_t record_type; + uint16_t type; + uint16_t clas; + uint8_t flush; + uint32_t ttl; + char * host; + char * service; + char * proto; + char * domain; + uint16_t data_len; + uint8_t *data; +} mdns_parsed_record_t; + +typedef struct { + tcpip_adapter_if_t tcpip_if; + mdns_ip_protocol_t ip_protocol; + //struct udp_pcb *pcb; + ip_addr_t src; + uint16_t src_port; + uint8_t multicast; + uint8_t authoritative; + uint8_t probe; + uint8_t discovery; + uint8_t distributed; + mdns_parsed_question_t * questions; + mdns_parsed_record_t * records; +} mdns_parsed_packet_t; + +typedef struct { + tcpip_adapter_if_t tcpip_if; + mdns_ip_protocol_t ip_protocol; + struct pbuf *pb; + ip_addr_t src; + ip_addr_t dest; + uint16_t src_port; + uint8_t multicast; +} mdns_rx_packet_t; + +typedef struct mdns_txt_linked_item_s { + const char * key; /*!< item key name */ + const char * value; /*!< item value string */ + struct mdns_txt_linked_item_s * next; /*!< next result, or NULL for the last result in the list */ +} mdns_txt_linked_item_t; + +typedef struct { + const char * instance; + const char * service; + const char * proto; + uint16_t priority; + uint16_t weight; + uint16_t port; + mdns_txt_linked_item_t * txt; +} mdns_service_t; + +typedef struct mdns_srv_item_s { + struct mdns_srv_item_s * next; + mdns_service_t * service; +} mdns_srv_item_t; + +typedef struct mdns_out_question_s { + struct mdns_out_question_s * next; + uint16_t type; + uint8_t unicast; + const char * host; + const char * service; + const char * proto; + const char * domain; +} mdns_out_question_t; + +typedef struct mdns_out_answer_s { + struct mdns_out_answer_s * next; + uint16_t type; + uint8_t bye; + uint8_t flush; + mdns_service_t * service; + const char * custom_instance; + const char * custom_service; + const char * custom_proto; +} mdns_out_answer_t; + +typedef struct mdns_tx_packet_s { + struct mdns_tx_packet_s * next; + uint32_t send_at; + tcpip_adapter_if_t tcpip_if; + mdns_ip_protocol_t ip_protocol; + ip_addr_t dst; + uint16_t port; + uint16_t flags; + uint8_t distributed; + mdns_out_question_t * questions; + mdns_out_answer_t * answers; + mdns_out_answer_t * servers; + mdns_out_answer_t * additional; +} mdns_tx_packet_t; + +typedef struct { + mdns_pcb_state_t state; + struct udp_pcb * pcb; + mdns_srv_item_t ** probe_services; + uint8_t probe_services_len; + uint8_t probe_ip; + uint8_t probe_running; + uint16_t failed_probes; +} mdns_pcb_t; + +typedef enum { + SEARCH_OFF, + SEARCH_INIT, + SEARCH_RUNNING, + SEARCH_MAX +} mdns_search_once_state_t; + +typedef struct mdns_search_once_s { + struct mdns_search_once_s * next; + + mdns_search_once_state_t state; + uint32_t started_at; + uint32_t sent_at; + uint32_t timeout; + SemaphoreHandle_t lock; + uint16_t type; + uint8_t max_results; + uint8_t num_results; + char * instance; + char * service; + char * proto; + mdns_result_t * result; +} mdns_search_once_t; + +typedef struct mdns_server_s { + struct { + mdns_pcb_t pcbs[MDNS_IP_PROTOCOL_MAX]; + } interfaces[TCPIP_ADAPTER_IF_MAX]; + const char * hostname; + const char * instance; + mdns_srv_item_t * services; + SemaphoreHandle_t lock; + QueueHandle_t action_queue; + mdns_tx_packet_t * tx_queue_head; + mdns_search_once_t * search_once; + esp_timer_handle_t timer_handle; +} mdns_server_t; + +typedef struct { + mdns_action_type_t type; + union { + char * hostname; + char * instance; + struct { + system_event_id_t event_id; + tcpip_adapter_if_t interface; + } sys_event; + struct { + mdns_srv_item_t * service; + } srv_add; + struct { + mdns_srv_item_t * service; + } srv_del; + struct { + mdns_srv_item_t * service; + char * instance; + } srv_instance; + struct { + mdns_srv_item_t * service; + uint16_t port; + } srv_port; + struct { + mdns_srv_item_t * service; + uint8_t num_items; + mdns_txt_item_t * txt; + } srv_txt_replace; + struct { + mdns_srv_item_t * service; + char * key; + char * value; + } srv_txt_set; + struct { + mdns_srv_item_t * service; + char * key; + } srv_txt_del; + struct { + mdns_search_once_t * search; + } search_add; + struct { + mdns_tx_packet_t * packet; + } tx_handle; + struct { + mdns_rx_packet_t * packet; + } rx_handle; + } data; +} mdns_action_t; + +#endif /* MDNS_PRIVATE_H_ */ diff --git a/components/mdns/test_afl_fuzz_host/Makefile b/components/mdns/test_afl_fuzz_host/Makefile new file mode 100644 index 00000000..61bedc52 --- /dev/null +++ b/components/mdns/test_afl_fuzz_host/Makefile @@ -0,0 +1,43 @@ +TEST_NAME=test +FUZZ=afl-fuzz +COMPONENTS_DIR=../.. +CFLAGS=-g -DMDNS_TEST_MODE -I. -I.. -I../include -I../private_include -I$(COMPONENTS_DIR)/tcpip_adapter/include -I$(COMPONENTS_DIR)/esp32/include -include esp32_compat.h +MDNS_C_DEPENDENCY_INJECTION=-include mdns_di.h +ifeq ($(INSTR),off) + CC=gcc + CFLAGS+=-DINSTR_IS_OFF + TEST_NAME=test_sim +else + CC=afl-clang-fast +endif +CPP=$(CC) +LD=$(CC) +OBJECTS=mdns.o esp32_mock.o test.o + +OS := $(shell uname) +ifeq ($(OS),Darwin) + LDLIBS= +else + LDLIBS=-lbsd + CFLAGS+=-DUSE_BSD_STRING +endif + +all: $(TEST_NAME) + +%.o: %.c + @echo "[CC] $<" + @$(CC) $(CFLAGS) -c $< -o $@ + +mdns.o: ../mdns.c + @echo "[CC] $<" + @$(CC) $(CFLAGS) $(MDNS_C_DEPENDENCY_INJECTION) -c $< -o $@ + +$(TEST_NAME): $(OBJECTS) + @echo "[LD] $@" + @$(LD) $(OBJECTS) -o $@ $(LDLIBS) + +fuzz: $(TEST_NAME) + @$(FUZZ) -i "in" -o "out" -- ./$(TEST_NAME) + +clean: + @rm -rf *.o *.SYM $(TEST_NAME) out diff --git a/components/mdns/test_afl_fuzz_host/README.md b/components/mdns/test_afl_fuzz_host/README.md new file mode 100644 index 00000000..7d78bab5 --- /dev/null +++ b/components/mdns/test_afl_fuzz_host/README.md @@ -0,0 +1,58 @@ +## Introduction +This test uses [american fuzzy lop](http://lcamtuf.coredump.cx/afl/) to mangle real mdns packets and look for exceptions caused by the parser. + +A few actuall packets are collected and exported as bins in the ```in``` folder, which is then passed as input to AFL when testing. The setup procedure for the test includes all possible services and scenarios that could be used with the given input packets. Output of the parser before fuzzing can be found in [input_packets.txt](input_packets.txt) + +## Installing AFL +To run the test yourself, you need to dounload the [latest afl archive](http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz) and extract it to a folder on your computer. + +The rest of the document will refer to that folder as ```PATH_TO_AFL```. + +### Preparation +- On Mac, you will need to insall the latest Xcode and llvm support from [Homebrew](https://brew.sh) + + ```bash + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + brew install --with-clang --with-lld --HEAD llvm + export PATH="/usr/local/opt/llvm/bin:$PATH" + ``` + +- On Ubuntu you need the following packages: + + ```bash + sudo apt-get install make clang llvm libbsd-dev + ``` + +### Compile AFL +Compiling AFL is as easy as running make: + +```bash +cd [PATH_TO_AFL] +make +cd llvm_mode/ +make +``` + +After successful compilation, you can export the following variables to your shell (you can also add them to your profile if you want to use afl in other projects) + +```bash +export AFL_PATH=[PATH_TO_AFL] +export PATH="$AFL_PATH:$PATH" +``` + +## Running the test +Apple has a crash reporting service that could interfere with AFLs normal operation. To turn that off, run the following command: + +```bash +launchctl unload -w /System/Library/LaunchAgents/com.apple.ReportCrash.plist +sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.ReportCrash.Root.plist +``` + +Ubuntu has a similar service. To turn that off, run as root: + +```bash +echo core >/proc/sys/kernel/core_pattern +``` + +After going through all of the requirements above, you can ```cd``` into this test's folder and simply run ```make fuzz```. + diff --git a/components/mdns/test_afl_fuzz_host/esp32_compat.h b/components/mdns/test_afl_fuzz_host/esp32_compat.h new file mode 100644 index 00000000..402807c4 --- /dev/null +++ b/components/mdns/test_afl_fuzz_host/esp32_compat.h @@ -0,0 +1,239 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP32_COMPAT_H_ +#define _ESP32_COMPAT_H_ + +#ifdef MDNS_TEST_MODE + +// Not to include +#define ESP_MDNS_NETWORKING_H_ +#define _TCPIP_ADAPTER_H_ +#define __ESP_EVENT_H__ + + +#ifdef USE_BSD_STRING +#include +#endif +#include +#include +#include +#include +#include +#include +#include + + +#define ERR_OK 0 +#define ESP_OK 0 +#define ESP_FAIL -1 + +#define ESP_ERR_NO_MEM 0x101 +#define ESP_ERR_INVALID_ARG 0x102 +#define ESP_ERR_INVALID_STATE 0x103 +#define ESP_ERR_INVALID_SIZE 0x104 +#define ESP_ERR_NOT_FOUND 0x105 +#define ESP_ERR_NOT_SUPPORTED 0x106 +#define ESP_ERR_TIMEOUT 0x107 +#define ESP_ERR_INVALID_RESPONSE 0x108 +#define ESP_ERR_INVALID_CRC 0x109 + +#define pdTRUE true +#define pdFALSE false +#define pdPASS ( pdTRUE ) +#define pdFAIL ( pdFALSE ) + +#define portMAX_DELAY 0xFFFFFFFF +#define portTICK_PERIOD_MS 1 + +#define xSemaphoreTake(s,d) +#define xTaskDelete(a) +#define vTaskDelete(a) free(a) +#define xSemaphoreGive(s) +#define _mdns_pcb_init(a,b) true +#define _mdns_pcb_deinit(a,b) true +#define xSemaphoreCreateMutex() malloc(1) +#define vSemaphoreDelete(s) free(s) +#define xTaskCreatePinnedToCore(a,b,c,d,e,f,g) *(f) = malloc(1) +#define vTaskDelay(m) usleep((m)*0) +#define pbuf_free(p) free(p) +#define esp_random() (rand()%UINT32_MAX) +#define tcpip_adapter_get_ip_info(i,d) true +#define tcpip_adapter_dhcpc_get_status(a, b) TCPIP_ADAPTER_DHCP_STARTED +#define tcpip_adapter_get_ip6_linklocal(i,d) (ESP_OK) +#define tcpip_adapter_get_hostname(i, n) *(n) = "esp32-0123456789AB" + +#define IP4_ADDR(ipaddr, a,b,c,d) \ + (ipaddr)->addr = ((uint32_t)((d) & 0xff) << 24) | \ + ((uint32_t)((c) & 0xff) << 16) | \ + ((uint32_t)((b) & 0xff) << 8) | \ + (uint32_t)((a) & 0xff) +#define ip_2_ip6(ipaddr) (&((ipaddr)->u_addr.ip6)) +#define ip_2_ip4(ipaddr) (&((ipaddr)->u_addr.ip4)) + +#define IPADDR_TYPE_V4 0U +#define IPADDR_TYPE_V6 6U +#define IPADDR_TYPE_ANY 46U +#define IP_SET_TYPE_VAL(ipaddr, iptype) do { (ipaddr).type = (iptype); }while(0) +#define IP_ADDR4(ipaddr,a,b,c,d) do { IP4_ADDR(ip_2_ip4(ipaddr),a,b,c,d); \ + IP_SET_TYPE_VAL(*(ipaddr), IPADDR_TYPE_V4); } while(0) +#define IP_ADDR6(ipaddr,i0,i1,i2,i3) do { IP6_ADDR(ip_2_ip6(ipaddr),i0,i1,i2,i3); \ + IP_SET_TYPE_VAL(*(ipaddr), IPADDR_TYPE_V6); } while(0) + +#define IPADDR6_INIT(a, b, c, d) { { { { a, b, c, d } } }, IPADDR_TYPE_V6 } + +typedef int32_t esp_err_t; + +typedef void * xSemaphoreHandle; +typedef void * SemaphoreHandle_t; +typedef void * xQueueHandle; +typedef void * QueueHandle_t; +typedef void * TaskHandle_t; +typedef void * esp_timer_handle_t; +typedef uint32_t TickType_t; +typedef uint32_t portTickType; + + +typedef enum { + WIFI_MODE_NULL = 0, /**< null mode */ + WIFI_MODE_STA, /**< WiFi station mode */ + WIFI_MODE_AP, /**< WiFi soft-AP mode */ + WIFI_MODE_APSTA, /**< WiFi station + soft-AP mode */ + WIFI_MODE_MAX +} wifi_mode_t; + +/* status of DHCP client or DHCP server */ +typedef enum { + TCPIP_ADAPTER_DHCP_INIT = 0, /**< DHCP client/server in initial state */ + TCPIP_ADAPTER_DHCP_STARTED, /**< DHCP client/server already been started */ + TCPIP_ADAPTER_DHCP_STOPPED, /**< DHCP client/server already been stopped */ + TCPIP_ADAPTER_DHCP_STATUS_MAX +} tcpip_adapter_dhcp_status_t; + +typedef enum { + SYSTEM_EVENT_WIFI_READY = 0, /**< ESP32 WiFi ready */ + SYSTEM_EVENT_SCAN_DONE, /**< ESP32 finish scanning AP */ + SYSTEM_EVENT_STA_START, /**< ESP32 station start */ + SYSTEM_EVENT_STA_STOP, /**< ESP32 station stop */ + SYSTEM_EVENT_STA_CONNECTED, /**< ESP32 station connected to AP */ + SYSTEM_EVENT_STA_DISCONNECTED, /**< ESP32 station disconnected from AP */ + SYSTEM_EVENT_STA_AUTHMODE_CHANGE, /**< the auth mode of AP connected by ESP32 station changed */ + SYSTEM_EVENT_STA_GOT_IP, /**< ESP32 station got IP from connected AP */ + SYSTEM_EVENT_STA_LOST_IP, /**< ESP32 station lost IP and the IP is reset to 0 */ + SYSTEM_EVENT_STA_WPS_ER_SUCCESS, /**< ESP32 station wps succeeds in enrollee mode */ + SYSTEM_EVENT_STA_WPS_ER_FAILED, /**< ESP32 station wps fails in enrollee mode */ + SYSTEM_EVENT_STA_WPS_ER_TIMEOUT, /**< ESP32 station wps timeout in enrollee mode */ + SYSTEM_EVENT_STA_WPS_ER_PIN, /**< ESP32 station wps pin code in enrollee mode */ + SYSTEM_EVENT_AP_START, /**< ESP32 soft-AP start */ + SYSTEM_EVENT_AP_STOP, /**< ESP32 soft-AP stop */ + SYSTEM_EVENT_AP_STACONNECTED, /**< a station connected to ESP32 soft-AP */ + SYSTEM_EVENT_AP_STADISCONNECTED, /**< a station disconnected from ESP32 soft-AP */ + SYSTEM_EVENT_AP_PROBEREQRECVED, /**< Receive probe request packet in soft-AP interface */ + SYSTEM_EVENT_GOT_IP6, /**< ESP32 station or ap or ethernet interface v6IP addr is preferred */ + SYSTEM_EVENT_ETH_START, /**< ESP32 ethernet start */ + SYSTEM_EVENT_ETH_STOP, /**< ESP32 ethernet stop */ + SYSTEM_EVENT_ETH_CONNECTED, /**< ESP32 ethernet phy link up */ + SYSTEM_EVENT_ETH_DISCONNECTED, /**< ESP32 ethernet phy link down */ + SYSTEM_EVENT_ETH_GOT_IP, /**< ESP32 ethernet got IP from connected AP */ + SYSTEM_EVENT_MAX +} system_event_id_t; + +struct udp_pcb { + uint8_t dummy; +}; + +struct ip4_addr { + uint32_t addr; +}; +typedef struct ip4_addr ip4_addr_t; + +struct ip6_addr { + uint32_t addr[4]; +}; +typedef struct ip6_addr ip6_addr_t; + +typedef struct _ip_addr { + union { + ip6_addr_t ip6; + ip4_addr_t ip4; + } u_addr; + uint8_t type; +} ip_addr_t; + +typedef struct { + ip4_addr_t ip; + ip4_addr_t netmask; + ip4_addr_t gw; +} tcpip_adapter_ip_info_t; + +// typedef int32_t system_event_id_t; +typedef enum { + TCPIP_ADAPTER_IF_STA = 0, /**< ESP32 station interface */ + TCPIP_ADAPTER_IF_AP, /**< ESP32 soft-AP interface */ + TCPIP_ADAPTER_IF_ETH, /**< ESP32 ethernet interface */ + TCPIP_ADAPTER_IF_MAX +} tcpip_adapter_if_t; + +typedef struct { + ip6_addr_t ip; +} tcpip_adapter_ip6_info_t; +typedef struct { + tcpip_adapter_if_t if_index; + tcpip_adapter_ip6_info_t ip6_info; +} system_event_got_ip6_t; +typedef union { + system_event_got_ip6_t got_ip6; /**< ESP32 station or ap or ethernet ipv6 addr state change to preferred */ +} system_event_info_t; + +typedef struct { + system_event_id_t event_id; /**< event ID */ + system_event_info_t event_info; /**< event information */ +} system_event_t; + +inline esp_err_t esp_wifi_get_mode(wifi_mode_t * mode) +{ + *mode = WIFI_MODE_APSTA; + return ESP_OK; +} + +struct pbuf { + struct pbuf *next; + void *payload; + uint16_t tot_len; + uint16_t len; + uint8_t /*pbuf_type*/ type; + uint8_t flags; + uint16_t ref; +}; + +#define IP_GET_TYPE(ipaddr) ((ipaddr)->type) +#define IP_IS_V6_VAL(ipaddr) (IP_GET_TYPE(&ipaddr) == IPADDR_TYPE_V6) +#define ip4_addr_copy(dest, src) ((dest).addr = (src).addr) +#define ip6_addr_copy(dest, src) do{(dest).addr[0] = (src).addr[0]; \ + (dest).addr[1] = (src).addr[1]; \ + (dest).addr[2] = (src).addr[2]; \ + (dest).addr[3] = (src).addr[3];}while(0) + +#define ip_addr_copy(dest, src) do{ IP_SET_TYPE_VAL(dest, IP_GET_TYPE(&src)); if(IP_IS_V6_VAL(src)){ \ + ip6_addr_copy(*ip_2_ip6(&(dest)), *ip_2_ip6(&(src))); }else{ \ + ip4_addr_copy(*ip_2_ip4(&(dest)), *ip_2_ip4(&(src))); }}while(0) + +#include "esp32_mock.h" + +uint32_t xTaskGetTickCount(); + + +#endif //MDNS_TEST_MODE + +#endif //_ESP32_COMPAT_H_ diff --git a/components/mdns/test_afl_fuzz_host/esp32_mock.c b/components/mdns/test_afl_fuzz_host/esp32_mock.c new file mode 100644 index 00000000..8cc986df --- /dev/null +++ b/components/mdns/test_afl_fuzz_host/esp32_mock.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include "esp32_compat.h" + +void* g_queue; +int g_queue_send_shall_fail = 0; +int g_size = 0; + +esp_err_t esp_timer_delete(esp_timer_handle_t timer) +{ + return ESP_OK; +} + +esp_err_t esp_timer_stop(esp_timer_handle_t timer) +{ + return ESP_OK; +} + +esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period) +{ + return ESP_OK; +} + +esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args, + esp_timer_handle_t* out_handle) +{ + return ESP_OK; +} + +uint32_t xTaskGetTickCount() +{ + struct timeval tv; + struct timezone tz; + if (gettimeofday(&tv, &tz) == 0) { + return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); + } + return 0; +} + +/// Queue mock + QueueHandle_t xQueueCreate( uint32_t uxQueueLength, uint32_t uxItemSize ) + { + g_size = uxItemSize; + g_queue = malloc((uxQueueLength)*(uxItemSize)); + return g_queue; + } + + +void vQueueDelete( QueueHandle_t xQueue ) +{ + free(xQueue); +} + +uint32_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait) +{ + if (g_queue_send_shall_fail) + { + return pdFALSE; + } + else + { + memcpy(xQueue, pvItemToQueue, g_size); + return pdPASS; + } +} + + +uint32_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait) +{ + return pdFALSE; +} + +void GetLastItem(void *pvBuffer) +{ + memcpy(pvBuffer, g_queue, g_size); +} + +void ForceTaskDelete() +{ + g_queue_send_shall_fail = 1; +} diff --git a/components/mdns/test_afl_fuzz_host/esp32_mock.h b/components/mdns/test_afl_fuzz_host/esp32_mock.h new file mode 100644 index 00000000..5ff5ad63 --- /dev/null +++ b/components/mdns/test_afl_fuzz_host/esp32_mock.h @@ -0,0 +1,45 @@ +#ifndef ESP32_MOCK_H_ +#define ESP32_MOCK_H_ + +typedef void (*esp_timer_cb_t)(void* arg); + +typedef enum +{ + ESP_TIMER_TASK, //!< Callback is called from timer task +} esp_timer_dispatch_t; + +typedef struct +{ + esp_timer_cb_t callback; //!< Function to call when timer expires + void* arg; //!< Argument to pass to the callback + esp_timer_dispatch_t dispatch_method; //!< Call the callback from task or from ISR + const char* name; //!< Timer name, used in esp_timer_dump function +} esp_timer_create_args_t; + +esp_err_t esp_timer_delete(esp_timer_handle_t timer); + +esp_err_t esp_timer_stop(esp_timer_handle_t timer); + +esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period); + +esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args, + esp_timer_handle_t* out_handle); + + +// Queue mock + QueueHandle_t xQueueCreate( uint32_t uxQueueLength, + uint32_t uxItemSize ); + +void vQueueDelete( QueueHandle_t xQueue ); + +uint32_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait); + +uint32_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait); + +void GetLastItem(void *pvBuffer); + +void ForceTaskDelete(); + +#define _mdns_udp_pcb_write(tcpip_if, ip_protocol, ip, port, data, len) len + +#endif /* ESP32_MOCK_H_ */ \ No newline at end of file diff --git a/components/mdns/test_afl_fuzz_host/in/test-14.bin b/components/mdns/test_afl_fuzz_host/in/test-14.bin new file mode 100644 index 0000000000000000000000000000000000000000..b9d059d8202682602e7232a8505c495a3047e3a4 GIT binary patch literal 568 zcmaiwPfNov7{)Vc-GqW7j)|wjsGcf~S~hRiF&W~a2k#+k0~WgWB}sAnMEe#aev*0f zYnYmKpg5L8@;*Gj_g@gw=M=T;r$cDQq2$scG66d;6PhFBpq5SEqy*Ki;|>9y6_W8k z(juaoD$gKwot!QVViQiwP4*$FL_!N=w2csr$^a@cZR*xs(X_FMHk)!f+~gb+NOM1> zk_yQgof*O+&`j$PkuwH9@N&MeNR@)Rs7LhfhXIZT<8b&C;fD#&geg za3+LG&Fd@xs>KLTNcn;-*5pXh{6Hl3Gf@0Bv?l!J7wTd;niT zpQFAGPvJB)ejMCnH#5JT{b&BO0M9^cBj1M?YNV6sL|5fVbt9Ej+lyF*S{proq+$Y_ zy(EZB<0$aQ0F|dTfW0^Pc%S>15?e0gf=P$9ZRU2E6gBl1yUeqg)y}c?t9peygs&#} z7|j&Zdeo=X8zO0VLkn$-((43yUL=X?$Ick}!j;^kSawken}R!yj@dNbnpQ!IPSfSw z6t(+@>poibXblSGtk>cz>tEHauqc}6a-MwrlizE$|MzI+T(JBX1>O+*nyq$)j zQylnX9d7k~Ow=qiw8BP1a9+~%qG`nQ)xD8Uv1!|NXD5mA#_QH@&$~uSCEPYe#h+j? zazWa{-WhziU)BS1Dn2W)3#)mrXUl8}NBQ(&0aZF-|Mg&HNV7tR6XMi3VDD9S7@$yYB{2+7P%Rd7x$C@#%O<%mzrEGoz^Dq)E)NiJZ`$xlwq zVPFtw0BRAO!mwg_d7`hQt+|1&xv{RZldhAAuCt}Cld(<_s<4x;fr+lGIY`(DNi{^+ z0x0a4YnzvpS*%-zV0U=oA+vm)V+|Sr{1T8R{fu1*g~=8yw(a(1*I26Ub&|U}#`) F005APLjnK* literal 0 HcmV?d00001 diff --git a/components/mdns/test_afl_fuzz_host/in/test-28.bin b/components/mdns/test_afl_fuzz_host/in/test-28.bin new file mode 100644 index 0000000000000000000000000000000000000000..537352ae67db92f820ff0d1299dae3534eef1f48 GIT binary patch literal 62 zcmZQz00Kr3!N4HuQIuI+lCNH@5R#djs^FYhP+Xdm${nAWR*+woT2zu;z!DE6Sab4| L6LT0C6dM=;y^9dW literal 0 HcmV?d00001 diff --git a/components/mdns/test_afl_fuzz_host/in/test-29.bin b/components/mdns/test_afl_fuzz_host/in/test-29.bin new file mode 100644 index 0000000000000000000000000000000000000000..837737b32e12e3f33ff3f1c3b59f6ff351b05fba GIT binary patch literal 39 qcmZQz00Jfu!N9XCLm_y%+1WpOe@MPsbbB^PfpBXV328G1WHse2>fdR(m)`h7xI6{k-wY= bco-O=ax4c{{1X83^`U$YAe+U3fq?@6q#+cG literal 0 HcmV?d00001 diff --git a/components/mdns/test_afl_fuzz_host/in/test-53.bin b/components/mdns/test_afl_fuzz_host/in/test-53.bin new file mode 100644 index 0000000000000000000000000000000000000000..73181ea8c378d33efed725aa209387113edffdd3 GIT binary patch literal 140 zcmZQz00Jf;1Ok@$;@l*b_>$xT)|~w0#2f|&9tKA7;QX}A%0ykyvc$anvczIt-^666 z{QT?#VhoH7j0bo?Y8V)}zcR>5K~yS0RX%E%S*!q4%mY{WjX^@xqbReuBwxK)AtW<5 PRlzy2ptv+A^#Bh5gdisG literal 0 HcmV?d00001 diff --git a/components/mdns/test_afl_fuzz_host/in/test-56.bin b/components/mdns/test_afl_fuzz_host/in/test-56.bin new file mode 100644 index 0000000000000000000000000000000000000000..980b6516a20908c4b681b1cf3717c7ef35f062b6 GIT binary patch literal 262 zcmZQzXkh>XCLm@L_9)6MF3Eq?Ftb>}H!)cuH#0AjCq5;$EHgP(H#09SpC!H|xqvk% zKRGdnfkA+Qk%2*Q3WJP5ZhlH?jxA6JNQ0S<5m$b3MOkW5v8|yQOMG!|(g9rt9;jk5 zY<3)oKTyCR&;T)xkpak21aTND7=%D>W8BLLGB~X$v!v>P(SfB5GEiB8e+?jc1_lwm xkpDZ5{N)77F+$~74y^bm05nM-YAh#^&C0;gz~BH>1QrAu&jDn!I5049006}!N5=pF literal 0 HcmV?d00001 diff --git a/components/mdns/test_afl_fuzz_host/in/test-63.bin b/components/mdns/test_afl_fuzz_host/in/test-63.bin new file mode 100644 index 0000000000000000000000000000000000000000..883d444ba269c8938aa87f3ba8da53a1305f9763 GIT binary patch literal 147 zcmZQz00Jf;1Oo2(#I%C^vecrIAisgkVmtU4x ztm~VY?3ACMeL$9hk%9354@eEr1V;v0DTqo1sLDqTGm8~qig}O}N{D(CWfqs@s~0PT SWag$SI42eqm*%7%-~j;3q9}|2 literal 0 HcmV?d00001 diff --git a/components/mdns/test_afl_fuzz_host/in/test-83.bin b/components/mdns/test_afl_fuzz_host/in/test-83.bin new file mode 100644 index 0000000000000000000000000000000000000000..c6cc159a2597bc35b4316a796af18534bfee7dc3 GIT binary patch literal 105 zcmZQzXkh>XMj&Pq56(}^tW4DPEKAJGFH0=e^-WB6%FoYc&B;$r%mFHD04b|rU^%ej opU43o1{o+{;9mn!90-_XHvj*(sc11!P9G}A0c5i{Ffecc09-p6=Kufz literal 0 HcmV?d00001 diff --git a/components/mdns/test_afl_fuzz_host/in/test-88.bin b/components/mdns/test_afl_fuzz_host/in/test-88.bin new file mode 100644 index 0000000000000000000000000000000000000000..fcbb384438a28961526215b8b66e32193b6fd0e8 GIT binary patch literal 48 xcmZQz00Jfu!N9;0UzC=_5?_*Bz?zevoS4JFz{9}E5ucb@RFGd(azGHo1pvO@39SGC literal 0 HcmV?d00001 diff --git a/components/mdns/test_afl_fuzz_host/in/test-89.bin b/components/mdns/test_afl_fuzz_host/in/test-89.bin new file mode 100644 index 0000000000000000000000000000000000000000..d60a987cad92fe5e9e86e83ea8d2ee16f7fe93e2 GIT binary patch literal 459 zcmZut!AiqG6nsjH6?&*>t%!$uQZQS0laj>4LYts?QG|-ulBSVH(x&XjT5mmg=~3_> ztY`gzT*ZHizSvY0e6Z}id9$-S3-AC~%eYnwZO0eGP(-mV!P{&eAgrX;5v-UP|9p7oBgos1w8f0EOdo2ePiw} zNje}4^KDp~;Po(uTs%uyd;q1i$LGwR@0_zHHz+YQVq3(j6Z?o*nj-$v7NK==Y^1dM zOFgGeq|f_&(D5aSJQ-nY_c&vUuzMxFc@pCZ#o|bZ%N*uF-f3J6VQOGQliZLVkQEZ||>|XE&P_Qhxn0o}&II^Gj0M Z$Nq4kL7Zy<6!6?PhySpOI1ey^#uu&jeZK$z literal 0 HcmV?d00001 diff --git a/components/mdns/test_afl_fuzz_host/in/test-95.bin b/components/mdns/test_afl_fuzz_host/in/test-95.bin new file mode 100644 index 0000000000000000000000000000000000000000..26553090fa6d7cddb5fb7c748fc72d8d20c77364 GIT binary patch literal 286 zcmZQz00L$PHW0-fpO{vVUzS=_l3c(N4XCLm@O^(e|LF3DFfRtU+=O;vDCEGRC`N#%)8NiEAvPSwrKOUq}8FG((7 z&B;$r%wb>1yv^6)dGB>tzcCvCZv2wPwax&IQObK(dHK;H&FfedS$u_cu z^DV$~ezs8iK?*}{O-<7hlaefwbQ8^tlXOi@Obm4`EzC`H%~Dg6Obtv@lZ*`#fi^3G pY-XrnkOhSV1E&Zi7>ad4{?UaRc)$dxP#@| +#include +#include +#include +#include + +#include "mdns.h" +#include "mdns_private.h" + +// +// Global stuctures containing packet payload, search +mdns_rx_packet_t g_packet; +struct pbuf mypbuf; +mdns_search_once_t * search = NULL; + +// +// Dependency injected test functions +void mdns_test_execute_action(void * action); +mdns_srv_item_t * mdns_test_mdns_get_service_item(const char * service, const char * proto); +mdns_search_once_t * mdns_test_search_init(const char * name, const char * service, const char * proto, uint16_t type, uint32_t timeout, uint8_t max_results); +esp_err_t mdns_test_send_search_action(mdns_action_type_t type, mdns_search_once_t * search); +void mdns_test_search_free(mdns_search_once_t * search); +void mdns_test_init_di(); + +// +// mdns function wrappers for mdns setup in test mode +static int mdns_test_hostname_set(const char * mdns_hostname) +{ + int ret = mdns_hostname_set(mdns_hostname); + mdns_action_t * a = NULL; + GetLastItem(&a); + mdns_test_execute_action(a); + return ret; +} + +static int mdns_test_service_instance_name_set(const char * service, const char * proto, const char * instance) +{ + int ret = mdns_service_instance_name_set(service, proto, instance); + mdns_action_t * a = NULL; + GetLastItem(&a); + mdns_test_execute_action(a); + return ret; +} + +static int mdns_test_service_txt_set(const char * service, const char * proto, uint8_t num_items, mdns_txt_item_t txt[]) +{ + int ret = mdns_service_txt_set(service, proto, txt, num_items); + mdns_action_t * a = NULL; + GetLastItem(&a); + mdns_test_execute_action(a); + return ret; +} + +static int mdns_test_service_add(const char * service_name, const char * proto, uint32_t port) +{ + if (mdns_service_add(NULL, service_name, proto, port, NULL, 0)) { + // This is expected failure as the service thread is not running + } + mdns_action_t * a = NULL; + GetLastItem(&a); + mdns_test_execute_action(a); + + if (mdns_test_mdns_get_service_item(service_name, proto)==NULL) { + return ESP_FAIL; + } + return ESP_OK; +} + +static mdns_result_t* mdns_test_query(const char * service_name, const char * proto) +{ + search = mdns_test_search_init(NULL, service_name, proto, MDNS_TYPE_PTR, 3000, 20); + if (!search) { + abort(); + } + + if (mdns_test_send_search_action(ACTION_SEARCH_ADD, search)) { + mdns_test_search_free(search); + abort(); + } + + mdns_action_t * a = NULL; + GetLastItem(&a); + mdns_test_execute_action(a); + return NULL; +} + +static void mdns_test_query_free() +{ + mdns_test_search_free(search); +} + +// +// function "under test" where afl-mangled packets passed +// +void mdns_parse_packet(mdns_rx_packet_t * packet); + +// +// Test starts here +// +int main(int argc, char** argv) +{ + int i; + const char * mdns_hostname = "minifritz"; + const char * mdns_instance = "Hristo's Time Capsule"; + mdns_txt_item_t arduTxtData[4] = { + {"board","esp32"}, + {"tcp_check","no"}, + {"ssh_upload","no"}, + {"auth_upload","no"} + }; + + const uint8_t mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x32}; + + uint8_t buf[1460]; + char winstance[21+strlen(mdns_hostname)]; + + sprintf(winstance, "%s [%02x:%02x:%02x:%02x:%02x:%02x]", mdns_hostname, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + // Init depencency injected methods + mdns_test_init_di(); + + if (mdns_init()) { + abort(); + } + + if (mdns_test_hostname_set(mdns_hostname)) { + abort(); + } + + if (mdns_test_service_add("_workstation", "_tcp", 9)) { + abort(); + } + if (mdns_test_service_instance_name_set("_workstation", "_tcp", winstance)) { + abort(); + } + + if (mdns_test_service_add("_arduino", "_tcp", 3232)) { + abort(); + } + + if (mdns_test_service_txt_set("_arduino", "_tcp", 4, arduTxtData)) { + abort(); + } + + if (mdns_test_service_add("_http", "_tcp", 80)) { + abort(); + } + + if (mdns_test_service_instance_name_set("_http", "_tcp", "ESP WebServer")) { + abort(); + } + + if ( + mdns_test_service_add("_afpovertcp", "_tcp", 548) + || mdns_test_service_add("_rfb", "_tcp", 885) + || mdns_test_service_add("_smb", "_tcp", 885) + || mdns_test_service_add("_adisk", "_tcp", 885) + || mdns_test_service_add("_airport", "_tcp", 885) + || mdns_test_service_add("_printer", "_tcp", 885) + || mdns_test_service_add("_airplay", "_tcp", 885) + || mdns_test_service_add("_raop", "_tcp", 885) + || mdns_test_service_add("_uscan", "_tcp", 885) + || mdns_test_service_add("_uscans", "_tcp", 885) + || mdns_test_service_add("_ippusb", "_tcp", 885) + || mdns_test_service_add("_scanner", "_tcp", 885) + || mdns_test_service_add("_ipp", "_tcp", 885) + || mdns_test_service_add("_ipps", "_tcp", 885) + || mdns_test_service_add("_pdl-datastream", "_tcp", 885) + || mdns_test_service_add("_ptp", "_tcp", 885) + || mdns_test_service_add("_sleep-proxy", "_udp", 885)) + { + abort(); + } + + mdns_result_t * results = NULL; + FILE *file; + size_t nread; + +#ifdef INSTR_IS_OFF + size_t len = 1460; + memset(buf, 0, 1460); + + if (argc != 2) + { + printf("Non-instrumentation mode: please supply a file name created by AFL to reproduce crash\n"); + return 1; + } + else + { + // + // Note: parameter1 is a file (mangled packet) which caused the crash + file = fopen(argv[1], "r"); + if (file) { + len = fread(buf, 1, 1460, file); + } + fclose(file); + } + + for (i=0; i<1; i++) { +#else + while (__AFL_LOOP(1000)) { + memset(buf, 0, 1460); + size_t len = read(0, buf, 1460); +#endif + mypbuf.payload = buf; + mypbuf.len = len; + g_packet.pb = &mypbuf; + mdns_test_query("_afpovertcp", "_tcp"); + mdns_parse_packet(&g_packet); + } + ForceTaskDelete(); + mdns_free(); + return 0; +} From 90b1d4e1bd269de7fbce2896fd9f6fac9596d56f Mon Sep 17 00:00:00 2001 From: Liu Han Date: Tue, 28 Aug 2018 11:29:50 +0800 Subject: [PATCH 2/5] feat(example): Add mDNS example --- examples/protocols/mdns/Makefile | 9 + examples/protocols/mdns/README.md | 21 ++ .../protocols/mdns/main/Kconfig.projbuild | 29 +++ examples/protocols/mdns/main/component.mk | 4 + .../protocols/mdns/main/mdns_example_main.c | 244 ++++++++++++++++++ 5 files changed, 307 insertions(+) create mode 100644 examples/protocols/mdns/Makefile create mode 100644 examples/protocols/mdns/README.md create mode 100644 examples/protocols/mdns/main/Kconfig.projbuild create mode 100644 examples/protocols/mdns/main/component.mk create mode 100755 examples/protocols/mdns/main/mdns_example_main.c diff --git a/examples/protocols/mdns/Makefile b/examples/protocols/mdns/Makefile new file mode 100644 index 00000000..0353c51c --- /dev/null +++ b/examples/protocols/mdns/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := mdns-test + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/mdns/README.md b/examples/protocols/mdns/README.md new file mode 100644 index 00000000..8cc56d83 --- /dev/null +++ b/examples/protocols/mdns/README.md @@ -0,0 +1,21 @@ +# mDNS example + +Shows how to use mDNS to advertise lookup services and hosts + +## Example workflow + +- mDNS is initialized with host name and instance name defined through `make menuconfig` and `_http._tcp` service is added to be advertised +- WiFi STA is started and trying to connect to the access point defined through `make menuconfig` +- The system event handler is used to pass the network events to mDNS so the service is aware when the interface comes up or down +- GPIO0 (BOOT Button) is initialized as pulled-up input that can be monitored for button press +- Example task is started to check if the button is pressed so it can execute the mDNS queries defined + +## Running the example + +- Run `make menuconfig` to configure the access point's SSID and Password and the default device mDNS host name and instance name +- Run `make flash monitor` to build and upload the example to your board and connect to it's serial terminal +- Wait for WiFi to connec to your access point +- You can now ping the device at `[hostname].local` and browse for `_http._tcp` on the same network to find the advertised service +- Pressing the BOOT button will start quring the local network for the predefined in `check_button` hosts and services + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/mdns/main/Kconfig.projbuild b/examples/protocols/mdns/main/Kconfig.projbuild new file mode 100644 index 00000000..3122e030 --- /dev/null +++ b/examples/protocols/mdns/main/Kconfig.projbuild @@ -0,0 +1,29 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "myssid" + help + WiFi password (WPA or WPA2) for the example to use. + + Can be left blank if the network has no security set. + +config MDNS_HOSTNAME + string "mDNS Hostname" + default "esp32-mdns" + help + mDNS Hostname for example to use + +config MDNS_INSTANCE + string "mDNS Instance Name" + default "ESP32 with mDNS" + help + mDNS Instance Name for example to use + +endmenu \ No newline at end of file diff --git a/examples/protocols/mdns/main/component.mk b/examples/protocols/mdns/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/examples/protocols/mdns/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/protocols/mdns/main/mdns_example_main.c b/examples/protocols/mdns/main/mdns_example_main.c new file mode 100755 index 00000000..1cfcf3e2 --- /dev/null +++ b/examples/protocols/mdns/main/mdns_example_main.c @@ -0,0 +1,244 @@ +/* MDNS-SD Query and advertise Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "mdns.h" +#include "driver/gpio.h" +#include +#include + +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +#define EXAMPLE_MDNS_HOSTNAME CONFIG_MDNS_HOSTNAME +#define EXAMPLE_MDNS_INSTANCE CONFIG_MDNS_INSTANCE + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int IP4_CONNECTED_BIT = BIT0; +const int IP6_CONNECTED_BIT = BIT1; + +static const char *TAG = "mdns-test"; +static bool auto_reconnect = true; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_CONNECTED: + /* enable ipv6 */ + tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, IP4_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STA_GOT_IP6: + xEventGroupSetBits(wifi_event_group, IP6_CONNECTED_BIT); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently + auto-reassociate. */ + if (auto_reconnect) { + esp_wifi_connect(); + } + xEventGroupClearBits(wifi_event_group, IP4_CONNECTED_BIT | IP6_CONNECTED_BIT); + break; + default: + break; + } + mdns_handle_system_event(ctx, event); + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +static void initialise_mdns(void) +{ + //initialize mDNS + ESP_ERROR_CHECK( mdns_init() ); + //set mDNS hostname (required if you want to advertise services) + ESP_ERROR_CHECK( mdns_hostname_set(EXAMPLE_MDNS_HOSTNAME) ); + //set default mDNS instance name + ESP_ERROR_CHECK( mdns_instance_name_set(EXAMPLE_MDNS_INSTANCE) ); + + //structure with TXT records + mdns_txt_item_t serviceTxtData[3] = { + {"board","esp32"}, + {"u","user"}, + {"p","password"} + }; + + //initialize service + ESP_ERROR_CHECK( mdns_service_add("ESP32-WebServer", "_http", "_tcp", 80, serviceTxtData, 3) ); + //add another TXT item + ESP_ERROR_CHECK( mdns_service_txt_item_set("_http", "_tcp", "path", "/foobar") ); + //change TXT item value + ESP_ERROR_CHECK( mdns_service_txt_item_set("_http", "_tcp", "u", "admin") ); +} + +static const char * if_str[] = {"STA", "AP", "ETH", "MAX"}; +static const char * ip_protocol_str[] = {"V4", "V6", "MAX"}; + +static void mdns_print_results(mdns_result_t * results){ + mdns_result_t * r = results; + mdns_ip_addr_t * a = NULL; + int i = 1, t; + while(r){ + printf("%d: Interface: %s, Type: %s\n", i++, if_str[r->tcpip_if], ip_protocol_str[r->ip_protocol]); + if(r->instance_name){ + printf(" PTR : %s\n", r->instance_name); + } + if(r->hostname){ + printf(" SRV : %s.local:%u\n", r->hostname, r->port); + } + if(r->txt_count){ + printf(" TXT : [%u] ", r->txt_count); + for(t=0; ttxt_count; t++){ + printf("%s=%s; ", r->txt[t].key, r->txt[t].value); + } + printf("\n"); + } + a = r->addr; + while(a){ + if(a->addr.type == MDNS_IP_PROTOCOL_V6){ + printf(" AAAA: " IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6)); + } else { + printf(" A : " IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4))); + } + a = a->next; + } + r = r->next; + } + +} + +static void query_mdns_service(const char * service_name, const char * proto) +{ + ESP_LOGI(TAG, "Query PTR: %s.%s.local", service_name, proto); + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query_ptr(service_name, proto, 3000, 20, &results); + if(err){ + ESP_LOGE(TAG, "Query Failed"); + return; + } + if(!results){ + ESP_LOGW(TAG, "No results found!"); + return; + } + + mdns_print_results(results); + mdns_query_results_free(results); +} + +static void query_mdns_host(const char * host_name) +{ + ESP_LOGI(TAG, "Query A: %s.local", host_name); + + struct ip4_addr addr; + addr.addr = 0; + + esp_err_t err = mdns_query_a(host_name, 2000, &addr); + if(err){ + if(err == ESP_ERR_NOT_FOUND){ + ESP_LOGW(TAG, "Host was not found!"); + return; + } + ESP_LOGE(TAG, "Query Failed"); + return; + } + + ESP_LOGI(TAG, IPSTR, IP2STR(&addr)); +} + +static void initialise_button(void) +{ + gpio_config_t io_conf; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.pin_bit_mask = 1; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + io_conf.pull_down_en = 0; + gpio_config(&io_conf); +} + +static void check_button(void) +{ + static bool old_level = true; + bool new_level = gpio_get_level(GPIO_NUM_0); + if (!new_level && old_level) { + query_mdns_host("esp32"); + query_mdns_service("_arduino", "_tcp"); + query_mdns_service("_http", "_tcp"); + query_mdns_service("_printer", "_tcp"); + query_mdns_service("_ipp", "_tcp"); + query_mdns_service("_afpovertcp", "_tcp"); + query_mdns_service("_smb", "_tcp"); + query_mdns_service("_ftp", "_tcp"); + query_mdns_service("_nfs", "_tcp"); + } + old_level = new_level; +} + +static void mdns_example_task(void *pvParameters) +{ + /* Wait for the callback to set the CONNECTED_BIT in the event group. */ + xEventGroupWaitBits(wifi_event_group, IP4_CONNECTED_BIT | IP6_CONNECTED_BIT, + false, true, portMAX_DELAY); + while(1) { + check_button(); + vTaskDelay(50 / portTICK_PERIOD_MS); + } +} + +void app_main() +{ + ESP_ERROR_CHECK( nvs_flash_init() ); + initialise_mdns(); + initialise_wifi(); + initialise_button(); + xTaskCreate(&mdns_example_task, "mdns_example_task", 2048, NULL, 5, NULL); +} From dcbbed9c27b86bbddbfd951eb5b934d11bdf9dca Mon Sep 17 00:00:00 2001 From: Liu Han Date: Wed, 26 Sep 2018 10:55:23 +0800 Subject: [PATCH 3/5] feat(mdns): Sync mDNS function from ESP-IDF 607d899503afd4ce0e457c815249a0213083f9f5 --- components/mdns/CMakeLists.txt | 9 + components/mdns/Kconfig | 13 + components/mdns/mdns.c | 63 +++-- components/mdns/mdns_networking.c | 225 ++++++++++-------- .../mdns/private_include/mdns_private.h | 6 +- .../mdns/test_afl_fuzz_host/esp32_compat.h | 1 + 6 files changed, 194 insertions(+), 123 deletions(-) create mode 100644 components/mdns/CMakeLists.txt create mode 100644 components/mdns/Kconfig diff --git a/components/mdns/CMakeLists.txt b/components/mdns/CMakeLists.txt new file mode 100644 index 00000000..0933e300 --- /dev/null +++ b/components/mdns/CMakeLists.txt @@ -0,0 +1,9 @@ +set(COMPONENT_SRCS "mdns.c" + "mdns_console.c" + "mdns_networking.c") +set(COMPONENT_ADD_INCLUDEDIRS "include") +set(COMPONENT_PRIV_INCLUDEDIRS "private_include") +set(COMPONENT_REQUIRES lwip mbedtls console tcpip_adapter) + +register_component() + diff --git a/components/mdns/Kconfig b/components/mdns/Kconfig new file mode 100644 index 00000000..321b0f65 --- /dev/null +++ b/components/mdns/Kconfig @@ -0,0 +1,13 @@ +menu "mDNS" + +config MDNS_MAX_SERVICES + int "Max number of services" + range 1 64 + default 10 + help + Services take up a certain amount of memory, and allowing fewer + services to be open at the same time conserves memory. Specify + the maximum amount of services here. The valid value is from 1 + to 64. + +endmenu diff --git a/components/mdns/mdns.c b/components/mdns/mdns.c index 0cc5865a..38d67826 100644 --- a/components/mdns/mdns.c +++ b/components/mdns/mdns.c @@ -16,7 +16,6 @@ #include "mdns_private.h" #include "mdns_networking.h" #include -#include #ifdef MDNS_ENABLE_DEBUG void mdns_debug_packet(const uint8_t * data, size_t len); @@ -97,6 +96,21 @@ static mdns_srv_item_t * _mdns_get_service_item(const char * service, const char return NULL; } +static bool _mdns_can_add_more_services(void) +{ + mdns_srv_item_t * s = _mdns_server->services; + uint16_t service_num = 0; + while (s) { + service_num ++; + s = s->next; + if (service_num >= MDNS_MAX_SERVICES) { + return false; + } + } + + return true; +} + esp_err_t _mdns_send_rx_action(mdns_rx_packet_t * packet) { mdns_action_t * action = NULL; @@ -1181,7 +1195,7 @@ static void _mdns_create_answer_from_parsed_packet(mdns_parsed_packet_t * parsed } if (service) { if (q->type == MDNS_TYPE_PTR || q->type == MDNS_TYPE_ANY) { - if (q->type == MDNS_TYPE_PTR) { + if (q->type == MDNS_TYPE_PTR || !parsed_packet->probe) { shared = true; } if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, service->service, false, false) @@ -1405,6 +1419,7 @@ static void _mdns_pcb_send_bye(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t i if (!packet) { return; } + packet->flags = MDNS_FLAGS_AUTHORITATIVE; size_t i; for (i=0; ianswers, MDNS_TYPE_PTR, services[i]->service, true, true)) { @@ -1741,6 +1756,17 @@ static mdns_txt_linked_item_t * _mdns_allocate_txt(size_t num_items, mdns_txt_it } return new_txt; } +static void _mdns_free_linked_txt(mdns_txt_linked_item_t *txt) +{ + mdns_txt_linked_item_t *t; + while (txt) { + t = txt; + txt = txt->next; + free((char *)t->value); + free((char *)t->key); + free(t); + } +} /** * @brief creates/allocates new service @@ -2555,7 +2581,7 @@ void mdns_parse_packet(mdns_rx_packet_t * packet) continue; } - if (type == MDNS_TYPE_ANY) { + if (type == MDNS_TYPE_ANY && !_str_null_or_empty(name->host)) { parsed_packet->probe = true; } @@ -3539,7 +3565,7 @@ static void _mdns_free_action(mdns_action_t * action) free(action->data.srv_instance.instance); break; case ACTION_SERVICE_TXT_REPLACE: - free(action->data.srv_txt_replace.txt); + _mdns_free_linked_txt(action->data.srv_txt_replace.txt); break; case ACTION_SERVICE_TXT_SET: free(action->data.srv_txt_set.key); @@ -3621,14 +3647,8 @@ static void _mdns_execute_action(mdns_action_t * action) service = action->data.srv_txt_replace.service->service; txt = service->txt; service->txt = NULL; - while (txt) { - t = txt; - txt = txt->next; - free((char *)t->value); - free((char *)t->key); - free(t); - } - service->txt = _mdns_allocate_txt(action->data.srv_txt_replace.num_items, action->data.srv_txt_replace.txt); + _mdns_free_linked_txt(txt); + service->txt = action->data.srv_txt_replace.txt; _mdns_announce_all_pcbs(&action->data.srv_txt_replace.service, 1, false); break; @@ -4125,6 +4145,11 @@ esp_err_t mdns_service_add(const char * instance, const char * service, const ch if (!_mdns_server || _str_null_or_empty(service) || _str_null_or_empty(proto) || !port) { return ESP_ERR_INVALID_ARG; } + + if (!_mdns_can_add_more_services()) { + return ESP_ERR_NO_MEM; + } + mdns_srv_item_t * item = _mdns_get_service_item(service, proto); if (item) { return ESP_ERR_INVALID_ARG; @@ -4204,27 +4229,25 @@ esp_err_t mdns_service_txt_set(const char * service, const char * proto, mdns_tx return ESP_ERR_NOT_FOUND; } - mdns_txt_item_t * txt_copy = NULL; + mdns_txt_linked_item_t * new_txt = NULL; if (num_items){ - txt_copy = (mdns_txt_item_t *)malloc(num_items * sizeof(mdns_txt_item_t)); - if (!txt_copy) { + new_txt = _mdns_allocate_txt(num_items, txt); + if (!new_txt) { return ESP_ERR_NO_MEM; } - memcpy(txt_copy, txt, num_items * sizeof(mdns_txt_item_t)); } mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); if (!action) { - free(txt_copy); + _mdns_free_linked_txt(new_txt); return ESP_ERR_NO_MEM; } action->type = ACTION_SERVICE_TXT_REPLACE; action->data.srv_txt_replace.service = s; - action->data.srv_txt_replace.num_items = num_items; - action->data.srv_txt_replace.txt = txt_copy; + action->data.srv_txt_replace.txt = new_txt; if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { - free(txt_copy); + _mdns_free_linked_txt(new_txt); free(action); return ESP_ERR_NO_MEM; } diff --git a/components/mdns/mdns_networking.c b/components/mdns/mdns_networking.c index e4ab816d..4e64e36c 100644 --- a/components/mdns/mdns_networking.c +++ b/components/mdns/mdns_networking.c @@ -14,6 +14,89 @@ extern mdns_server_t * _mdns_server; * */ +static struct udp_pcb * _pcb_main = NULL; + +static void _udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip_addr_t *raddr, uint16_t rport); + +/** + * @brief Low level UDP PCB Initialize + */ +static esp_err_t _udp_pcb_main_init() +{ + if(_pcb_main) { + return ESP_OK; + } + _pcb_main = udp_new(); + if (!_pcb_main) { + return ESP_ERR_NO_MEM; + } + if (udp_bind(_pcb_main, IP_ANY_TYPE, MDNS_SERVICE_PORT) != 0) { + udp_remove(_pcb_main); + _pcb_main = NULL; + return ESP_ERR_INVALID_STATE; + } + _pcb_main->mcast_ttl = 1; + _pcb_main->remote_port = MDNS_SERVICE_PORT; + ip_addr_copy(_pcb_main->remote_ip, ip_addr_any_type); + udp_recv(_pcb_main, &_udp_recv, _mdns_server); + return ESP_OK; +} + +/** + * @brief Low level UDP PCB Free + */ +static void _udp_pcb_main_deinit() +{ + if(_pcb_main){ + udp_recv(_pcb_main, NULL, NULL); + udp_disconnect(_pcb_main); + udp_remove(_pcb_main); + _pcb_main = NULL; + } +} + +/** + * @brief Low level UDP Multicast membership control + */ +static esp_err_t _udp_join_group(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, bool join) +{ + struct netif * netif = NULL; + void * nif = NULL; + esp_err_t err = tcpip_adapter_get_netif(tcpip_if, &nif); + if (err) { + return ESP_ERR_INVALID_ARG; + } + netif = (struct netif *)nif; + + if (ip_protocol == MDNS_IP_PROTOCOL_V4) { + ip_addr_t multicast_addr; + IP_ADDR4(&multicast_addr, 224, 0, 0, 251); + + if(join){ + if (igmp_joingroup_netif(netif, (const struct ip4_addr *)&multicast_addr.u_addr.ip4)) { + return ESP_ERR_INVALID_STATE; + } + } else { + if (igmp_leavegroup_netif(netif, (const struct ip4_addr *)&multicast_addr.u_addr.ip4)) { + return ESP_ERR_INVALID_STATE; + } + } + } else { + ip_addr_t multicast_addr = IPADDR6_INIT(0x000002ff, 0, 0, 0xfb000000); + + if(join){ + if (mld6_joingroup_netif(netif, &(multicast_addr.u_addr.ip6))) { + return ESP_ERR_INVALID_STATE; + } + } else { + if (mld6_leavegroup_netif(netif, &(multicast_addr.u_addr.ip6))) { + return ESP_ERR_INVALID_STATE; + } + } + } + return ESP_OK; +} + /** * @brief the receive callback of the raw udp api. Packets are received here * @@ -82,6 +165,21 @@ static void _udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip } +/** + * @brief Check if any of the interfaces is up + */ +static bool _udp_pcb_is_in_use(){ + int i, p; + for (i=0; iinterfaces[i].pcbs[p].pcb){ + return true; + } + } + } + return false; +} + /** * @brief Stop PCB Main code */ @@ -92,122 +190,47 @@ static void _udp_pcb_deinit(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_p } mdns_pcb_t * _pcb = &_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol]; if (_pcb->pcb) { - _pcb->state = PCB_OFF; - udp_recv(_pcb->pcb, NULL, NULL); - udp_disconnect(_pcb->pcb); - udp_remove(_pcb->pcb); free(_pcb->probe_services); + _pcb->state = PCB_OFF; _pcb->pcb = NULL; _pcb->probe_ip = false; _pcb->probe_services = NULL; _pcb->probe_services_len = 0; _pcb->probe_running = false; _pcb->failed_probes = 0; + _udp_join_group(tcpip_if, ip_protocol, false); + if(!_udp_pcb_is_in_use()) { + _udp_pcb_main_deinit(); + } } } -/** - * @brief Start PCB V4 - */ -static esp_err_t _udp_pcb_v4_init(tcpip_adapter_if_t tcpip_if) -{ - tcpip_adapter_ip_info_t if_ip_info; - - if (!_mdns_server || _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb) { - return ESP_ERR_INVALID_STATE; - } - - if (tcpip_adapter_get_ip_info(tcpip_if, &if_ip_info) || if_ip_info.ip.addr == 0) { - return ESP_ERR_INVALID_ARG; - } - - ip_addr_t interface_addr = IPADDR4_INIT(if_ip_info.ip.addr); - - ip_addr_t multicast_addr; - IP_ADDR4(&multicast_addr, 224, 0, 0, 251); - - if (igmp_joingroup((const struct ip4_addr *)&interface_addr.u_addr.ip4, (const struct ip4_addr *)&multicast_addr.u_addr.ip4)) { - return ESP_ERR_INVALID_STATE; - } - - struct udp_pcb * pcb = udp_new_ip_type(IPADDR_TYPE_V4); - if (!pcb) { - return ESP_ERR_NO_MEM; - } - - if (udp_bind(pcb, &interface_addr, MDNS_SERVICE_PORT) != 0) { - udp_remove(pcb); - return ESP_ERR_INVALID_STATE; - } - - pcb->mcast_ttl = 1; - pcb->remote_port = MDNS_SERVICE_PORT; - ip_addr_copy(pcb->multicast_ip, interface_addr); - ip_addr_copy(pcb->remote_ip, multicast_addr); - - _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb = pcb; - _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].failed_probes = 0; - udp_recv(pcb, &_udp_recv, _mdns_server); - - return ESP_OK; -} - -/** - * @brief Start PCB V6 - */ -static esp_err_t _udp_pcb_v6_init(tcpip_adapter_if_t tcpip_if) -{ - ip_addr_t multicast_addr = IPADDR6_INIT(0x000002ff, 0, 0, 0xfb000000); - ip_addr_t interface_addr; - interface_addr.type = IPADDR_TYPE_V6; - - if (!_mdns_server || _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb) { - return ESP_ERR_INVALID_STATE; - } - - if (tcpip_adapter_get_ip6_linklocal(tcpip_if, &interface_addr.u_addr.ip6)) { - return ESP_ERR_INVALID_ARG; - } - - if (mld6_joingroup(&(interface_addr.u_addr.ip6), &(multicast_addr.u_addr.ip6))) { - return ESP_ERR_INVALID_STATE; - } - - struct udp_pcb * pcb = udp_new_ip_type(IPADDR_TYPE_V6); - if (!pcb) { - return ESP_ERR_NO_MEM; - } - - if (udp_bind(pcb, &interface_addr, MDNS_SERVICE_PORT) != 0) { - udp_remove(pcb); - return ESP_ERR_INVALID_STATE; - } - - pcb->remote_port = MDNS_SERVICE_PORT; - ip_addr_copy(pcb->remote_ip, multicast_addr); - - _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb = pcb; - _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].failed_probes = 0; - udp_recv(pcb, &_udp_recv, _mdns_server); - - return ESP_OK; -} - /** * @brief Start PCB Main code */ static esp_err_t _udp_pcb_init(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) { - if (ip_protocol == MDNS_IP_PROTOCOL_V4) { - return _udp_pcb_v4_init(tcpip_if); - } else if (ip_protocol == MDNS_IP_PROTOCOL_V6) { - return _udp_pcb_v6_init(tcpip_if); + if (!_mdns_server || _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb) { + return ESP_ERR_INVALID_STATE; } - return ESP_ERR_INVALID_ARG; + + esp_err_t err = _udp_join_group(tcpip_if, ip_protocol, true); + if(err){ + return err; + } + + err = _udp_pcb_main_init(); + if(err){ + return err; + } + + _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb = _pcb_main; + _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].failed_probes = 0; + return ESP_OK; } typedef struct { - struct tcpip_api_call call; + struct tcpip_api_call_data call; tcpip_adapter_if_t tcpip_if; mdns_ip_protocol_t ip_protocol; esp_err_t err; @@ -216,7 +239,7 @@ typedef struct { /** * @brief Start PCB from LwIP thread */ -static err_t _mdns_pcb_init_api(struct tcpip_api_call *api_call_msg) +static err_t _mdns_pcb_init_api(struct tcpip_api_call_data *api_call_msg) { mdns_api_call_t * msg = (mdns_api_call_t *)api_call_msg; msg->err = _udp_pcb_init(msg->tcpip_if, msg->ip_protocol); @@ -226,7 +249,7 @@ static err_t _mdns_pcb_init_api(struct tcpip_api_call *api_call_msg) /** * @brief Stop PCB from LwIP thread */ -static err_t _mdns_pcb_deinit_api(struct tcpip_api_call *api_call_msg) +static err_t _mdns_pcb_deinit_api(struct tcpip_api_call_data *api_call_msg) { mdns_api_call_t * msg = (mdns_api_call_t *)api_call_msg; _udp_pcb_deinit(msg->tcpip_if, msg->ip_protocol); @@ -245,7 +268,7 @@ esp_err_t _mdns_pcb_init(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_prot .tcpip_if = tcpip_if, .ip_protocol = ip_protocol }; - tcpip_api_call(_mdns_pcb_init_api, (struct tcpip_api_call*)&msg); + tcpip_api_call(_mdns_pcb_init_api, (struct tcpip_api_call_data*)&msg); return msg.err; } @@ -255,7 +278,7 @@ esp_err_t _mdns_pcb_deinit(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_pr .tcpip_if = tcpip_if, .ip_protocol = ip_protocol }; - tcpip_api_call(_mdns_pcb_deinit_api, (struct tcpip_api_call*)&msg); + tcpip_api_call(_mdns_pcb_deinit_api, (struct tcpip_api_call_data*)&msg); return msg.err; } diff --git a/components/mdns/private_include/mdns_private.h b/components/mdns/private_include/mdns_private.h index a3d9fa27..568c81b0 100644 --- a/components/mdns/private_include/mdns_private.h +++ b/components/mdns/private_include/mdns_private.h @@ -20,6 +20,9 @@ #define _mdns_dbg_printf(...) printf(__VA_ARGS__) #endif +/** The maximum number of services */ +#define MDNS_MAX_SERVICES CONFIG_MDNS_MAX_SERVICES + #define MDNS_ANSWER_PTR_TTL 4500 #define MDNS_ANSWER_TXT_TTL 4500 #define MDNS_ANSWER_SRV_TTL 120 @@ -361,8 +364,7 @@ typedef struct { } srv_port; struct { mdns_srv_item_t * service; - uint8_t num_items; - mdns_txt_item_t * txt; + mdns_txt_linked_item_t * txt; } srv_txt_replace; struct { mdns_srv_item_t * service; diff --git a/components/mdns/test_afl_fuzz_host/esp32_compat.h b/components/mdns/test_afl_fuzz_host/esp32_compat.h index 402807c4..7a662607 100644 --- a/components/mdns/test_afl_fuzz_host/esp32_compat.h +++ b/components/mdns/test_afl_fuzz_host/esp32_compat.h @@ -34,6 +34,7 @@ #include #include +#define CONFIG_MDNS_MAX_SERVICES 25 #define ERR_OK 0 #define ESP_OK 0 From f6166d555e104e1e409a410a123584c46d79eccc Mon Sep 17 00:00:00 2001 From: Liu Han Date: Wed, 26 Sep 2018 11:21:04 +0800 Subject: [PATCH 4/5] feat(mdns): change the file order for compile on 8266 --- components/mdns/mdns.c | 4 +++- components/mdns/mdns_console.c | 3 ++- components/mdns/mdns_networking.c | 1 + components/mdns/private_include/mdns_networking.h | 5 ++--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/components/mdns/mdns.c b/components/mdns/mdns.c index 38d67826..52d25fb7 100644 --- a/components/mdns/mdns.c +++ b/components/mdns/mdns.c @@ -13,9 +13,11 @@ // limitations under the License. #include "mdns.h" -#include "mdns_private.h" #include "mdns_networking.h" +#include "mdns_private.h" + #include +#include #ifdef MDNS_ENABLE_DEBUG void mdns_debug_packet(const uint8_t * data, size_t len); diff --git a/components/mdns/mdns_console.c b/components/mdns/mdns_console.c index 17bffa55..4ac521c9 100644 --- a/components/mdns/mdns_console.c +++ b/components/mdns/mdns_console.c @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#ifdef MDNS_ENABLE_CONSOLE #include #include #include "esp_console.h" @@ -1057,4 +1058,4 @@ void mdns_console_register() register_mdns_query_ip(); register_mdns_query_svc(); } - +#endif diff --git a/components/mdns/mdns_networking.c b/components/mdns/mdns_networking.c index 4e64e36c..6f8f7e19 100644 --- a/components/mdns/mdns_networking.c +++ b/components/mdns/mdns_networking.c @@ -4,6 +4,7 @@ * */ #include +#include #include "mdns_networking.h" diff --git a/components/mdns/private_include/mdns_networking.h b/components/mdns/private_include/mdns_networking.h index 5b56f806..6e7f80ea 100644 --- a/components/mdns/private_include/mdns_networking.h +++ b/components/mdns/private_include/mdns_networking.h @@ -5,8 +5,6 @@ * MDNS Server Networking -- private include * */ -#include "mdns.h" -#include "mdns_private.h" #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/queue.h" @@ -21,7 +19,8 @@ #include "esp_system.h" #include "esp_timer.h" #include "esp_event_loop.h" - +#include "mdns.h" +#include "mdns_private.h" /** * @brief Queue RX packet action From 6ab1ff4b361b6b32faf260bb75644d9c38271bbf Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Thu, 11 Oct 2018 14:11:45 +0800 Subject: [PATCH 5/5] feat(mdns): Add configuration to enable/disable mDNS --- components/mdns/CMakeLists.txt | 6 +++--- components/mdns/Kconfig | 8 ++++++++ components/mdns/component.mk | 4 ++++ components/mdns/{ => src}/mdns.c | 0 components/mdns/{ => src}/mdns_console.c | 0 components/mdns/{ => src}/mdns_networking.c | 0 examples/protocols/mdns/sdkconfig.defaults | 1 + 7 files changed, 16 insertions(+), 3 deletions(-) rename components/mdns/{ => src}/mdns.c (100%) rename components/mdns/{ => src}/mdns_console.c (100%) rename components/mdns/{ => src}/mdns_networking.c (100%) create mode 100644 examples/protocols/mdns/sdkconfig.defaults diff --git a/components/mdns/CMakeLists.txt b/components/mdns/CMakeLists.txt index 0933e300..fd592b53 100644 --- a/components/mdns/CMakeLists.txt +++ b/components/mdns/CMakeLists.txt @@ -1,6 +1,6 @@ -set(COMPONENT_SRCS "mdns.c" - "mdns_console.c" - "mdns_networking.c") +set(COMPONENT_SRCS "src/mdns.c" + "src/mdns_console.c" + "src/mdns_networking.c") set(COMPONENT_ADD_INCLUDEDIRS "include") set(COMPONENT_PRIV_INCLUDEDIRS "private_include") set(COMPONENT_REQUIRES lwip mbedtls console tcpip_adapter) diff --git a/components/mdns/Kconfig b/components/mdns/Kconfig index 321b0f65..3c55cd93 100644 --- a/components/mdns/Kconfig +++ b/components/mdns/Kconfig @@ -1,9 +1,17 @@ menu "mDNS" +config ENABLE_MDNS + bool "Enable mDNS" + default n + select LWIP_IPV6 + help + Enable this option and then mDNS is to be used. + config MDNS_MAX_SERVICES int "Max number of services" range 1 64 default 10 + depends on ENABLE_MDNS help Services take up a certain amount of memory, and allowing fewer services to be open at the same time conserves memory. Specify diff --git a/components/mdns/component.mk b/components/mdns/component.mk index 064cc060..df87e77c 100644 --- a/components/mdns/component.mk +++ b/components/mdns/component.mk @@ -1,2 +1,6 @@ COMPONENT_ADD_INCLUDEDIRS := include COMPONENT_PRIV_INCLUDEDIRS := private_include + +ifdef CONFIG_ENABLE_MDNS +COMPONENT_SRCDIRS := src +endif diff --git a/components/mdns/mdns.c b/components/mdns/src/mdns.c similarity index 100% rename from components/mdns/mdns.c rename to components/mdns/src/mdns.c diff --git a/components/mdns/mdns_console.c b/components/mdns/src/mdns_console.c similarity index 100% rename from components/mdns/mdns_console.c rename to components/mdns/src/mdns_console.c diff --git a/components/mdns/mdns_networking.c b/components/mdns/src/mdns_networking.c similarity index 100% rename from components/mdns/mdns_networking.c rename to components/mdns/src/mdns_networking.c diff --git a/examples/protocols/mdns/sdkconfig.defaults b/examples/protocols/mdns/sdkconfig.defaults new file mode 100644 index 00000000..8a694b14 --- /dev/null +++ b/examples/protocols/mdns/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_ENABLE_MDNS=y \ No newline at end of file