diff --git a/.github/workflows/build_and_run_example_test_mdns.yml b/.github/workflows/build_and_run_example_test_mdns.yml
new file mode 100644
index 000000000..de4aac90e
--- /dev/null
+++ b/.github/workflows/build_and_run_example_test_mdns.yml
@@ -0,0 +1,35 @@
+name: Build mDNS
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ idf_ver: ["latest"]
+ idf_target: ["esp32", "esp32s2", "esp32c3"]
+
+ runs-on: ubuntu-20.04
+ container: espressif/idf:${{ matrix.idf_ver }}
+ steps:
+ - name: Checkout esp-protocols
+ uses: actions/checkout@master
+ with:
+ path: esp-protocols
+ - name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
+ env:
+ IDF_TARGET: ${{ matrix.idf_target }}
+ shell: bash
+ run: |
+ . ${IDF_PATH}/export.sh
+ cd $GITHUB_WORKSPACE/esp-protocols/components/mdns/examples/
+ cat sdkconfig.ci.eth_def >> sdkconfig.defaults
+ idf.py build
+ rm sdkconfig.defaults
+ cat sdkconfig.ci.eth_custom_netif >> sdkconfig.defaults
+ idf.py build
+ rm sdkconfig.defaults
+ cat sdkconfig.ci.eth_socket >> sdkconfig.defaults
+ idf.py build
+ cd $GITHUB_WORKSPACE/esp-protocols/components/mdns/tests/test_apps/
+ idf.py build
diff --git a/.github/workflows/publish-docs-component.yml b/.github/workflows/publish-docs-component.yml
index 08b330994..fbc674a2d 100644
--- a/.github/workflows/publish-docs-component.yml
+++ b/.github/workflows/publish-docs-component.yml
@@ -21,25 +21,36 @@ jobs:
run: |
sudo apt-get update
sudo apt-get -y install doxygen clang python3-pip
- python -m pip install breathe recommonmark esp-docs==0.2.1
+ python -m pip install breathe recommonmark esp-docs
cd $GITHUB_WORKSPACE/components/esp_modem/docs
./generate_docs
mkdir -p $GITHUB_WORKSPACE/docs/esp_modem
cp -r html/. $GITHUB_WORKSPACE/docs/esp_modem
+
cd $GITHUB_WORKSPACE/components/esp_websocket_client/docs
./generate_docs
mkdir -p $GITHUB_WORKSPACE/docs/esp_websocket_client
cp -r html/. $GITHUB_WORKSPACE/docs/esp_websocket_client
+
+ cd $GITHUB_WORKSPACE/components/mdns/docs
+ ./generate_docs
+ mkdir -p $GITHUB_WORKSPACE/docs/mdns/en
+ mkdir -p $GITHUB_WORKSPACE/docs/mdns/zh_CN
+ cp -r html_en/. $GITHUB_WORKSPACE/docs/mdns/en
+ cp -r html_zh_CN/. $GITHUB_WORKSPACE/docs/mdns/zh_CN
+
cd $GITHUB_WORKSPACE/docs
touch .nojekyll
- echo 'esp-modem' > index.html
- echo 'esp-websocket-client' >> index.html
+ echo 'esp-modem
' > index.html
+ echo 'esp-websocket-client
' >> index.html
+ echo 'mDNS_en
' >> index.html
+ echo 'mDNS_zh_CN
' >> index.html
- name: Upload components to component service
uses: espressif/github-actions/upload_components@master
with:
- directories: "components/esp_modem;components/esp_websocket_client"
+ directories: "components/esp_modem;components/esp_websocket_client;components/mdns"
namespace: "espressif"
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}
diff --git a/.github/workflows/test_afl_fuzzer.yml b/.github/workflows/test_afl_fuzzer.yml
new file mode 100644
index 000000000..a82e0096a
--- /dev/null
+++ b/.github/workflows/test_afl_fuzzer.yml
@@ -0,0 +1,30 @@
+name: AFL fuzzer compilation test
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ idf_ver: ["latest"]
+ idf_target: ["esp32"]
+
+ runs-on: ubuntu-20.04
+ container: espressif/idf:${{ matrix.idf_ver }}
+ steps:
+ - name: Checkout esp-protocols
+ uses: actions/checkout@master
+ with:
+ path: esp-protocols
+ - name: Install Necessary Libs
+ run: |
+ apt-get update -y
+ apt-get install -y libbsd-dev
+ - name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
+ env:
+ IDF_TARGET: ${{ matrix.idf_target }}
+ shell: bash
+ run: |
+ . ${IDF_PATH}/export.sh
+ cd $GITHUB_WORKSPACE/esp-protocols/components/mdns/tests/test_afl_fuzz_host/
+ make INSTR=off
diff --git a/README.md b/README.md
index 366e078c9..f1952a9e6 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,11 @@
* Brief introduction [README](components/esp_modem/README.md)
* Full html [documentation](https://espressif.github.io/esp-protocols/esp_modem/index.html)
+### mDNS
+
+* Brief introduction [README](components/mdns/README.md)
+* Full html [documentation](https://espressif.github.io/esp-protocols/mdns/index.html)
+
### esp_websocket_client
* Brief introduction [README](components/esp_websocket_client/README.md)
diff --git a/common_components/protocol_examples_common/CMakeLists.txt b/common_components/protocol_examples_common/CMakeLists.txt
new file mode 100644
index 000000000..5f9317e17
--- /dev/null
+++ b/common_components/protocol_examples_common/CMakeLists.txt
@@ -0,0 +1,4 @@
+idf_component_register(SRCS "connect.c" "stdin_out.c" "addr_from_stdin.c"
+ INCLUDE_DIRS "include"
+ PRIV_REQUIRES esp_netif driver
+ )
diff --git a/common_components/protocol_examples_common/Kconfig.projbuild b/common_components/protocol_examples_common/Kconfig.projbuild
new file mode 100644
index 000000000..86ee4f140
--- /dev/null
+++ b/common_components/protocol_examples_common/Kconfig.projbuild
@@ -0,0 +1,322 @@
+menu "Example Connection Configuration"
+
+ orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
+
+ config EXAMPLE_CONNECT_WIFI
+ bool "connect using WiFi interface"
+ default y
+ help
+ Protocol examples can use Wi-Fi and/or Ethernet to connect to the network.
+ Choose this option to connect with WiFi
+
+ if EXAMPLE_CONNECT_WIFI
+ config EXAMPLE_WIFI_SSID
+ string "WiFi SSID"
+ default "myssid"
+ help
+ SSID (network name) for the example to connect to.
+
+ config EXAMPLE_WIFI_PASSWORD
+ string "WiFi Password"
+ default "mypassword"
+ help
+ WiFi password (WPA or WPA2) for the example to use.
+ Can be left blank if the network has no security set.
+
+ choice EXAMPLE_WIFI_SCAN_METHOD
+ prompt "WiFi Scan Method"
+ default EXAMPLE_WIFI_SCAN_METHOD_ALL_CHANNEL
+ help
+ WiFi scan method:
+
+ If "Fast" is selected, scan will end after find SSID match AP.
+
+ If "All Channel" is selected, scan will end after scan all the channel.
+
+ config EXAMPLE_WIFI_SCAN_METHOD_FAST
+ bool "Fast"
+ config EXAMPLE_WIFI_SCAN_METHOD_ALL_CHANNEL
+ bool "All Channel"
+ endchoice
+
+ menu "WiFi Scan threshold"
+ config EXAMPLE_WIFI_SCAN_RSSI_THRESHOLD
+ int "WiFi minimum rssi"
+ range -127 0
+
+ default -127
+ help
+ The minimum rssi to accept in the scan mode.
+
+ choice EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD
+ prompt "WiFi Scan auth mode threshold"
+ default EXAMPLE_WIFI_AUTH_OPEN
+ help
+ The weakest authmode to accept in the scan mode.
+
+ config EXAMPLE_WIFI_AUTH_OPEN
+ bool "OPEN"
+ config EXAMPLE_WIFI_AUTH_WEP
+ bool "WEP"
+ config EXAMPLE_WIFI_AUTH_WPA_PSK
+ bool "WPA PSK"
+ config EXAMPLE_WIFI_AUTH_WPA2_PSK
+ bool "WPA2 PSK"
+ config EXAMPLE_WIFI_AUTH_WPA_WPA2_PSK
+ bool "WPA WPA2 PSK"
+ config EXAMPLE_WIFI_AUTH_WPA2_ENTERPRISE
+ bool "WPA2 ENTERPRISE"
+ config EXAMPLE_WIFI_AUTH_WPA3_PSK
+ bool "WPA3 PSK"
+ config EXAMPLE_WIFI_AUTH_WPA2_WPA3_PSK
+ bool "WPA2 WPA3 PSK"
+ config EXAMPLE_WIFI_AUTH_WAPI_PSK
+ bool "WAPI PSK"
+ endchoice
+ endmenu
+
+ choice EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD
+ prompt "WiFi Connect AP Sort Method"
+ default EXAMPLE_WIFI_CONNECT_AP_BY_SIGNAL
+ help
+ WiFi connect AP sort method:
+
+ If "Signal" is selected, Sort matched APs in scan list by RSSI.
+
+ If "Security" is selected, Sort matched APs in scan list by security mode.
+
+ config EXAMPLE_WIFI_CONNECT_AP_BY_SIGNAL
+ bool "Signal"
+ config EXAMPLE_WIFI_CONNECT_AP_BY_SECURITY
+ bool "Security"
+ endchoice
+ endif
+
+ config EXAMPLE_CONNECT_ETHERNET
+ bool "connect using Ethernet interface"
+ default n
+ help
+ Protocol examples can use Wi-Fi and/or Ethernet to connect to the network.
+ Choose this option to connect with Ethernet
+
+ if EXAMPLE_CONNECT_ETHERNET
+ config EXAMPLE_USE_SPI_ETHERNET
+ bool
+
+ choice EXAMPLE_ETHERNET_TYPE
+ prompt "Ethernet Type"
+ default EXAMPLE_USE_INTERNAL_ETHERNET if IDF_TARGET_ESP32
+ default EXAMPLE_USE_W5500
+ help
+ Select which kind of Ethernet will be used in the example.
+
+ config EXAMPLE_USE_INTERNAL_ETHERNET
+ depends on IDF_TARGET_ESP32
+ select ETH_USE_ESP32_EMAC
+ bool "Internal EMAC"
+ help
+ Select internal Ethernet MAC controller.
+
+ config EXAMPLE_USE_DM9051
+ bool "DM9051 Module"
+ select EXAMPLE_USE_SPI_ETHERNET
+ select ETH_USE_SPI_ETHERNET
+ select ETH_SPI_ETHERNET_DM9051
+ help
+ Select external SPI-Ethernet module.
+
+ config EXAMPLE_USE_W5500
+ bool "W5500 Module"
+ select EXAMPLE_USE_SPI_ETHERNET
+ select ETH_USE_SPI_ETHERNET
+ select ETH_SPI_ETHERNET_W5500
+ help
+ Select external SPI-Ethernet module (W5500).
+
+ config EXAMPLE_USE_OPENETH
+ bool "OpenCores Ethernet MAC (EXPERIMENTAL)"
+ select ETH_USE_OPENETH
+ help
+ When this option is enabled, the example is built with support for
+ OpenCores Ethernet MAC, which allows testing the example in QEMU.
+ Note that this option is used for internal testing purposes, and
+ not officially supported. Examples built with this option enabled
+ will not run on a real ESP32 chip.
+
+ endchoice # EXAMPLE_ETHERNET_TYPE
+
+ if EXAMPLE_USE_INTERNAL_ETHERNET
+ choice EXAMPLE_ETH_PHY_MODEL
+ prompt "Ethernet PHY Device"
+ default EXAMPLE_ETH_PHY_IP101
+ help
+ Select the Ethernet PHY device to use in the example.
+
+ config EXAMPLE_ETH_PHY_IP101
+ bool "IP101"
+ help
+ IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
+ Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
+
+ config EXAMPLE_ETH_PHY_RTL8201
+ bool "RTL8201/SR8201"
+ help
+ RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
+ Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
+
+ config EXAMPLE_ETH_PHY_LAN87XX
+ bool "LAN87xx"
+ help
+ Below chips are supported:
+ LAN8710A is a small footprint MII/RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
+ flexPWR® Technology.
+ LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
+ LAN8740A/LAN8741A is a small footprint MII/RMII 10/100 Energy Efficient Ethernet Transceiver
+ with HP Auto-MDIX and flexPWR® Technology.
+ LAN8742A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
+ flexPWR® Technology.
+ Goto https://www.microchip.com for more information about them.
+
+ config EXAMPLE_ETH_PHY_DP83848
+ bool "DP83848"
+ help
+ DP83848 is a single port 10/100Mb/s Ethernet Physical Layer Transceiver.
+ Goto http://www.ti.com/product/DP83848J for more information about it.
+
+ config EXAMPLE_ETH_PHY_KSZ80XX
+ bool "KSZ80xx"
+ help
+ With the KSZ80xx series, Microchip offers single-chip 10BASE-T/100BASE-TX
+ Ethernet Physical Layer Tranceivers (PHY).
+ The following chips are supported: KSZ8001, KSZ8021, KSZ8031, KSZ8041,
+ KSZ8051, KSZ8061, KSZ8081, KSZ8091
+ Goto https://www.microchip.com for more information about them.
+ endchoice
+
+ config EXAMPLE_ETH_MDC_GPIO
+ int "SMI MDC GPIO number"
+ range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
+ default 23
+ help
+ Set the GPIO number used by SMI MDC.
+
+ config EXAMPLE_ETH_MDIO_GPIO
+ int "SMI MDIO GPIO number"
+ range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
+ default 18
+ help
+ Set the GPIO number used by SMI MDIO.
+ endif
+
+ if EXAMPLE_USE_SPI_ETHERNET
+ config EXAMPLE_ETH_SPI_HOST
+ int "SPI Host Number"
+ range 0 2
+ default 1
+ help
+ Set the SPI host used to communicate with the SPI Ethernet Controller.
+
+ config EXAMPLE_ETH_SPI_SCLK_GPIO
+ int "SPI SCLK GPIO number"
+ range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
+ default 14
+ help
+ Set the GPIO number used by SPI SCLK.
+
+ config EXAMPLE_ETH_SPI_MOSI_GPIO
+ int "SPI MOSI GPIO number"
+ range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
+ default 13
+ help
+ Set the GPIO number used by SPI MOSI.
+
+ config EXAMPLE_ETH_SPI_MISO_GPIO
+ int "SPI MISO GPIO number"
+ range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
+ default 12
+ help
+ Set the GPIO number used by SPI MISO.
+
+ config EXAMPLE_ETH_SPI_CS_GPIO
+ int "SPI CS GPIO number"
+ range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
+ default 15
+ help
+ Set the GPIO number used by SPI CS.
+
+ config EXAMPLE_ETH_SPI_CLOCK_MHZ
+ int "SPI clock speed (MHz)"
+ range 5 80
+ default 36
+ help
+ Set the clock speed (MHz) of SPI interface.
+
+ config EXAMPLE_ETH_SPI_INT_GPIO
+ int "Interrupt GPIO number"
+ range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
+ default 4
+ help
+ Set the GPIO number used by the SPI Ethernet module interrupt line.
+ endif # EXAMPLE_USE_SPI_ETHERNET
+
+ config EXAMPLE_ETH_PHY_RST_GPIO
+ int "PHY Reset GPIO number"
+ range -1 ENV_GPIO_OUT_RANGE_MAX
+ default 5
+ help
+ Set the GPIO number used to reset PHY chip.
+ Set to -1 to disable PHY chip hardware reset.
+
+ config EXAMPLE_ETH_PHY_ADDR
+ int "PHY Address"
+ range 0 31 if EXAMPLE_USE_INTERNAL_ETHERNET
+ default 1
+ help
+ Set PHY address according your board schematic.
+ endif # EXAMPLE_CONNECT_ETHERNET
+
+ config EXAMPLE_CONNECT_IPV6
+ bool "Obtain IPv6 address"
+ default y
+ depends on EXAMPLE_CONNECT_WIFI || EXAMPLE_CONNECT_ETHERNET
+ select LWIP_IPV6
+ help
+ By default, examples will wait until IPv4 and IPv6 local link addresses are obtained.
+ Disable this option if the network does not support IPv6.
+ Choose the preferred IPv6 address type if the connection code should wait until other than
+ the local link address gets assigned.
+ Consider enabling IPv6 stateless address autoconfiguration (SLAAC) in the LWIP component.
+
+ if EXAMPLE_CONNECT_IPV6
+ choice EXAMPLE_CONNECT_PREFERRED_IPV6
+ prompt "Preferred IPv6 Type"
+ default EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK
+ help
+ Select which kind of IPv6 address the connect logic waits for.
+
+ config EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK
+ bool "Local Link Address"
+ help
+ Blocks until Local link address assigned.
+
+ config EXAMPLE_CONNECT_IPV6_PREF_GLOBAL
+ bool "Global Address"
+ help
+ Blocks until Global address assigned.
+
+ config EXAMPLE_CONNECT_IPV6_PREF_SITE_LOCAL
+ bool "Site Local Address"
+ help
+ Blocks until Site link address assigned.
+
+ config EXAMPLE_CONNECT_IPV6_PREF_UNIQUE_LOCAL
+ bool "Unique Local Link Address"
+ help
+ Blocks until Unique local address assigned.
+
+ endchoice
+
+ endif
+
+
+endmenu
diff --git a/common_components/protocol_examples_common/addr_from_stdin.c b/common_components/protocol_examples_common/addr_from_stdin.c
new file mode 100644
index 000000000..c907ffd96
--- /dev/null
+++ b/common_components/protocol_examples_common/addr_from_stdin.c
@@ -0,0 +1,68 @@
+#include
+#include "esp_system.h"
+#include "esp_log.h"
+#include "esp_netif.h"
+#include "protocol_examples_common.h"
+
+#include "lwip/sockets.h"
+#include
+#include
+
+#define HOST_IP_SIZE 128
+
+esp_err_t get_addr_from_stdin(int port, int sock_type, int *ip_protocol, int *addr_family, struct sockaddr_storage *dest_addr)
+{
+ char host_ip[HOST_IP_SIZE];
+ int len;
+ static bool already_init = false;
+
+ // this function could be called multiple times -> make sure UART init runs only once
+ if (!already_init) {
+ example_configure_stdin_stdout();
+ already_init = true;
+ }
+
+ // ignore empty or LF only string (could receive from DUT class)
+ do {
+ fgets(host_ip, HOST_IP_SIZE, stdin);
+ len = strlen(host_ip);
+ } while (len<=1 && host_ip[0] == '\n');
+ host_ip[len - 1] = '\0';
+
+ struct addrinfo hints, *addr_list, *cur;
+ memset( &hints, 0, sizeof( hints ) );
+
+ // run getaddrinfo() to decide on the IP protocol
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = sock_type;
+ hints.ai_protocol = IPPROTO_TCP;
+ if( getaddrinfo( host_ip, NULL, &hints, &addr_list ) != 0 ) {
+ return ESP_FAIL;
+ }
+ for( cur = addr_list; cur != NULL; cur = cur->ai_next ) {
+ memcpy(dest_addr, cur->ai_addr, sizeof(*dest_addr));
+ if (cur->ai_family == AF_INET) {
+ *ip_protocol = IPPROTO_IP;
+ *addr_family = AF_INET;
+ // add port number and return on first IPv4 match
+ ((struct sockaddr_in*)dest_addr)->sin_port = htons(port);
+ freeaddrinfo( addr_list );
+ return ESP_OK;
+
+ }
+#if CONFIG_LWIP_IPV6
+ else if (cur->ai_family == AF_INET6) {
+ *ip_protocol = IPPROTO_IPV6;
+ *addr_family = AF_INET6;
+ // add port and interface number and return on first IPv6 match
+ ((struct sockaddr_in6*)dest_addr)->sin6_port = htons(port);
+ ((struct sockaddr_in6*)dest_addr)->sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE);
+ freeaddrinfo( addr_list );
+ return ESP_OK;
+ }
+#endif
+ }
+ // no match found
+ freeaddrinfo( addr_list );
+ return ESP_FAIL;
+}
diff --git a/common_components/protocol_examples_common/connect.c b/common_components/protocol_examples_common/connect.c
new file mode 100644
index 000000000..85878d78b
--- /dev/null
+++ b/common_components/protocol_examples_common/connect.c
@@ -0,0 +1,515 @@
+/* Common functions for protocol examples, to establish Wi-Fi or Ethernet connection.
+
+ 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 "protocol_examples_common.h"
+#include "sdkconfig.h"
+#include "esp_event.h"
+#include "esp_wifi.h"
+#include "esp_wifi_default.h"
+#if CONFIG_EXAMPLE_CONNECT_ETHERNET
+#include "esp_eth.h"
+#if CONFIG_ETH_USE_SPI_ETHERNET
+#include "driver/spi_master.h"
+#endif // CONFIG_ETH_USE_SPI_ETHERNET
+#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
+#include "esp_log.h"
+#include "esp_netif.h"
+#include "driver/gpio.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "lwip/err.h"
+#include "lwip/sys.h"
+
+#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
+#define MAX_IP6_ADDRS_PER_NETIF (5)
+#define NR_OF_IP_ADDRESSES_TO_WAIT_FOR (s_active_interfaces*2)
+
+#if defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK)
+#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_LINK_LOCAL
+#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_GLOBAL)
+#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_GLOBAL
+#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_SITE_LOCAL)
+#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_SITE_LOCAL
+#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_UNIQUE_LOCAL)
+#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_UNIQUE_LOCAL
+#endif // if-elif CONFIG_EXAMPLE_CONNECT_IPV6_PREF_...
+
+#else
+#define NR_OF_IP_ADDRESSES_TO_WAIT_FOR (s_active_interfaces)
+#endif
+
+#define EXAMPLE_DO_CONNECT CONFIG_EXAMPLE_CONNECT_WIFI || CONFIG_EXAMPLE_CONNECT_ETHERNET
+
+#if CONFIG_EXAMPLE_WIFI_SCAN_METHOD_FAST
+#define EXAMPLE_WIFI_SCAN_METHOD WIFI_FAST_SCAN
+#elif CONFIG_EXAMPLE_WIFI_SCAN_METHOD_ALL_CHANNEL
+#define EXAMPLE_WIFI_SCAN_METHOD WIFI_ALL_CHANNEL_SCAN
+#endif
+
+#if CONFIG_EXAMPLE_WIFI_CONNECT_AP_BY_SIGNAL
+#define EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL
+#elif CONFIG_EXAMPLE_WIFI_CONNECT_AP_BY_SECURITY
+#define EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD WIFI_CONNECT_AP_BY_SECURITY
+#endif
+
+#if CONFIG_EXAMPLE_WIFI_AUTH_OPEN
+#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
+#elif CONFIG_EXAMPLE_WIFI_AUTH_WEP
+#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
+#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA_PSK
+#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
+#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA2_PSK
+#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
+#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA_WPA2_PSK
+#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
+#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA2_ENTERPRISE
+#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_ENTERPRISE
+#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA3_PSK
+#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
+#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA2_WPA3_PSK
+#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
+#elif CONFIG_EXAMPLE_WIFI_AUTH_WAPI_PSK
+#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
+#endif
+
+static int s_active_interfaces = 0;
+static SemaphoreHandle_t s_semph_get_ip_addrs;
+static esp_netif_t *s_example_esp_netif = NULL;
+
+#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
+static esp_ip6_addr_t s_ipv6_addr;
+
+/* types of ipv6 addresses to be displayed on ipv6 events */
+static const char *s_ipv6_addr_types[] = {
+ "ESP_IP6_ADDR_IS_UNKNOWN",
+ "ESP_IP6_ADDR_IS_GLOBAL",
+ "ESP_IP6_ADDR_IS_LINK_LOCAL",
+ "ESP_IP6_ADDR_IS_SITE_LOCAL",
+ "ESP_IP6_ADDR_IS_UNIQUE_LOCAL",
+ "ESP_IP6_ADDR_IS_IPV4_MAPPED_IPV6"
+};
+#endif
+
+static const char *TAG = "example_connect";
+
+#if CONFIG_EXAMPLE_CONNECT_WIFI
+static esp_netif_t *wifi_start(void);
+static void wifi_stop(void);
+#endif
+#if CONFIG_EXAMPLE_CONNECT_ETHERNET
+static esp_netif_t *eth_start(void);
+static void eth_stop(void);
+#endif
+
+/**
+ * @brief Checks the netif description if it contains specified prefix.
+ * All netifs created withing common connect component are prefixed with the module TAG,
+ * so it returns true if the specified netif is owned by this module
+ */
+static bool is_our_netif(const char *prefix, esp_netif_t *netif)
+{
+ return strncmp(prefix, esp_netif_get_desc(netif), strlen(prefix) - 1) == 0;
+}
+
+/* set up connection, Wi-Fi and/or Ethernet */
+static void start(void)
+{
+
+#if CONFIG_EXAMPLE_CONNECT_WIFI
+ s_example_esp_netif = wifi_start();
+ s_active_interfaces++;
+#endif
+
+#if CONFIG_EXAMPLE_CONNECT_ETHERNET
+ s_example_esp_netif = eth_start();
+ s_active_interfaces++;
+#endif
+
+#if CONFIG_EXAMPLE_CONNECT_WIFI && CONFIG_EXAMPLE_CONNECT_ETHERNET
+ /* if both intefaces at once, clear out to indicate that multiple netifs are active */
+ s_example_esp_netif = NULL;
+#endif
+
+#if EXAMPLE_DO_CONNECT
+ /* create semaphore if at least one interface is active */
+ s_semph_get_ip_addrs = xSemaphoreCreateCounting(NR_OF_IP_ADDRESSES_TO_WAIT_FOR, 0);
+#endif
+
+}
+
+/* tear down connection, release resources */
+static void stop(void)
+{
+#if CONFIG_EXAMPLE_CONNECT_WIFI
+ wifi_stop();
+ s_active_interfaces--;
+#endif
+
+#if CONFIG_EXAMPLE_CONNECT_ETHERNET
+ eth_stop();
+ s_active_interfaces--;
+#endif
+}
+
+#if EXAMPLE_DO_CONNECT
+static esp_ip4_addr_t s_ip_addr;
+
+static void on_got_ip(void *arg, esp_event_base_t event_base,
+ int32_t event_id, void *event_data)
+{
+ ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
+ if (!is_our_netif(TAG, event->esp_netif)) {
+ ESP_LOGW(TAG, "Got IPv4 from another interface \"%s\": ignored", esp_netif_get_desc(event->esp_netif));
+ return;
+ }
+ ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip));
+ memcpy(&s_ip_addr, &event->ip_info.ip, sizeof(s_ip_addr));
+ xSemaphoreGive(s_semph_get_ip_addrs);
+}
+#endif
+
+#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
+
+static void on_got_ipv6(void *arg, esp_event_base_t event_base,
+ int32_t event_id, void *event_data)
+{
+ ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data;
+ if (!is_our_netif(TAG, event->esp_netif)) {
+ ESP_LOGW(TAG, "Got IPv6 from another netif: ignored");
+ return;
+ }
+ esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&event->ip6_info.ip);
+ ESP_LOGI(TAG, "Got IPv6 event: Interface \"%s\" address: " IPV6STR ", type: %s", esp_netif_get_desc(event->esp_netif),
+ IPV62STR(event->ip6_info.ip), s_ipv6_addr_types[ipv6_type]);
+ if (ipv6_type == EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE) {
+ memcpy(&s_ipv6_addr, &event->ip6_info.ip, sizeof(s_ipv6_addr));
+ xSemaphoreGive(s_semph_get_ip_addrs);
+ }
+}
+
+#endif // CONFIG_EXAMPLE_CONNECT_IPV6
+
+esp_err_t example_connect(void)
+{
+#if EXAMPLE_DO_CONNECT
+ if (s_semph_get_ip_addrs != NULL) {
+ return ESP_ERR_INVALID_STATE;
+ }
+#endif
+ start();
+ ESP_ERROR_CHECK(esp_register_shutdown_handler(&stop));
+ ESP_LOGI(TAG, "Waiting for IP(s)");
+ for (int i = 0; i < NR_OF_IP_ADDRESSES_TO_WAIT_FOR; ++i) {
+ xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY);
+ }
+ // iterate over active interfaces, and print out IPs of "our" netifs
+ esp_netif_t *netif = NULL;
+ esp_netif_ip_info_t ip;
+ for (int i = 0; i < esp_netif_get_nr_of_ifs(); ++i) {
+ netif = esp_netif_next(netif);
+ if (is_our_netif(TAG, netif)) {
+ ESP_LOGI(TAG, "Connected to %s", esp_netif_get_desc(netif));
+ ESP_ERROR_CHECK(esp_netif_get_ip_info(netif, &ip));
+
+ ESP_LOGI(TAG, "- IPv4 address: " IPSTR, IP2STR(&ip.ip));
+#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
+ esp_ip6_addr_t ip6[MAX_IP6_ADDRS_PER_NETIF];
+ int ip6_addrs = esp_netif_get_all_ip6(netif, ip6);
+ for (int j = 0; j < ip6_addrs; ++j) {
+ esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&(ip6[j]));
+ ESP_LOGI(TAG, "- IPv6 address: " IPV6STR ", type: %s", IPV62STR(ip6[j]), s_ipv6_addr_types[ipv6_type]);
+ }
+#endif
+
+ }
+ }
+ return ESP_OK;
+}
+
+esp_err_t example_disconnect(void)
+{
+ if (s_semph_get_ip_addrs == NULL) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ vSemaphoreDelete(s_semph_get_ip_addrs);
+ s_semph_get_ip_addrs = NULL;
+ stop();
+ ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&stop));
+ return ESP_OK;
+}
+
+#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
+
+static void on_wifi_disconnect(void *arg, esp_event_base_t event_base,
+ int32_t event_id, void *event_data)
+{
+ ESP_LOGI(TAG, "Wi-Fi disconnected, trying to reconnect...");
+ esp_err_t err = esp_wifi_connect();
+ if (err == ESP_ERR_WIFI_NOT_STARTED) {
+ return;
+ }
+ ESP_ERROR_CHECK(err);
+}
+
+#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
+
+static void on_wifi_connect(void *esp_netif, esp_event_base_t event_base,
+ int32_t event_id, void *event_data)
+{
+ esp_netif_create_ip6_linklocal(esp_netif);
+}
+
+#endif // CONFIG_EXAMPLE_CONNECT_IPV6
+
+static esp_netif_t *wifi_start(void)
+{
+ char *desc;
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_WIFI_STA();
+ // Prefix the interface description with the module TAG
+ // Warning: the interface desc is used in tests to capture actual connection details (IP, gw, mask)
+ asprintf(&desc, "%s: %s", TAG, esp_netif_config.if_desc);
+ esp_netif_config.if_desc = desc;
+ esp_netif_config.route_prio = 128;
+ esp_netif_t *netif = esp_netif_create_wifi(WIFI_IF_STA, &esp_netif_config);
+ free(desc);
+ esp_wifi_set_default_wifi_sta_handlers();
+
+ ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &on_wifi_disconnect, NULL));
+ ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_got_ip, NULL));
+#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
+ ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &on_wifi_connect, netif));
+ ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &on_got_ipv6, NULL));
+#endif
+
+ ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
+ wifi_config_t wifi_config = {
+ .sta = {
+ .ssid = CONFIG_EXAMPLE_WIFI_SSID,
+ .password = CONFIG_EXAMPLE_WIFI_PASSWORD,
+ .scan_method = EXAMPLE_WIFI_SCAN_METHOD,
+ .sort_method = EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD,
+ .threshold.rssi = CONFIG_EXAMPLE_WIFI_SCAN_RSSI_THRESHOLD,
+ .threshold.authmode = EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD,
+ },
+ };
+ ESP_LOGI(TAG, "Connecting to %s...", wifi_config.sta.ssid);
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
+ ESP_ERROR_CHECK(esp_wifi_start());
+ esp_wifi_connect();
+ return netif;
+}
+
+static void wifi_stop(void)
+{
+ esp_netif_t *wifi_netif = get_example_netif_from_desc("sta");
+ ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &on_wifi_disconnect));
+ ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_got_ip));
+#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
+ ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_GOT_IP6, &on_got_ipv6));
+ ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &on_wifi_connect));
+#endif
+ esp_err_t err = esp_wifi_stop();
+ if (err == ESP_ERR_WIFI_NOT_INIT) {
+ return;
+ }
+ ESP_ERROR_CHECK(err);
+ ESP_ERROR_CHECK(esp_wifi_deinit());
+ ESP_ERROR_CHECK(esp_wifi_clear_default_wifi_driver_and_handlers(wifi_netif));
+ esp_netif_destroy(wifi_netif);
+ s_example_esp_netif = NULL;
+}
+#endif // CONFIG_EXAMPLE_CONNECT_WIFI
+
+#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
+
+#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
+
+/** Event handler for Ethernet events */
+static void on_eth_event(void *esp_netif, esp_event_base_t event_base,
+ int32_t event_id, void *event_data)
+{
+ switch (event_id) {
+ case ETHERNET_EVENT_CONNECTED:
+ ESP_LOGI(TAG, "Ethernet Link Up");
+ ESP_ERROR_CHECK(esp_netif_create_ip6_linklocal(esp_netif));
+ break;
+ default:
+ break;
+ }
+}
+
+#endif // CONFIG_EXAMPLE_CONNECT_IPV6
+
+static esp_eth_handle_t s_eth_handle = NULL;
+static esp_eth_mac_t *s_mac = NULL;
+static esp_eth_phy_t *s_phy = NULL;
+static esp_eth_netif_glue_handle_t s_eth_glue = NULL;
+
+static esp_netif_t *eth_start(void)
+{
+ char *desc;
+ esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_ETH();
+ // Prefix the interface description with the module TAG
+ // Warning: the interface desc is used in tests to capture actual connection details (IP, gw, mask)
+ asprintf(&desc, "%s: %s", TAG, esp_netif_config.if_desc);
+ esp_netif_config.if_desc = desc;
+ esp_netif_config.route_prio = 64;
+ esp_netif_config_t netif_config = {
+ .base = &esp_netif_config,
+ .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH
+ };
+ esp_netif_t *netif = esp_netif_new(&netif_config);
+ assert(netif);
+ free(desc);
+
+ eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
+ eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
+ phy_config.phy_addr = CONFIG_EXAMPLE_ETH_PHY_ADDR;
+ phy_config.reset_gpio_num = CONFIG_EXAMPLE_ETH_PHY_RST_GPIO;
+#if CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
+ eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
+ esp32_emac_config.smi_mdc_gpio_num = CONFIG_EXAMPLE_ETH_MDC_GPIO;
+ esp32_emac_config.smi_mdio_gpio_num = CONFIG_EXAMPLE_ETH_MDIO_GPIO;
+ s_mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
+#if CONFIG_EXAMPLE_ETH_PHY_IP101
+ s_phy = esp_eth_phy_new_ip101(&phy_config);
+#elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
+ s_phy = esp_eth_phy_new_rtl8201(&phy_config);
+#elif CONFIG_EXAMPLE_ETH_PHY_LAN87XX
+ s_phy = esp_eth_phy_new_lan87xx(&phy_config);
+#elif CONFIG_EXAMPLE_ETH_PHY_DP83848
+ s_phy = esp_eth_phy_new_dp83848(&phy_config);
+#elif CONFIG_EXAMPLE_ETH_PHY_KSZ80XX
+ s_phy = esp_eth_phy_new_ksz80xx(&phy_config);
+#endif
+#elif CONFIG_EXAMPLE_USE_SPI_ETHERNET
+ gpio_install_isr_service(0);
+ spi_device_handle_t spi_handle = NULL;
+ spi_bus_config_t buscfg = {
+ .miso_io_num = CONFIG_EXAMPLE_ETH_SPI_MISO_GPIO,
+ .mosi_io_num = CONFIG_EXAMPLE_ETH_SPI_MOSI_GPIO,
+ .sclk_io_num = CONFIG_EXAMPLE_ETH_SPI_SCLK_GPIO,
+ .quadwp_io_num = -1,
+ .quadhd_io_num = -1,
+ };
+ ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, 1));
+#if CONFIG_EXAMPLE_USE_DM9051
+ spi_device_interface_config_t devcfg = {
+ .command_bits = 1,
+ .address_bits = 7,
+ .mode = 0,
+ .clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
+ .spics_io_num = CONFIG_EXAMPLE_ETH_SPI_CS_GPIO,
+ .queue_size = 20
+ };
+ ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
+ /* dm9051 ethernet driver is based on spi driver */
+ eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(spi_handle);
+ dm9051_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
+ s_mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config);
+ s_phy = esp_eth_phy_new_dm9051(&phy_config);
+#elif CONFIG_EXAMPLE_USE_W5500
+ spi_device_interface_config_t devcfg = {
+ .command_bits = 16, // Actually it's the address phase in W5500 SPI frame
+ .address_bits = 8, // Actually it's the control phase in W5500 SPI frame
+ .mode = 0,
+ .clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
+ .spics_io_num = CONFIG_EXAMPLE_ETH_SPI_CS_GPIO,
+ .queue_size = 20
+ };
+ ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
+ /* w5500 ethernet driver is based on spi driver */
+ eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
+ w5500_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
+ s_mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
+ s_phy = esp_eth_phy_new_w5500(&phy_config);
+#endif
+#elif CONFIG_EXAMPLE_USE_OPENETH
+ phy_config.autonego_timeout_ms = 100;
+ s_mac = esp_eth_mac_new_openeth(&mac_config);
+ s_phy = esp_eth_phy_new_dp83848(&phy_config);
+#endif
+
+ // Install Ethernet driver
+ esp_eth_config_t config = ETH_DEFAULT_CONFIG(s_mac, s_phy);
+ ESP_ERROR_CHECK(esp_eth_driver_install(&config, &s_eth_handle));
+#if !CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
+ /* The SPI Ethernet module might doesn't have a burned factory MAC address, we cat to set it manually.
+ 02:00:00 is a Locally Administered OUI range so should not be used except when testing on a LAN under your control.
+ */
+ ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_S_MAC_ADDR, (uint8_t[]) {
+ 0x02, 0x00, 0x00, 0x12, 0x34, 0x56
+ }));
+#endif
+ // combine driver with netif
+ s_eth_glue = esp_eth_new_netif_glue(s_eth_handle);
+ esp_netif_attach(netif, s_eth_glue);
+
+ // Register user defined event handers
+ ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &on_got_ip, NULL));
+#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
+ ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_CONNECTED, &on_eth_event, netif));
+ ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &on_got_ipv6, NULL));
+#endif
+
+ esp_eth_start(s_eth_handle);
+ return netif;
+}
+
+static void eth_stop(void)
+{
+ esp_netif_t *eth_netif = get_example_netif_from_desc("eth");
+ ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, &on_got_ip));
+#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
+ ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_GOT_IP6, &on_got_ipv6));
+ ESP_ERROR_CHECK(esp_event_handler_unregister(ETH_EVENT, ETHERNET_EVENT_CONNECTED, &on_eth_event));
+#endif
+ ESP_ERROR_CHECK(esp_eth_stop(s_eth_handle));
+ ESP_ERROR_CHECK(esp_eth_del_netif_glue(s_eth_glue));
+ ESP_ERROR_CHECK(esp_eth_driver_uninstall(s_eth_handle));
+ s_eth_handle = NULL;
+ ESP_ERROR_CHECK(s_phy->del(s_phy));
+ ESP_ERROR_CHECK(s_mac->del(s_mac));
+
+ esp_netif_destroy(eth_netif);
+ s_example_esp_netif = NULL;
+}
+
+esp_eth_handle_t get_example_eth_handle(void)
+{
+ return s_eth_handle;
+}
+
+#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
+
+esp_netif_t *get_example_netif(void)
+{
+ return s_example_esp_netif;
+}
+
+esp_netif_t *get_example_netif_from_desc(const char *desc)
+{
+ esp_netif_t *netif = NULL;
+ char *expected_desc;
+ asprintf(&expected_desc, "%s: %s", TAG, desc);
+ while ((netif = esp_netif_next(netif)) != NULL) {
+ if (strcmp(esp_netif_get_desc(netif), expected_desc) == 0) {
+ free(expected_desc);
+ return netif;
+ }
+ }
+ free(expected_desc);
+ return netif;
+}
diff --git a/common_components/protocol_examples_common/include/addr_from_stdin.h b/common_components/protocol_examples_common/include/addr_from_stdin.h
new file mode 100644
index 000000000..9a059c149
--- /dev/null
+++ b/common_components/protocol_examples_common/include/addr_from_stdin.h
@@ -0,0 +1,44 @@
+/* Common utilities for socket address input interface:
+ The API get_addr_from_stdin() is mainly used by socket client examples which read IP address from stdin (if configured).
+ This option is typically used in the CI, but could be enabled in the project configuration.
+ In that case this component is used to receive a string that is evaluated and processed to output
+ socket structures to open a connectio
+ 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.
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lwip/sys.h"
+#include
+#include
+
+/**
+ * @brief Read and evaluate IP address from stdin
+ *
+ * This API reads stdin and parses the input address using getaddrinfo()
+ * to fill in struct sockaddr_storage (for both IPv4 and IPv6) used to open
+ * a socket. IP protocol is guessed from the IP address string.
+ *
+ * @param[in] port port number of expected connection
+ * @param[in] sock_type expected protocol: SOCK_STREAM or SOCK_DGRAM
+ * @param[out] ip_protocol resultant IP protocol: IPPROTO_IP or IPPROTO_IP6
+ * @param[out] addr_family resultant address family: AF_INET or AF_INET6
+ * @param[out] dest_addr sockaddr_storage structure (for both IPv4 and IPv6)
+ * @return ESP_OK on success, ESP_FAIL otherwise
+ */
+esp_err_t get_addr_from_stdin(int port, int sock_type,
+ int *ip_protocol,
+ int *addr_family,
+ struct sockaddr_storage *dest_addr);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/common_components/protocol_examples_common/include/protocol_examples_common.h b/common_components/protocol_examples_common/include/protocol_examples_common.h
new file mode 100644
index 000000000..7afc78a74
--- /dev/null
+++ b/common_components/protocol_examples_common/include/protocol_examples_common.h
@@ -0,0 +1,92 @@
+/* Common functions for protocol examples, to establish Wi-Fi or Ethernet connection.
+
+ 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.
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "esp_err.h"
+#include "esp_netif.h"
+
+#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
+#define EXAMPLE_INTERFACE get_example_netif()
+#endif
+
+#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
+#define EXAMPLE_INTERFACE get_example_netif()
+#endif
+
+#if !defined (CONFIG_EXAMPLE_CONNECT_ETHERNET) && !defined (CONFIG_EXAMPLE_CONNECT_WIFI)
+// This is useful for some tests which do not need a network connection
+#define EXAMPLE_INTERFACE NULL
+#endif
+
+/**
+ * @brief Configure Wi-Fi or Ethernet, connect, wait for IP
+ *
+ * This all-in-one helper function is used in protocols examples to
+ * reduce the amount of boilerplate in the example.
+ *
+ * It is not intended to be used in real world applications.
+ * See examples under examples/wifi/getting_started/ and examples/ethernet/
+ * for more complete Wi-Fi or Ethernet initialization code.
+ *
+ * Read "Establishing Wi-Fi or Ethernet Connection" section in
+ * examples/protocols/README.md for more information about this function.
+ *
+ * @return ESP_OK on successful connection
+ */
+esp_err_t example_connect(void);
+
+/**
+ * Counterpart to example_connect, de-initializes Wi-Fi or Ethernet
+ */
+esp_err_t example_disconnect(void);
+
+/**
+ * @brief Configure stdin and stdout to use blocking I/O
+ *
+ * This helper function is used in ASIO examples. It wraps installing the
+ * UART driver and configuring VFS layer to use UART driver for console I/O.
+ */
+esp_err_t example_configure_stdin_stdout(void);
+
+/**
+ * @brief Returns esp-netif pointer created by example_connect()
+ *
+ * @note If multiple interfaces active at once, this API return NULL
+ * In that case the get_example_netif_from_desc() should be used
+ * to get esp-netif pointer based on interface description
+ */
+esp_netif_t *get_example_netif(void);
+
+/**
+ * @brief Returns esp-netif pointer created by example_connect() described by
+ * the supplied desc field
+ *
+ * @param desc Textual interface of created network interface, for example "sta"
+ * indicate default WiFi station, "eth" default Ethernet interface.
+ *
+ */
+esp_netif_t *get_example_netif_from_desc(const char *desc);
+
+#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
+/**
+ * @brief Get the example Ethernet driver handle
+ *
+ * @return esp_eth_handle_t
+ */
+esp_eth_handle_t get_example_eth_handle(void);
+#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/common_components/protocol_examples_common/stdin_out.c b/common_components/protocol_examples_common/stdin_out.c
new file mode 100644
index 000000000..b57e0e715
--- /dev/null
+++ b/common_components/protocol_examples_common/stdin_out.c
@@ -0,0 +1,29 @@
+/* Common functions for protocol examples, to configure stdin and stdout.
+
+ 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 "protocol_examples_common.h"
+#include "esp_err.h"
+#include "esp_vfs_dev.h"
+#include "driver/uart.h"
+#include "sdkconfig.h"
+
+esp_err_t example_configure_stdin_stdout(void)
+{
+ // Initialize VFS & UART so we can use std::cout/cin
+ setvbuf(stdin, NULL, _IONBF, 0);
+ /* Install UART driver for interrupt-driven reads and writes */
+ ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM,
+ 256, 0, 0, NULL, 0) );
+ /* Tell VFS to use UART driver */
+ esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
+ esp_vfs_dev_uart_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR);
+ /* Move the caret to the beginning of the next line on '\n' */
+ esp_vfs_dev_uart_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF);
+ return ESP_OK;
+}
diff --git a/components/mdns/CMakeLists.txt b/components/mdns/CMakeLists.txt
new file mode 100644
index 000000000..cb3ed4996
--- /dev/null
+++ b/components/mdns/CMakeLists.txt
@@ -0,0 +1,22 @@
+if(CONFIG_MDNS_NETWORKING_SOCKET)
+ set(MDNS_NETWORKING "mdns_networking_socket.c")
+else()
+ set(MDNS_NETWORKING "mdns_networking_lwip.c")
+endif()
+
+idf_build_get_property(target IDF_TARGET)
+if(${target} STREQUAL "linux")
+ set(dependencies esp_system_protocols_linux)
+ set(srcs "mdns.c" ${MDNS_NETWORKING})
+else()
+ set(dependencies lwip console esp_netif)
+ set(private_dependencies esp_timer)
+ set(srcs "mdns.c" ${MDNS_NETWORKING} "mdns_console.c")
+endif()
+
+idf_component_register(
+ SRCS ${srcs}
+ INCLUDE_DIRS "include"
+ PRIV_INCLUDE_DIRS "private_include"
+ REQUIRES ${dependencies}
+ PRIV_REQUIRES ${private_dependencies})
diff --git a/components/mdns/Kconfig b/components/mdns/Kconfig
new file mode 100644
index 000000000..def0d850d
--- /dev/null
+++ b/components/mdns/Kconfig
@@ -0,0 +1,127 @@
+menu "mDNS"
+
+ config MDNS_MAX_INTERFACES
+ int "Max number of interfaces"
+ range 1 9
+ default 3
+ help
+ Number of network interfaces to be served by the mDNS library.
+ Lowering this number helps to reduce some static RAM usage.
+
+ 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.
+
+ config MDNS_TASK_PRIORITY
+ int "mDNS task priority"
+ range 1 255
+ default 1
+ help
+ Allows setting mDNS task priority. Please do not set the task priority
+ higher than priorities of system tasks. Compile time warning/error
+ would be emitted if the chosen task priority were too high.
+
+ config MDNS_TASK_STACK_SIZE
+ int "mDNS task stack size"
+ default 4096
+ help
+ Allows setting mDNS task stacksize.
+
+ choice MDNS_TASK_AFFINITY
+ prompt "mDNS task affinity"
+ default MDNS_TASK_AFFINITY_CPU0
+ help
+ Allows setting mDNS tasks affinity, i.e. whether the task is pinned to
+ CPU0, pinned to CPU1, or allowed to run on any CPU.
+
+ config MDNS_TASK_AFFINITY_NO_AFFINITY
+ bool "No affinity"
+ config MDNS_TASK_AFFINITY_CPU0
+ bool "CPU0"
+ config MDNS_TASK_AFFINITY_CPU1
+ bool "CPU1"
+ depends on !FREERTOS_UNICORE
+
+ endchoice
+
+ config MDNS_TASK_AFFINITY
+ hex
+ default FREERTOS_NO_AFFINITY if MDNS_TASK_AFFINITY_NO_AFFINITY
+ default 0x0 if MDNS_TASK_AFFINITY_CPU0
+ default 0x1 if MDNS_TASK_AFFINITY_CPU1
+
+ config MDNS_SERVICE_ADD_TIMEOUT_MS
+ int "mDNS adding service timeout (ms)"
+ range 10 30000
+ default 2000
+ help
+ Configures timeout for adding a new mDNS service. Adding a service
+ fails if could not be completed within this time.
+
+ config MDNS_STRICT_MODE
+ bool "mDNS strict mode"
+ default "n"
+ help
+ Configures strict mode. Set this to 1 for the mDNS library to strictly follow the RFC6762:
+ Currently the only strict feature: Do not repeat original questions in response packets
+ (defined in RFC6762 sec. 6).
+ Default configuration is 0, i.e. non-strict mode, since some implementations,
+ such as lwIP mDNS resolver (used by standard POSIX API like getaddrinfo, gethostbyname)
+ could not correctly resolve advertised names.
+
+ config MDNS_TIMER_PERIOD_MS
+ int "mDNS timer period (ms)"
+ range 10 10000
+ default 100
+ help
+ Configures period of mDNS timer, which periodically transmits packets
+ and schedules mDNS searches.
+
+ config MDNS_NETWORKING_SOCKET
+ bool "Use BSD sockets for mDNS networking"
+ default n
+ help
+ Enables optional mDNS networking implementation using BSD sockets
+ in UDP multicast mode.
+ This option creates a new thread to serve receiving packets (TODO).
+ This option uses additional N sockets, where N is number of interfaces.
+
+ config MDNS_MULTIPLE_INSTANCE
+ bool "Multiple instances under the same service type"
+ default y
+ help
+ Enables adding multiple service instances under the same service type.
+
+ menu "MDNS Predefined interfaces"
+
+ config MDNS_PREDEF_NETIF_STA
+ bool "Use predefined interface for WiFi Station"
+ default y
+ help
+ Set up mDNS for the default WiFi station.
+ Disable this option if you do not need mDNS on default WiFi STA.
+
+ config MDNS_PREDEF_NETIF_AP
+ bool "Use predefined interface for WiFi Access Point"
+ default y
+ help
+ Set up mDNS for the default WiFi Access Point.
+ Disable this option if you do not need mDNS on default WiFi AP.
+
+ config MDNS_PREDEF_NETIF_ETH
+ bool "Use predefined interface for Ethernet"
+ depends on ETH_ENABLED
+ default y
+ help
+ Set up mDNS for the default Ethernet interface.
+ Disable this option if you do not need mDNS on default Ethernet.
+
+ endmenu # MDNS Predefined interfaces
+
+endmenu
diff --git a/components/mdns/README.md b/components/mdns/README.md
new file mode 100644
index 000000000..dd8678527
--- /dev/null
+++ b/components/mdns/README.md
@@ -0,0 +1,11 @@
+# mDNS Service
+
+mDNS is a multicast UDP service that is used to provide local network service and host discovery.
+
+## Examples
+
+Get started with example test [Example](examples/README.md):
+
+## Documentation
+
+* View the full [html documentation](https://espressif.github.io/esp-protocols/mdns/index.html)
diff --git a/components/mdns/docs/Doxyfile b/components/mdns/docs/Doxyfile
new file mode 100755
index 000000000..4c6b12bcc
--- /dev/null
+++ b/components/mdns/docs/Doxyfile
@@ -0,0 +1,75 @@
+# This is Doxygen configuration file
+#
+# Doxygen provides over 260 configuration statements
+# To make this file easier to follow,
+# it contains only statements that are non-default
+#
+# NOTE:
+# It is recommended not to change defaults unless specifically required
+# Test any changes how they affect generated documentation
+# Make sure that correct warnings are generated to flag issues with documented code
+#
+# For the complete list of configuration statements see:
+# http://doxygen.nl/manual/config.html
+
+
+PROJECT_NAME = "ESP Protocols Programming Guide"
+
+## The 'INPUT' statement below is used as input by script 'gen-df-input.py'
+## to automatically generate API reference list files heder_file.inc
+## These files are placed in '_inc' directory
+## and used to include in API reference documentation
+
+INPUT = \
+ $(PROJECT_PATH)/include/mdns.h
+
+## Get warnings for functions that have no documentation for their parameters or return value
+##
+WARN_NO_PARAMDOC = YES
+
+## Enable preprocessing and remove __attribute__(...) expressions from the INPUT files
+##
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = YES
+EXPAND_ONLY_PREDEF = YES
+PREDEFINED = \
+ $(ENV_DOXYGEN_DEFINES) \
+ __DOXYGEN__=1 \
+ __attribute__(x)= \
+ _Static_assert()= \
+ IDF_DEPRECATED(X)= \
+ IRAM_ATTR= \
+ configSUPPORT_DYNAMIC_ALLOCATION=1 \
+ configSUPPORT_STATIC_ALLOCATION=1 \
+ configQUEUE_REGISTRY_SIZE=1 \
+ configUSE_RECURSIVE_MUTEXES=1 \
+ configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS=1 \
+ configNUM_THREAD_LOCAL_STORAGE_POINTERS=1 \
+ configUSE_APPLICATION_TASK_TAG=1 \
+ configTASKLIST_INCLUDE_COREID=1 \
+ "ESP_EVENT_DECLARE_BASE(x)=extern esp_event_base_t x"
+
+## Do not complain about not having dot
+##
+HAVE_DOT = NO
+
+## Generate XML that is required for Breathe
+##
+GENERATE_XML = YES
+XML_OUTPUT = xml
+
+GENERATE_HTML = NO
+HAVE_DOT = NO
+GENERATE_LATEX = NO
+GENERATE_MAN = YES
+GENERATE_RTF = NO
+
+## Skip distracting progress messages
+##
+QUIET = YES
+
+## Enable Section Tags for conditional documentation
+##
+ENABLED_SECTIONS += \
+ DOC_EXCLUDE_HEADER_SECTION \ ## To conditionally remove doc sections from IDF source files without affecting documentation in upstream files.
+ DOC_SINGLE_GROUP ## To conditionally remove groups from the documentation and create a 'flat' document without affecting documentation in upstream files.
diff --git a/components/mdns/docs/conf_common.py b/components/mdns/docs/conf_common.py
new file mode 100644
index 000000000..10d1e997e
--- /dev/null
+++ b/components/mdns/docs/conf_common.py
@@ -0,0 +1,21 @@
+from esp_docs.conf_docs import * # noqa: F403,F401
+
+extensions += ['sphinx_copybutton',
+ # Needed as a trigger for running doxygen
+ 'esp_docs.esp_extensions.dummy_build_system',
+ 'esp_docs.esp_extensions.run_doxygen',
+ ]
+
+# link roles config
+github_repo = 'espressif/esp-protocols'
+
+# context used by sphinx_idf_theme
+html_context['github_user'] = 'espressif'
+html_context['github_repo'] = 'esp-protocols'
+
+# Extra options required by sphinx_idf_theme
+project_slug = 'esp-idf' # >=5.0
+versions_url = 'https://github.com/espressif/esp-protocols/docs/docs_versions.js'
+
+idf_targets = ['esp32']
+languages = ['en', 'zh_CN']
diff --git a/components/mdns/docs/en/conf.py b/components/mdns/docs/en/conf.py
new file mode 100644
index 000000000..bba642268
--- /dev/null
+++ b/components/mdns/docs/en/conf.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+#
+# English Language RTD & Sphinx config file
+#
+# Uses ../conf_common.py for most non-language-specific settings.
+
+# Importing conf_common adds all the non-language-specific
+# parts to this conf module
+
+try:
+ from conf_common import * # noqa: F403,F401
+except ImportError:
+ import os
+ import sys
+ sys.path.insert(0, os.path.abspath('../'))
+ from conf_common import * # noqa: F403,F401
+
+# General information about the project.
+project = u'ESP-Protocols'
+copyright = u'2016 - 2022, Espressif Systems (Shanghai) Co., Ltd'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+language = 'en'
diff --git a/components/mdns/docs/en/index.rst b/components/mdns/docs/en/index.rst
new file mode 100644
index 000000000..007d1bd9d
--- /dev/null
+++ b/components/mdns/docs/en/index.rst
@@ -0,0 +1,205 @@
+mDNS Service
+============
+:link_to_translation:`zh_CN:[中文]`
+
+Overview
+--------
+
+mDNS is a multicast UDP service that is used to provide local network service and host discovery.
+
+mDNS is installed by default on most operating systems or is available as separate package. On ``Mac OS`` it is installed by default and is called ``Bonjour``. Apple releases an installer for ``Windows`` that can be found `on Apple's support page `_. On ``Linux``, mDNS is provided by `avahi `_ and is usually installed by default.
+
+mDNS Properties
+^^^^^^^^^^^^^^^
+
+ * ``hostname``: the hostname that the device will respond to. If not set, the ``hostname`` will be read from the interface. Example: ``my-{IDF_TARGET_PATH_NAME}`` will resolve to ``my-{IDF_TARGET_PATH_NAME}.local``
+ * ``default_instance``: friendly name for your device, like ``Jhon's {IDF_TARGET_NAME} Thing``. If not set, ``hostname`` will be used.
+
+Example method to start mDNS for the STA interface and set ``hostname`` and ``default_instance``:
+
+.. highlight:: c
+
+::
+
+ void start_mdns_service()
+ {
+ //initialize mDNS service
+ esp_err_t err = mdns_init();
+ if (err) {
+ printf("MDNS Init failed: %d\n", err);
+ return;
+ }
+
+ //set hostname
+ mdns_hostname_set("my-{IDF_TARGET_PATH_NAME}");
+ //set default instance
+ mdns_instance_name_set("Jhon's {IDF_TARGET_NAME} Thing");
+ }
+
+mDNS Services
+^^^^^^^^^^^^^
+
+mDNS can advertise information about network services that your device offers. Each service is defined by a few properties.
+
+ * ``instance_name``: friendly name for your service, like ``Jhon's E{IDF_TARGET_NAME} Web Server``. If not defined, ``default_instance`` will be used.
+ * ``service_type``: (required) service type, prepended with underscore. Some common types can be found `here `_.
+ * ``proto``: (required) protocol that the service runs on, prepended with underscore. Example: ``_tcp`` or ``_udp``
+ * ``port``: (required) network port that the service runs on
+ * ``txt``: ``{var, val}`` array of strings, used to define properties for your service
+
+Example method to add a few services and different properties::
+
+ void add_mdns_services()
+ {
+ //add our services
+ mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0);
+ mdns_service_add(NULL, "_arduino", "_tcp", 3232, NULL, 0);
+ mdns_service_add(NULL, "_myservice", "_udp", 1234, NULL, 0);
+
+ //NOTE: services must be added before their properties can be set
+ //use custom instance for the web server
+ mdns_service_instance_name_set("_http", "_tcp", "Jhon's {IDF_TARGET_NAME} Web Server");
+
+ mdns_txt_item_t serviceTxtData[3] = {
+ {"board","{{IDF_TARGET_PATH_NAME}}"},
+ {"u","user"},
+ {"p","password"}
+ };
+ //set txt data for service (will free and replace current data)
+ mdns_service_txt_set("_http", "_tcp", serviceTxtData, 3);
+
+ //change service port
+ mdns_service_port_set("_myservice", "_udp", 4321);
+ }
+
+mDNS Query
+^^^^^^^^^^
+
+mDNS provides methods for browsing for services and resolving host's IP/IPv6 addresses.
+
+Results for services are returned as a linked list of ``mdns_result_t`` objects.
+
+Example method to resolve host IPs::
+
+ void resolve_mdns_host(const char * host_name)
+ {
+ printf("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){
+ printf("Host was not found!");
+ return;
+ }
+ printf("Query Failed");
+ return;
+ }
+
+ printf(IPSTR, IP2STR(&addr));
+ }
+
+Example method to resolve local services::
+
+ static const char * if_str[] = {"STA", "AP", "ETH", "MAX"};
+ static const char * ip_protocol_str[] = {"V4", "V6", "MAX"};
+
+ 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;
+ }
+
+ }
+
+ void find_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);
+ }
+
+Example of using the methods above::
+
+ void my_app_some_method(){
+ //search for {IDF_TARGET_PATH_NAME}-mdns.local
+ resolve_mdns_host("{IDF_TARGET_PATH_NAME}-mdns");
+
+ //search for HTTP servers
+ find_mdns_service("_http", "_tcp");
+ //or file servers
+ find_mdns_service("_smb", "_tcp"); //windows sharing
+ find_mdns_service("_afpovertcp", "_tcp"); //apple sharing
+ find_mdns_service("_nfs", "_tcp"); //NFS server
+ find_mdns_service("_ftp", "_tcp"); //FTP server
+ //or networked printer
+ find_mdns_service("_printer", "_tcp");
+ find_mdns_service("_ipp", "_tcp");
+ }
+
+
+Performance Optimization
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+Execution Speed
+^^^^^^^^^^^^^^^
+
+ - mDNS creates a task with default low priority 1 ``CONFIG_MDNS_TASK_PRIORITY`` (If ``CONFIG_FREERTOS_UNICORE`` enabeled it pinned to CPU0 (``CONFIG_MDNS_TASK_AFFINITY``).
+ Please check `Maximizing Execution Speed `_ for more details.
+
+Minimizing RAM Usage
+^^^^^^^^^^^^^^^^^^^^
+
+- mDNS creates a tasks with stack sizes configured by ``CONFIG_MDNS_TASK_STACK_SIZE``.
+Please check `Minimizing RAM Usage `_ for more details.
+
+Application Example
+-------------------
+
+mDNS server/scanner example: :example:`<../examples>`.
+
+API Reference
+-------------
+
+.. include-build-file:: inc/mdns.inc
+
+
diff --git a/components/mdns/docs/generate_docs b/components/mdns/docs/generate_docs
new file mode 100755
index 000000000..d822222b5
--- /dev/null
+++ b/components/mdns/docs/generate_docs
@@ -0,0 +1,28 @@
+build-docs --target esp32 --language en
+build-docs --target esp32 --language zh_CN
+
+cp -rf _build/en/esp32/html html_en
+cp -rf _build/zh_CN/esp32/html html_zh_CN
+rm -rf _build __pycache__ tee
+
+# Modifes some version and target fields of index.html
+echo "" | tee -a html_en/index.html html_zh_CN/index.html > /dev/null
diff --git a/components/mdns/docs/zh_CN/conf.py b/components/mdns/docs/zh_CN/conf.py
new file mode 100644
index 000000000..cc6fbc271
--- /dev/null
+++ b/components/mdns/docs/zh_CN/conf.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+# English Language RTD & Sphinx config file
+#
+# Uses ../conf_common.py for most non-language-specific settings.
+
+# Importing conf_common adds all the non-language-specific
+# parts to this conf module
+try:
+ from conf_common import * # noqa: F403,F401
+except ImportError:
+ import os
+ import sys
+ sys.path.insert(0, os.path.abspath('..'))
+ from conf_common import * # noqa: F403,F401
+
+import datetime
+
+current_year = datetime.datetime.now().year
+
+# General information about the project.
+project = u'ESP-IDF 编程指南'
+copyright = u'2016 - {} 乐鑫信息科技(上海)股份有限公司'.format(current_year)
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+language = 'zh_CN'
diff --git a/components/mdns/docs/zh_CN/index.rst b/components/mdns/docs/zh_CN/index.rst
new file mode 100644
index 000000000..68befb637
--- /dev/null
+++ b/components/mdns/docs/zh_CN/index.rst
@@ -0,0 +1,189 @@
+mDNS 服务
+=========
+:link_to_translation:`en:[English]`
+
+概述
+----
+
+mDNS 是一种组播 UDP 服务,用来提供本地网络服务和主机发现。
+
+绝大多数的操作系统默认都会安装 mDNS 服务,或者提供单独的安装包。``Mac OS`` 默认会安装名为 ``Bonjour`` 的服务(该服务基于 mDNS),此外 Apple 还发布了适用于 Windows 系统的安装程序,可以在 `官方支持 `_ 找到。在 ``Linux`` 上,mDNS 服务由 `avahi `_ 提供,通常也会被默认安装。
+
+mDNS 属性
+^^^^^^^^^
+
+ * ``hostname``:设备会去响应的主机名,如果没有设置,会根据设备的网络接口名定义 ``hostname`` 。例如,``my-{IDF_TARGET_PATH_NAME}`` 会被解析为 ``my-{IDF_TARGET_PATH_NAME}.local``。
+ * ``default_instance``:默认实例名(即易记的设备名),例如 ``Jhon's {IDF_TARGET_NAME} Thing``。如果没有设置,将会使用 ``hostname``。
+
+以下为 STA 接口启动 mDNS 服务并设置 ``hostname`` 和 ``default_instance`` 的示例方法:
+
+.. highlight:: c
+
+::
+
+ void start_mdns_service()
+ {
+ // 初始化 mDNS 服务
+ esp_err_t err = mdns_init();
+ if (err) {
+ printf("MDNS Init failed: %d\n", err);
+ return;
+ }
+
+ // 设置 hostname
+ mdns_hostname_set("my-{IDF_TARGET_PATH_NAME}");
+ // 设置默认实例
+ mdns_instance_name_set("Jhon's {IDF_TARGET_NAME} Thing");
+ }
+
+mDNS 服务
+^^^^^^^^^
+
+mDNS 可以广播设备能够提供的网络服务的相关信息,每个服务会由以下属性构成。
+
+ * ``instance_name``:实例名(即易记的服务名),例如 ``Jhon's {IDF_TARGET_NAME} Web Server``。如果没有定义,会使用 ``default_instance``。
+ * ``service_type``:(必需)服务类型,以下划线为前缀,`这里 `_ 列出了常见的类型。
+ * ``proto``:(必需)服务运行所依赖的协议,以下划线为前缀,例如 ``_tcp`` 或者 ``_udp``。
+ * ``port``:(必需)服务运行所用的端口号。
+ * ``txt``:形如 ``{var, val}`` 的字符串数组,用于定义服务的属性。
+
+添加一些服务和不同属性的示例方法::
+
+ void add_mdns_services()
+ {
+ // 添加服务
+ mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0);
+ mdns_service_add(NULL, "_arduino", "_tcp", 3232, NULL, 0);
+ mdns_service_add(NULL, "_myservice", "_udp", 1234, NULL, 0);
+
+ // 注意:必须先添加服务,然后才能设置其属性
+ // web 服务器使用自定义的实例名
+ mdns_service_instance_name_set("_http", "_tcp", "Jhon's {IDF_TARGET_NAME} Web Server");
+
+ mdns_txt_item_t serviceTxtData[3] = {
+ {"board","{IDF_TARGET_PATH_NAME}"},
+ {"u","user"},
+ {"p","password"}
+ };
+ // 设置服务的文本数据(会释放并替换当前数据)
+ mdns_service_txt_set("_http", "_tcp", serviceTxtData, 3);
+
+ // 修改服务端口号
+ mdns_service_port_set("_myservice", "_udp", 4321);
+ }
+
+mDNS 查询
+^^^^^^^^^
+
+mDNS 提供查询服务和解析主机 IP/IPv6 地址的方法。
+
+服务查询的结果会作为 ``mdns_result_t`` 类型对象的链表返回。
+
+解析主机 IP 地址的示例方法::
+
+ void resolve_mdns_host(const char * host_name)
+ {
+ printf("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){
+ printf("Host was not found!");
+ return;
+ }
+ printf("Query Failed");
+ return;
+ }
+
+ printf(IPSTR, IP2STR(&addr));
+ }
+
+解析本地服务的示例方法::
+
+ static const char * if_str[] = {"STA", "AP", "ETH", "MAX"};
+ static const char * ip_protocol_str[] = {"V4", "V6", "MAX"};
+
+ 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;
+ }
+
+ }
+
+ void find_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);
+ }
+
+使用上述方法的示例::
+
+ void my_app_some_method(){
+ // 搜索 {IDF_TARGET_PATH_NAME}-mdns.local
+ resolve_mdns_host("{IDF_TARGET_PATH_NAME}-mdns");
+
+ // 搜索 HTTP 服务器
+ find_mdns_service("_http", "_tcp");
+ // 或者搜索文件服务器
+ find_mdns_service("_smb", "_tcp"); // Windows 系统的共享服务
+ find_mdns_service("_afpovertcp", "_tcp"); // Apple AFP 文件共享服务
+ find_mdns_service("_nfs", "_tcp"); // NFS 服务器
+ find_mdns_service("_ftp", "_tcp"); // FTP 服务器
+ // 或者网络打印机
+ find_mdns_service("_printer", "_tcp");
+ find_mdns_service("_ipp", "_tcp");
+ }
+
+应用示例
+--------
+
+有关 mDNS 服务器和查询器的应用示例请参考 :example:`<../examples>`。
+
+API 参考
+--------
+
+.. include-build-file:: inc/mdns.inc
+
+
diff --git a/components/mdns/examples/CMakeLists.txt b/components/mdns/examples/CMakeLists.txt
new file mode 100644
index 000000000..9461155a1
--- /dev/null
+++ b/components/mdns/examples/CMakeLists.txt
@@ -0,0 +1,10 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+set(EXTRA_COMPONENT_DIRS "../..")
+# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
+list(APPEND EXTRA_COMPONENT_DIRS "../../../common_components/protocol_examples_common")
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(mdns_test)
diff --git a/components/mdns/examples/README.md b/components/mdns/examples/README.md
new file mode 100644
index 000000000..04047a6f7
--- /dev/null
+++ b/components/mdns/examples/README.md
@@ -0,0 +1,91 @@
+# 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 the project configuration and `_http._tcp` service is added to be advertised
+- A delegated host `esp32-delegated._local` is added and another `_http._tcp` service is added for this host.
+- WiFi STA is started and trying to connect to the access point defined through the project configuration
+- 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
+
+### Configure the project
+
+* Open the project configuration menu (`idf.py menuconfig`)
+
+* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu
+* Set `mDNS Hostname` as host name prefix for the device and its instance name in `mDNS Instance Name`
+* Disable `Resolve test services` to prevent the example from querying defined names/services on startup (cause warnings in example logs, as illustrated below)
+
+### Build and Flash
+
+Build the project and flash it to the board, then run monitor tool to view serial output:
+
+```
+idf.py -p PORT flash monitor
+```
+
+- Wait for WiFi to connect to your access point
+- You can now ping the device at `[board-hostname].local`, where `[board-hostname]` is preconfigured hostname, `esp32-mdns` by default.
+- You can also browse for `_http._tcp` on the same network to find the advertised service
+- Pressing the BOOT button will start querying the local network for the predefined in `check_button` hosts and services
+- Note that for purpose of CI tests, configuration options of `MDNS_RESOLVE_TEST_SERVICES` and `MDNS_ADD_MAC_TO_HOSTNAME` are available, but disabled by default. If enabled, then the hostname suffix of last 3 bytes from device MAC address is added, e.g. `esp32-mdns-80FFFF`, and a query for test service is issued.
+
+
+(To exit the serial monitor, type ``Ctrl-]``.)
+
+See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
+
+## Example Output
+```
+I (0) cpu_start: Starting scheduler on APP CPU.
+I (276) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
+I (276) mdns-test: mdns hostname set to: [esp32-mdns]
+I (286) wifi: wifi driver task: 3ffc2fa4, prio:23, stack:3584, core=0
+I (286) wifi: wifi firmware version: a3be639
+I (286) wifi: config NVS flash: enabled
+I (296) wifi: config nano formating: disabled
+I (296) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
+I (306) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
+I (336) wifi: Init dynamic tx buffer num: 32
+I (336) wifi: Init data frame dynamic rx buffer num: 32
+I (336) wifi: Init management frame dynamic rx buffer num: 32
+I (346) wifi: Init static rx buffer size: 1600
+I (346) wifi: Init static rx buffer num: 10
+I (346) wifi: Init dynamic rx buffer num: 32
+I (356) mdns-test: Setting WiFi configuration SSID myssid...
+I (426) phy: phy_version: 4000, b6198fa, Sep 3 2018, 15:11:06, 0, 0
+I (426) wifi: mode : sta (30:ae:a4:80:FF:FF)
+I (426) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
+I (1756) wifi: n:11 0, o:1 0, ap:255 255, sta:11 0, prof:1
+I (2736) wifi: state: init -> auth (b0)
+I (2756) wifi: state: auth -> assoc (0)
+I (2766) wifi: state: assoc -> run (10)
+I (2786) wifi: connected with myssid, channel 11
+I (2786) wifi: pm start, type: 1
+
+I (4786) event: sta ip: 192.168.0.139, mask: 255.255.255.0, gw: 192.168.0.2
+I (21126) mdns-test: Query A: esp32.local
+W (23176) mdns-test: ESP_ERR_NOT_FOUND: Host was not found!
+I (23176) mdns-test: Query PTR: _arduino._tcp.local
+W (26276) mdns-test: No results found!
+I (26276) mdns-test: Query PTR: _http._tcp.local
+1: Interface: STA, Type: V6
+ PTR : HP Color LaserJet MFP M277dw (7C2E10)
+ SRV : NPI7C2E10.local:80
+ A : 254.128.0.0
+2: Interface: STA, Type: V4
+ PTR : switch4e4919
+ SRV : switch4e4919.local:80
+ TXT : [1] path=/config/authentication_page.htm;
+ A : 192.168.0.118
+I (29396) mdns-test: Query PTR: _printer._tcp.local
+1: Interface: STA, Type: V6
+ PTR : HP Color LaserJet MFP M277dw (7C2E10)
+ SRV : NPI7C2E10.local:515
+ A : 254.128.0.0
+2: Interface: STA, Type: V4
+ PTR : HP Color LaserJet MFP M277dw (7C2E10)
+```
diff --git a/components/mdns/examples/main/CMakeLists.txt b/components/mdns/examples/main/CMakeLists.txt
new file mode 100644
index 000000000..eb0e6d9eb
--- /dev/null
+++ b/components/mdns/examples/main/CMakeLists.txt
@@ -0,0 +1,2 @@
+idf_component_register(SRCS "mdns_example_main.c"
+ INCLUDE_DIRS ".")
diff --git a/components/mdns/examples/main/Kconfig.projbuild b/components/mdns/examples/main/Kconfig.projbuild
new file mode 100644
index 000000000..18e1cc209
--- /dev/null
+++ b/components/mdns/examples/main/Kconfig.projbuild
@@ -0,0 +1,55 @@
+menu "Example Configuration"
+
+ orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
+
+ 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
+
+ config MDNS_PUBLISH_DELEGATE_HOST
+ bool "Publish a delegated host"
+ help
+ Enable publishing a delegated host other than ESP32.
+ The example will also add a mock service for this host.
+
+ config MDNS_RESOLVE_TEST_SERVICES
+ bool "Resolve test services"
+ default n
+ help
+ Enable resolving test services on startup.
+ These services are advertized and evaluated in automated tests.
+ When executed locally, these will not be resolved and warnings appear in the log.
+ Please set to false to disable initial querying to avoid warnings.
+
+ config MDNS_ADD_MAC_TO_HOSTNAME
+ bool "Add mac suffix to hostname"
+ default n
+ help
+ If enabled, a portion of MAC address is added to the hostname, this is used
+ for evaluation of tests in CI
+
+ config MDNS_BUTTON_GPIO
+ int "Button GPIO to trigger querries"
+ range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
+ default 0
+ help
+ Set the GPIO number used as mDNS test button
+
+ config MDNS_ADD_CUSTOM_NETIF
+ bool "Add user netif to mdns service"
+ default n
+ help
+ If enabled, we try to add a custom netif to mdns service.
+ Note that for using with common connection example code, we have to disable
+ all predefined interfaces in mdns component setup (since we're adding one
+ of the default interfaces)
+
+endmenu
diff --git a/components/mdns/examples/main/mdns_example_main.c b/components/mdns/examples/main/mdns_example_main.c
new file mode 100644
index 000000000..da0cbe53b
--- /dev/null
+++ b/components/mdns/examples/main/mdns_example_main.c
@@ -0,0 +1,343 @@
+/* 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 "esp_netif_ip_addr.h"
+#include "esp_mac.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "nvs_flash.h"
+#include "esp_netif.h"
+#include "protocol_examples_common.h"
+#include "mdns.h"
+#include "driver/gpio.h"
+#include "netdb.h"
+
+
+#define EXAMPLE_MDNS_INSTANCE CONFIG_MDNS_INSTANCE
+#define EXAMPLE_BUTTON_GPIO CONFIG_MDNS_BUTTON_GPIO
+
+static const char * TAG = "mdns-test";
+static char * generate_hostname(void);
+
+#if CONFIG_MDNS_RESOLVE_TEST_SERVICES == 1
+static void query_mdns_host_with_gethostbyname(char * host);
+static void query_mdns_host_with_getaddrinfo(char * host);
+#endif
+
+static void initialise_mdns(void)
+{
+ char * hostname = generate_hostname();
+
+ //initialize mDNS
+ ESP_ERROR_CHECK( mdns_init() );
+ //set mDNS hostname (required if you want to advertise services)
+ ESP_ERROR_CHECK( mdns_hostname_set(hostname) );
+ ESP_LOGI(TAG, "mdns hostname set to: [%s]", 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) );
+ ESP_ERROR_CHECK( mdns_service_subtype_add_for_host("ESP32-WebServer", "_http", "_tcp", NULL, "_server") );
+#if CONFIG_MDNS_MULTIPLE_INSTANCE
+ ESP_ERROR_CHECK( mdns_service_add("ESP32-WebServer1", "_http", "_tcp", 80, NULL, 0) );
+#endif
+
+#if CONFIG_MDNS_PUBLISH_DELEGATE_HOST
+ char *delegated_hostname;
+ if (-1 == asprintf(&delegated_hostname, "%s-delegated", hostname)) {
+ abort();
+ }
+
+ mdns_ip_addr_t addr4, addr6;
+ esp_netif_str_to_ip4("10.0.0.1", &addr4.addr.u_addr.ip4);
+ addr4.addr.type = ESP_IPADDR_TYPE_V4;
+ esp_netif_str_to_ip6("fd11:22::1", &addr6.addr.u_addr.ip6);
+ addr6.addr.type = ESP_IPADDR_TYPE_V6;
+ addr4.next = &addr6;
+ addr6.next = NULL;
+ ESP_ERROR_CHECK( mdns_delegate_hostname_add(delegated_hostname, &addr4) );
+ ESP_ERROR_CHECK( mdns_service_add_for_host("test0", "_http", "_tcp", delegated_hostname, 1234, serviceTxtData, 3) );
+ free(delegated_hostname);
+#endif // CONFIG_MDNS_PUBLISH_DELEGATE_HOST
+
+ //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_with_explicit_value_len("_http", "_tcp", "u", "admin", strlen("admin")) );
+ free(hostname);
+}
+
+/* these strings match mdns_ip_protocol_t enumeration */
+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, TTL: %u\n", i++, esp_netif_get_ifkey(r->esp_netif), ip_protocol_str[r->ip_protocol],
+ r->ttl);
+ if (r->instance_name) {
+ printf(" PTR : %s.%s.%s\n", r->instance_name, r->service_type, r->proto);
+ }
+ if (r->hostname) {
+ printf(" SRV : %s.local:%u\n", r->hostname, r->port);
+ }
+ if (r->txt_count) {
+ printf(" TXT : [%zu] ", r->txt_count);
+ for (t = 0; t < r->txt_count; t++) {
+ printf("%s=%s(%d); ", r->txt[t].key, r->txt[t].value ? r->txt[t].value : "NULL", r->txt_value_len[t]);
+ }
+ printf("\n");
+ }
+ a = r->addr;
+ while (a) {
+ if (a->addr.type == ESP_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 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: %s", esp_err_to_name(err));
+ return;
+ }
+ if(!results){
+ ESP_LOGW(TAG, "No results found!");
+ return;
+ }
+
+ mdns_print_results(results);
+ mdns_query_results_free(results);
+}
+
+static bool check_and_print_result(mdns_search_once_t *search)
+{
+ // Check if any result is available
+ mdns_result_t * result = NULL;
+ if (!mdns_query_async_get_results(search, 0, &result, NULL)) {
+ return false;
+ }
+
+ if (!result) { // search timeout, but no result
+ return true;
+ }
+
+ // If yes, print the result
+ mdns_ip_addr_t * a = result->addr;
+ while (a) {
+ if(a->addr.type == ESP_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;
+ }
+ // and free the result
+ mdns_query_results_free(result);
+ return true;
+}
+
+static void query_mdns_hosts_async(const char * host_name)
+{
+ ESP_LOGI(TAG, "Query both A and AAA: %s.local", host_name);
+
+ mdns_search_once_t *s_a = mdns_query_async_new(host_name, NULL, NULL, MDNS_TYPE_A, 1000, 1, NULL);
+ mdns_search_once_t *s_aaaa = mdns_query_async_new(host_name, NULL, NULL, MDNS_TYPE_AAAA, 1000, 1, NULL);
+ while (s_a || s_aaaa) {
+ if (s_a && check_and_print_result(s_a)) {
+ ESP_LOGI(TAG, "Query A %s.local finished", host_name);
+ mdns_query_async_delete(s_a);
+ s_a = NULL;
+ }
+ if (s_aaaa && check_and_print_result(s_aaaa)) {
+ ESP_LOGI(TAG, "Query AAAA %s.local finished", host_name);
+ mdns_query_async_delete(s_aaaa);
+ s_aaaa = NULL;
+ }
+ vTaskDelay(50 / portTICK_PERIOD_MS);
+ }
+}
+
+static void query_mdns_host(const char * host_name)
+{
+ ESP_LOGI(TAG, "Query A: %s.local", host_name);
+
+ struct esp_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, "%s: Host was not found!", esp_err_to_name(err));
+ return;
+ }
+ ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err));
+ return;
+ }
+
+ ESP_LOGI(TAG, "Query A: %s.local resolved to: " IPSTR, host_name, IP2STR(&addr));
+}
+
+static void initialise_button(void)
+{
+ gpio_config_t io_conf = {0};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.pin_bit_mask = BIT64(EXAMPLE_BUTTON_GPIO);
+ 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(EXAMPLE_BUTTON_GPIO);
+ if (!new_level && old_level) {
+ query_mdns_hosts_async("esp32-mdns");
+ 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)
+{
+#if CONFIG_MDNS_RESOLVE_TEST_SERVICES == 1
+ /* Send initial queries that are started by CI tester */
+ query_mdns_host("tinytester");
+ query_mdns_host_with_gethostbyname("tinytester-lwip.local");
+ query_mdns_host_with_getaddrinfo("tinytester-lwip.local");
+#endif
+
+ while (1) {
+ check_button();
+ vTaskDelay(50 / portTICK_PERIOD_MS);
+ }
+}
+
+void app_main(void)
+{
+ ESP_ERROR_CHECK(nvs_flash_init());
+ ESP_ERROR_CHECK(esp_netif_init());
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ initialise_mdns();
+
+ /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
+ * Read "Establishing Wi-Fi or Ethernet Connection" section in
+ * examples/protocols/README.md for more information about this function.
+ */
+ ESP_ERROR_CHECK(example_connect());
+
+#if defined(CONFIG_MDNS_ADD_CUSTOM_NETIF) && !defined(CONFIG_MDNS_PREDEF_NETIF_STA) && !defined(CONFIG_MDNS_PREDEF_NETIF_ETH)
+ /* Demonstration of adding a custom netif to mdns service, but we're adding the default example one,
+ * so we must disable all predefined interfaces (PREDEF_NETIF_STA, AP and ETH) first
+ */
+ ESP_ERROR_CHECK(mdns_register_netif(EXAMPLE_INTERFACE));
+ /* It is not enough to just register the interface, we have to enable is manually.
+ * This is typically performed in "GOT_IP" event handler, but we call it here directly
+ * since the `EXAMPLE_INTERFACE` netif is connected already, to keep the example simple.
+ */
+ ESP_ERROR_CHECK(mdns_netif_action(EXAMPLE_INTERFACE, MDNS_EVENT_ENABLE_IP4));
+ ESP_ERROR_CHECK(mdns_netif_action(EXAMPLE_INTERFACE, MDNS_EVENT_ANNOUNCE_IP4));
+#endif
+ initialise_button();
+ xTaskCreate(&mdns_example_task, "mdns_example_task", 2048, NULL, 5, NULL);
+}
+
+/** Generate host name based on sdkconfig, optionally adding a portion of MAC address to it.
+ * @return host name string allocated from the heap
+ */
+static char* generate_hostname(void)
+{
+#ifndef CONFIG_MDNS_ADD_MAC_TO_HOSTNAME
+ return strdup(CONFIG_MDNS_HOSTNAME);
+#else
+ uint8_t mac[6];
+ char *hostname;
+ esp_read_mac(mac, ESP_MAC_WIFI_STA);
+ if (-1 == asprintf(&hostname, "%s-%02X%02X%02X", CONFIG_MDNS_HOSTNAME, mac[3], mac[4], mac[5])) {
+ abort();
+ }
+ return hostname;
+#endif
+}
+
+#if CONFIG_MDNS_RESOLVE_TEST_SERVICES == 1
+/**
+ * @brief Executes gethostbyname and displays list of resolved addresses.
+ * Note: This function is used only to test advertised mdns hostnames resolution
+ */
+static void query_mdns_host_with_gethostbyname(char * host)
+{
+ struct hostent *res = gethostbyname(host);
+ if (res) {
+ unsigned int i = 0;
+ while (res->h_addr_list[i] != NULL) {
+ ESP_LOGI(TAG, "gethostbyname: %s resolved to: %s", host, inet_ntoa(*(struct in_addr *) (res->h_addr_list[i])));
+ i++;
+ }
+ }
+}
+
+/**
+ * @brief Executes getaddrinfo and displays list of resolved addresses.
+ * Note: This function is used only to test advertised mdns hostnames resolution
+ */
+static void query_mdns_host_with_getaddrinfo(char * host)
+{
+ struct addrinfo hints;
+ struct addrinfo * res;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ if (!getaddrinfo(host, NULL, &hints, &res)) {
+ while (res) {
+ ESP_LOGI(TAG, "getaddrinfo: %s resolved to: %s", host,
+ res->ai_family == AF_INET?
+ inet_ntoa(((struct sockaddr_in *) res->ai_addr)->sin_addr):
+ inet_ntoa(((struct sockaddr_in6 *) res->ai_addr)->sin6_addr));
+ res = res->ai_next;
+ }
+ }
+}
+#endif
diff --git a/components/mdns/examples/mdns_example_test.py b/components/mdns/examples/mdns_example_test.py
new file mode 100644
index 000000000..83763634e
--- /dev/null
+++ b/components/mdns/examples/mdns_example_test.py
@@ -0,0 +1,166 @@
+import os
+import re
+import select
+import socket
+import struct
+import subprocess
+import time
+from threading import Event, Thread
+
+import dpkt
+import dpkt.dns
+import ttfw_idf
+from tiny_test_fw import DUT
+from tiny_test_fw.Utility import console_log
+
+
+def get_dns_query_for_esp(esp_host):
+ dns = dpkt.dns.DNS(b'\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01')
+ dns.qd[0].name = esp_host + u'.local'
+ console_log('Created query for esp host: {} '.format(dns.__repr__()))
+ return dns.pack()
+
+
+def get_dns_answer_to_mdns(tester_host):
+ dns = dpkt.dns.DNS(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
+ dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
+ dns.rcode = dpkt.dns.DNS_RCODE_NOERR
+ arr = dpkt.dns.DNS.RR()
+ arr.cls = dpkt.dns.DNS_IN
+ arr.type = dpkt.dns.DNS_A
+ arr.name = tester_host
+ arr.ip = socket.inet_aton('127.0.0.1')
+ dns.an.append(arr)
+ console_log('Created answer to mdns query: {} '.format(dns.__repr__()))
+ return dns.pack()
+
+
+def get_dns_answer_to_mdns_lwip(tester_host, id):
+ dns = dpkt.dns.DNS(b'\x5e\x39\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x0a\x64\x61\x76\x69\x64'
+ b'\x2d\x63\x6f\x6d\x70\x05\x6c\x6f\x63\x61\x6c\x00\x00\x01\x00\x01\xc0\x0c'
+ b'\x00\x01\x00\x01\x00\x00\x00\x0a\x00\x04\xc0\xa8\x0a\x6c')
+ dns.qd[0].name = tester_host
+ dns.an[0].name = tester_host
+ dns.an[0].ip = socket.inet_aton('127.0.0.1')
+ dns.an[0].rdata = socket.inet_aton('127.0.0.1')
+ dns.id = id
+ print('Created answer to mdns (lwip) query: {} '.format(dns.__repr__()))
+ return dns.pack()
+
+
+def mdns_server(esp_host, events):
+ UDP_IP = '0.0.0.0'
+ UDP_PORT = 5353
+ MCAST_GRP = '224.0.0.251'
+ TESTER_NAME = u'tinytester.local'
+ TESTER_NAME_LWIP = u'tinytester-lwip.local'
+ QUERY_TIMEOUT = 0.2
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ sock.setblocking(False)
+ sock.bind((UDP_IP, UDP_PORT))
+ mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
+ sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
+ last_query_timepoint = time.time()
+ while not events['stop'].is_set():
+ try:
+ current_time = time.time()
+ if current_time - last_query_timepoint > QUERY_TIMEOUT:
+ last_query_timepoint = current_time
+ if not events['esp_answered'].is_set():
+ sock.sendto(get_dns_query_for_esp(esp_host), (MCAST_GRP, UDP_PORT))
+ if not events['esp_delegated_answered'].is_set():
+ sock.sendto(get_dns_query_for_esp(esp_host + '-delegated'), (MCAST_GRP, UDP_PORT))
+ timeout = max(0, QUERY_TIMEOUT - (current_time - last_query_timepoint))
+ read_socks, _, _ = select.select([sock], [], [], timeout)
+ if not read_socks:
+ continue
+ data, addr = sock.recvfrom(1024)
+ dns = dpkt.dns.DNS(data)
+ if len(dns.qd) > 0 and dns.qd[0].type == dpkt.dns.DNS_A:
+ if dns.qd[0].name == TESTER_NAME:
+ console_log('Received query: {} '.format(dns.__repr__()))
+ sock.sendto(get_dns_answer_to_mdns(TESTER_NAME), (MCAST_GRP, UDP_PORT))
+ elif dns.qd[0].name == TESTER_NAME_LWIP:
+ console_log('Received query: {} '.format(dns.__repr__()))
+ sock.sendto(get_dns_answer_to_mdns_lwip(TESTER_NAME_LWIP, dns.id), addr)
+ if len(dns.an) > 0 and dns.an[0].type == dpkt.dns.DNS_A:
+ console_log('Received answer from {}'.format(dns.an[0].name))
+ if dns.an[0].name == esp_host + u'.local':
+ console_log('Received answer to esp32-mdns query: {}'.format(dns.__repr__()))
+ events['esp_answered'].set()
+ if dns.an[0].name == esp_host + u'-delegated.local':
+ console_log('Received answer to esp32-mdns-delegate query: {}'.format(dns.__repr__()))
+ events['esp_delegated_answered'].set()
+ except socket.timeout:
+ break
+ except dpkt.UnpackError:
+ continue
+
+
+def test_examples_protocol_mdns(env, config):
+ """
+ steps: |
+ 1. obtain IP address + init mdns example
+ 2. get the dut host name (and IP address)
+ 3. check the mdns name is accessible
+ 4. check DUT output if mdns advertized host is resolved
+ """
+ dut1 = env.get_dut('mdns-test', 'examples/protocols/mdns', dut_class=ttfw_idf.ESP32DUT, app_config_name=config)
+ # check and log bin size
+ binary_file = os.path.join(dut1.app.binary_path, 'mdns_test.bin')
+ bin_size = os.path.getsize(binary_file)
+ ttfw_idf.log_performance('mdns-test_bin_size', '{}KB'.format(bin_size // 1024))
+ # 1. start mdns application
+ dut1.start_app()
+ # 2. get the dut host name (and IP address)
+ specific_host = dut1.expect(re.compile(r'mdns hostname set to: \[([^\]]+)\]'), timeout=30)[0]
+
+ mdns_server_events = {'stop': Event(), 'esp_answered': Event(), 'esp_delegated_answered': Event()}
+ mdns_responder = Thread(target=mdns_server, args=(str(specific_host), mdns_server_events))
+ try:
+ ip_address = dut1.expect(re.compile(r' eth ip: ([^,]+),'), timeout=30)[0]
+ console_log('Connected to AP with IP: {}'.format(ip_address))
+ except DUT.ExpectTimeout:
+ raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+ try:
+ # 3. check the mdns name is accessible
+ mdns_responder.start()
+ if not mdns_server_events['esp_answered'].wait(timeout=30):
+ raise ValueError('Test has failed: did not receive mdns answer within timeout')
+ if not mdns_server_events['esp_delegated_answered'].wait(timeout=30):
+ raise ValueError('Test has failed: did not receive mdns answer for delegated host within timeout')
+ # 4. check DUT output if mdns advertized host is resolved
+ dut1.expect(re.compile(r'mdns-test: Query A: tinytester.local resolved to: 127.0.0.1'), timeout=30)
+ dut1.expect(re.compile(r'mdns-test: gethostbyname: tinytester-lwip.local resolved to: 127.0.0.1'), timeout=30)
+ dut1.expect(re.compile(r'mdns-test: getaddrinfo: tinytester-lwip.local resolved to: 127.0.0.1'), timeout=30)
+ # 5. check the DUT answers to `dig` command
+ dig_output = subprocess.check_output(['dig', '+short', '-p', '5353', '@224.0.0.251',
+ '{}.local'.format(specific_host)])
+ console_log('Resolving {} using "dig" succeeded with:\n{}'.format(specific_host, dig_output))
+ if not ip_address.encode('utf-8') in dig_output:
+ raise ValueError('Test has failed: Incorrectly resolved DUT hostname using dig'
+ "Output should've contained DUT's IP address:{}".format(ip_address))
+ finally:
+ mdns_server_events['stop'].set()
+ mdns_responder.join()
+
+
+@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
+def test_examples_protocol_mdns_default(env, _):
+ test_examples_protocol_mdns(env, 'eth_def')
+
+
+@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
+def test_examples_protocol_mdns_socket(env, _):
+ test_examples_protocol_mdns(env, 'eth_socket')
+
+
+@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
+def test_examples_protocol_mdns_custom_netif(env, _):
+ test_examples_protocol_mdns(env, 'eth_custom_netif')
+
+
+if __name__ == '__main__':
+ test_examples_protocol_mdns_default()
diff --git a/components/mdns/examples/sdkconfig.ci.eth_custom_netif b/components/mdns/examples/sdkconfig.ci.eth_custom_netif
new file mode 100644
index 000000000..56499bd43
--- /dev/null
+++ b/components/mdns/examples/sdkconfig.ci.eth_custom_netif
@@ -0,0 +1,19 @@
+CONFIG_IDF_TARGET="esp32"
+CONFIG_MDNS_RESOLVE_TEST_SERVICES=y
+CONFIG_MDNS_ADD_MAC_TO_HOSTNAME=y
+CONFIG_MDNS_PUBLISH_DELEGATE_HOST=y
+CONFIG_MDNS_PREDEF_NETIF_STA=n
+CONFIG_MDNS_PREDEF_NETIF_AP=n
+CONFIG_MDNS_PREDEF_NETIF_ETH=n
+CONFIG_MDNS_ADD_CUSTOM_NETIF=y
+CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
+CONFIG_EXAMPLE_CONNECT_ETHERNET=y
+CONFIG_EXAMPLE_CONNECT_WIFI=n
+CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
+CONFIG_EXAMPLE_ETH_PHY_IP101=y
+CONFIG_EXAMPLE_ETH_MDC_GPIO=23
+CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
+CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
+CONFIG_EXAMPLE_ETH_PHY_ADDR=1
+CONFIG_EXAMPLE_CONNECT_IPV6=y
+CONFIG_MDNS_BUTTON_GPIO=32
diff --git a/components/mdns/examples/sdkconfig.ci.eth_def b/components/mdns/examples/sdkconfig.ci.eth_def
new file mode 100644
index 000000000..703b4543e
--- /dev/null
+++ b/components/mdns/examples/sdkconfig.ci.eth_def
@@ -0,0 +1,15 @@
+CONFIG_IDF_TARGET="esp32"
+CONFIG_MDNS_RESOLVE_TEST_SERVICES=y
+CONFIG_MDNS_ADD_MAC_TO_HOSTNAME=y
+CONFIG_MDNS_PUBLISH_DELEGATE_HOST=y
+CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
+CONFIG_EXAMPLE_CONNECT_ETHERNET=y
+CONFIG_EXAMPLE_CONNECT_WIFI=n
+CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
+CONFIG_EXAMPLE_ETH_PHY_IP101=y
+CONFIG_EXAMPLE_ETH_MDC_GPIO=23
+CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
+CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
+CONFIG_EXAMPLE_ETH_PHY_ADDR=1
+CONFIG_EXAMPLE_CONNECT_IPV6=y
+CONFIG_MDNS_BUTTON_GPIO=32
diff --git a/components/mdns/examples/sdkconfig.ci.eth_socket b/components/mdns/examples/sdkconfig.ci.eth_socket
new file mode 100644
index 000000000..5d9240526
--- /dev/null
+++ b/components/mdns/examples/sdkconfig.ci.eth_socket
@@ -0,0 +1,16 @@
+CONFIG_IDF_TARGET="esp32"
+CONFIG_MDNS_RESOLVE_TEST_SERVICES=y
+CONFIG_MDNS_ADD_MAC_TO_HOSTNAME=y
+CONFIG_MDNS_PUBLISH_DELEGATE_HOST=y
+CONFIG_MDNS_NETWORKING_SOCKET=y
+CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
+CONFIG_EXAMPLE_CONNECT_ETHERNET=y
+CONFIG_EXAMPLE_CONNECT_WIFI=n
+CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
+CONFIG_EXAMPLE_ETH_PHY_IP101=y
+CONFIG_EXAMPLE_ETH_MDC_GPIO=23
+CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
+CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
+CONFIG_EXAMPLE_ETH_PHY_ADDR=1
+CONFIG_EXAMPLE_CONNECT_IPV6=y
+CONFIG_MDNS_BUTTON_GPIO=32
diff --git a/components/mdns/include/mdns.h b/components/mdns/include/mdns.h
new file mode 100644
index 000000000..7f81d0af8
--- /dev/null
+++ b/components/mdns/include/mdns.h
@@ -0,0 +1,773 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef ESP_MDNS_H_
+#define ESP_MDNS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+
+#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 Asynchronous query handle
+ */
+typedef struct mdns_search_once_s mdns_search_once_t;
+
+
+typedef enum {
+ MDNS_EVENT_ENABLE_IP4 = 1 << 1,
+ MDNS_EVENT_ENABLE_IP6 = 1 << 2,
+ MDNS_EVENT_ANNOUNCE_IP4 = 1 << 3,
+ MDNS_EVENT_ANNOUNCE_IP6 = 1 << 4,
+ MDNS_EVENT_DISABLE_IP4 = 1 << 5,
+ MDNS_EVENT_DISABLE_IP6 = 1 << 6,
+} mdns_event_actions_t;
+
+/**
+ * @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 {
+ const char * key; /*!< item key name */
+ const char * value; /*!< item value string */
+} mdns_txt_item_t;
+
+/**
+ * @brief mDNS query linked list IP item
+ */
+typedef struct mdns_ip_addr_s {
+ esp_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 type to be explicitly set to either Unicast or Multicast
+ */
+typedef enum {
+ MDNS_QUERY_UNICAST,
+ MDNS_QUERY_MULTICAST,
+} mdns_query_transmission_type_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 */
+
+ esp_netif_t* esp_netif; /*!< ptr to corresponding esp-netif */
+ uint32_t ttl; /*!< time to live */
+
+ mdns_ip_protocol_t ip_protocol; /*!< ip_protocol type of the interface (v4/v6) */
+ // PTR
+ char * instance_name; /*!< instance name */
+ char * service_type; /*!< service type */
+ char * proto; /*!< srevice protocol */
+ // SRV
+ char * hostname; /*!< hostname */
+ uint16_t port; /*!< service port */
+ // TXT
+ mdns_txt_item_t * txt; /*!< txt record */
+ uint8_t *txt_value_len; /*!< array of txt value len of each record */
+ size_t txt_count; /*!< number of txt items */
+ // A and AAAA
+ mdns_ip_addr_t * addr; /*!< linked list of IP addresses found */
+} mdns_result_t;
+
+typedef void (*mdns_query_notify_t)(mdns_search_once_t *search);
+
+/**
+ * @brief Initialize mDNS on given interface
+ *
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_STATE when failed to register event handler
+ * - ESP_ERR_NO_MEM on memory error
+ * - ESP_FAIL when failed to start mdns task
+ */
+esp_err_t mdns_init(void);
+
+/**
+ * @brief Stop and free mDNS server
+ *
+ */
+void mdns_free(void);
+
+/**
+ * @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 Adds a hostname and address to be delegated
+ * A/AAAA queries will be replied for the hostname and
+ * services can be added to this host.
+ *
+ * @param hostname Hostname to add
+ * @param address_list The IP address list of the host
+ *
+ * @return
+ * - ESP_OK success
+ * - ESP_ERR_INVALID_STATE mDNS is not running
+ * - ESP_ERR_INVALID_ARG Parameter error
+ * - ESP_ERR_NO_MEM memory error
+ *
+ */
+esp_err_t mdns_delegate_hostname_add(const char * hostname, const mdns_ip_addr_t *address_list);
+
+/**
+ * @brief Remove a delegated hostname
+ * All the services added to this host will also be removed.
+ *
+ * @param hostname Hostname to remove
+ *
+ * @return
+ * - ESP_OK success
+ * - ESP_ERR_INVALID_STATE mDNS is not running
+ * - ESP_ERR_INVALID_ARG Parameter error
+ * - ESP_ERR_NO_MEM memory error
+ *
+ */
+esp_err_t mdns_delegate_hostname_remove(const char * hostname);
+
+/**
+ * @brief Query whether a hostname has been added
+ *
+ * @param hostname Hostname to query
+ *
+ * @return
+ * - true The hostname has been added.
+ * - false The hostname has not been added.
+ *
+ */
+bool mdns_hostname_exists(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
+ *
+ * @note The value length of txt items will be automatically decided by strlen
+ *
+ * @param instance_name instance name to set. If NULL,
+ * global instance name or hostname will be used.
+ * Note that MDNS_MULTIPLE_INSTANCE config option
+ * needs to be enabled for adding multiple instances
+ * with the same instance type.
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param port service port
+ * @param txt string array of TXT data (eg. {{"var","val"},{"other","2"}})
+ * @param num_items number of items in TXT data
+ *
+ * @return
+ * - ESP_OK success
+ * - ESP_ERR_INVALID_ARG Parameter error
+ * - ESP_ERR_NO_MEM memory error
+ * - ESP_FAIL failed to add service
+ */
+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 Add service to mDNS server with a delegated hostname
+ *
+ * @note The value length of txt items will be automatically decided by strlen
+ *
+ * @param instance_name instance name to set. If NULL,
+ * global instance name or hostname will be used
+ * Note that MDNS_MULTIPLE_INSTANCE config option
+ * needs to be enabled for adding multiple instances
+ * with the same instance type.
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param hostname service hostname. If NULL, local hostname will be used.
+ * @param port service port
+ * @param txt string array of TXT data (eg. {{"var","val"},{"other","2"}})
+ * @param num_items number of items in TXT data
+ *
+ * @return
+ * - ESP_OK success
+ * - ESP_ERR_INVALID_ARG Parameter error
+ * - ESP_ERR_NO_MEM memory error
+ * - ESP_FAIL failed to add service
+ */
+esp_err_t mdns_service_add_for_host(const char * instance_name, const char * service_type, const char * proto,
+ const char * hostname, uint16_t port, mdns_txt_item_t txt[], size_t num_items);
+
+/**
+ * @brief Check whether a service has been added.
+ *
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param hostname service hostname. If NULL, checks for the local hostname.
+ *
+ * @return
+ * - true Correspondding service has been added.
+ * - false Service not found.
+ */
+bool mdns_service_exists(const char * service_type, const char * proto, const char * hostname);
+
+
+/**
+ * @brief Check whether a service has been added.
+ *
+ * @param instance instance name
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param hostname service hostname. If NULL, checks for the local hostname.
+ *
+ * @return
+ * - true Correspondding service has been added.
+ * - false Service not found.
+ */
+bool mdns_service_exists_with_instance(const char *instance, const char *service_type, const char *proto,
+ const char *hostname);
+
+/**
+ * @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_ERR_NO_MEM memory error
+ */
+esp_err_t mdns_service_remove(const char * service_type, const char * proto);
+
+/**
+ * @brief Remove service from mDNS server with hostname
+ *
+ * @param instance instance name
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param hostname service hostname. If NULL, local hostname will be used.
+ *
+ * @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_remove_for_host(const char *instance, const char * service_type, const char * proto, const char *hostname);
+
+/**
+ * @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 instance name for service with hostname
+ *
+ * @param instance_old original instance name
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param hostname service hostname. If NULL, local hostname will be used.
+ * @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_for_host(const char * instance_old, const char * service_type, const char * proto, const char * hostname,
+ 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_NO_MEM memory error
+ */
+esp_err_t mdns_service_port_set(const char * service_type, const char * proto, uint16_t port);
+
+
+/**
+ * @brief Set service port with hostname
+ *
+ * @param instance instance name
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param hostname service hostname. If NULL, local hostname will be used.
+ * @param port service port
+ *
+ * @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_port_set_for_host(const char * instance, const char * service_type, const char * proto, const char * hostname,
+ uint16_t port);
+
+/**
+ * @brief Replace all TXT items for service
+ *
+ * @note The value length of txt items will be automatically decided by strlen
+ *
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param txt array of TXT data (eg. {{"var","val"},{"other","2"}})
+ * @param num_items number of items in TXT data
+ *
+ * @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 Replace all TXT items for service with hostname
+ *
+ * @note The value length of txt items will be automatically decided by strlen
+ *
+ * @param instance instance name
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param hostname service hostname. If NULL, local hostname will be used.
+ * @param txt array of TXT data (eg. {{"var","val"},{"other","2"}})
+ * @param num_items number of items in TXT data
+ *
+ * @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_for_host(const char * instance, const char * service_type, const char * proto, const char * hostname,
+ mdns_txt_item_t txt[], uint8_t num_items);
+
+/**
+ * @brief Set/Add TXT item for service TXT record
+ *
+ * @note The value length will be automatically decided by strlen
+ *
+ * @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 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
+ * @param value_len the length of the value
+ *
+ * @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_with_explicit_value_len(const char *service_type, const char *proto,
+ const char *key, const char *value, uint8_t value_len);
+
+/**
+ * @brief Set/Add TXT item for service TXT record with hostname
+ *
+ * @note The value length will be automatically decided by strlen
+ *
+ * @param instance instance name
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param hostname service hostname. If NULL, local hostname will be used.
+ * @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_for_host(const char * instance, const char * service_type, const char * proto, const char * hostname,
+ const char * key, const char * value);
+
+/**
+ * @brief Set/Add TXT item for service TXT record with hostname and txt value length
+ *
+ * @param instance instance name
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param hostname service hostname. If NULL, local hostname will be used.
+ * @param key the key that you want to add/update
+ * @param value the new value of the key
+ * @param value_len the length of the value
+ *
+ * @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_for_host_with_explicit_value_len(const char * instance, const char *service_type, const char *proto,
+ const char *hostname, const char *key,
+ const char *value, uint8_t value_len);
+
+/**
+ * @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 TXT item for service TXT record with hostname
+ *
+ * @param instance instance name
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param hostname service hostname. If NULL, local hostname will be used.
+ * @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_for_host(const char * instance, const char * service_type, const char * proto, const char * hostname,
+ const char * key);
+
+/**
+ * @brief Add subtype for service.
+ *
+ * @param instance_name instance name. If NULL, will find the first service with the same service type and protocol.
+ * @param service_type service type (_http, _ftp, etc)
+ * @param proto service protocol (_tcp, _udp)
+ * @param hostname service hostname. If NULL, local hostname will be used.
+ * @param subtype The subtype to add.
+ *
+ * @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_subtype_add_for_host(const char *instance_name, const char *service_type, const char *proto,
+ const char *hostname, const char *subtype);
+
+/**
+ * @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(void);
+
+/**
+ * @brief Deletes the finished query. Call this only after the search has ended!
+ *
+ * @param search pointer to search object
+ *
+ * @return
+ * - ESP_OK success
+ * - ESP_ERR_INVALID_STATE search has not finished
+ * - ESP_ERR_INVALID_ARG pointer to search object is NULL
+ */
+esp_err_t mdns_query_async_delete(mdns_search_once_t* search);
+
+/**
+ * @brief Get results from search pointer. Results available as a pointer to the output parameter.
+ * Pointer to search object has to be deleted via `mdns_query_async_delete` once the query has finished.
+ * The results although have to be freed manually.
+ *
+ * @param search pointer to search object
+ * @param timeout time in milliseconds to wait for answers
+ * @param results pointer to the results of the query
+ * @param num_results pointer to the number of the actual result items (set to NULL to ignore this return value)
+ *
+ * @return
+ * True if search has finished before or at timeout
+ * False if search timeout is over
+ */
+bool mdns_query_async_get_results(mdns_search_once_t* search, uint32_t timeout, mdns_result_t ** results, uint8_t * num_results);
+
+/**
+ * @brief Query mDNS for host or service asynchronousely.
+ * Search has to be tested for progress and deleted manually!
+ *
+ * @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 during which mDNS query is active
+ * @param max_results maximum results to be collected
+ * @param notifier Notification function to be called when the result is ready, can be NULL
+ *
+ * @return mdns_search_once_s pointer to new search object if query initiated successfully.
+ * NULL otherwise.
+ */
+mdns_search_once_t *mdns_query_async_new(const char *name, const char *service_type, const char *proto, uint16_t type,
+ uint32_t timeout, size_t max_results, mdns_query_notify_t notifier);
+
+/**
+ * @brief Generic mDNS query
+ * 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 transmission_type either Unicast query, or Multicast query
+ * @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_generic(const char * name, const char * service_type, const char * proto, uint16_t type,
+ mdns_query_transmission_type_t transmission_type, uint32_t timeout, size_t max_results, mdns_result_t ** results);
+
+/**
+ * @brief Query mDNS for host or service
+ *
+ * Note that querying PTR types sends Multicast query, all other types send Unicast queries
+ *
+ * @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, esp_ip4_addr_t * addr);
+
+#if CONFIG_LWIP_IPV6
+/**
+ * @brief Query mDNS for A record
+ *
+ * Please note that hostname must not contain domain name, as mDNS uses '.local' domain.
+ *
+ * @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, esp_ip6_addr_t * addr);
+#endif
+
+
+/**
+ * @brief Register custom esp_netif with mDNS functionality
+ * mDNS service runs by default on preconfigured interfaces (STA, AP, ETH).
+ * This API enables running the service on any customized interface,
+ * either using standard WiFi or Ethernet driver or any kind of user defined driver.
+ *
+ * @param esp_netif Pointer to esp-netif interface
+ * @return
+ * - ESP_OK success
+ * - ESP_ERR_INVALID_STATE mDNS is not running or this netif is already registered
+ * - ESP_ERR_NO_MEM not enough memory for this in interface in the netif list (see CONFIG_MDNS_MAX_INTERFACES)
+ */
+esp_err_t mdns_register_netif(esp_netif_t *esp_netif);
+
+/**
+ * @brief Unregister esp-netif already registered in mDNS service
+ *
+ * @param esp_netif Pointer to esp-netif interface
+ * @return
+ * - ESP_OK success
+ * - ESP_ERR_INVALID_STATE mDNS is not running
+ * - ESP_ERR_NOT_FOUND this esp-netif was not registered in mDNS service
+ */
+esp_err_t mdns_unregister_netif(esp_netif_t *esp_netif);
+
+/**
+ * @brief Set esp_netif to a desired state, or perform a desired action, such as enable/disable this interface
+ * or send announcement packets to this netif
+ *
+ * * This function is used to enable (probe, resolve conflicts and announce), announce, or disable (send bye) mDNS
+ * services on the specified network interface.
+ * * This function must be called if users registers a specific interface using mdns_register_netif()
+ * to enable mDNS services on that interface.
+ * * This function could be used in IP/connection event handlers to automatically enable/announce mDNS services
+ * when network properties change and/or disable them on disconnection.
+ *
+ * @param esp_netif Pointer to esp-netif interface
+ * @param event_action Disable/Enable/Announce on this interface over IPv4/IPv6 protocol.
+ * Actions enumerated in mdns_event_actions_t type.
+ * @return
+ * - ESP_OK success
+ * - ESP_ERR_INVALID_STATE mDNS is not running or this netif is not registered
+ * - ESP_ERR_NO_MEM memory error
+ */
+esp_err_t mdns_netif_action(esp_netif_t *esp_netif, mdns_event_actions_t event_action);
+
+#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 000000000..059e03de5
--- /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(void);
+
+#endif /* _MDNS_CONSOLE_H_ */
diff --git a/components/mdns/mdns.c b/components/mdns/mdns.c
new file mode 100644
index 000000000..15b32e338
--- /dev/null
+++ b/components/mdns/mdns.c
@@ -0,0 +1,6279 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "freertos/semphr.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "mdns.h"
+#include "mdns_private.h"
+#include "mdns_networking.h"
+#include "esp_log.h"
+#include "esp_random.h"
+
+#ifdef MDNS_ENABLE_DEBUG
+void mdns_debug_packet(const uint8_t * data, size_t len);
+#endif
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+#endif
+
+// Internal size of IPv6 address is defined here as size of AAAA record in mdns packet
+// since the ip6_addr_t is defined in lwip and depends on using IPv6 zones
+#define _MDNS_SIZEOF_IP6_ADDR (MDNS_ANSWER_AAAA_SIZE)
+
+static const char * MDNS_DEFAULT_DOMAIN = "local";
+static const char * MDNS_SUB_STR = "_sub";
+
+mdns_server_t * _mdns_server = NULL;
+static mdns_host_item_t * _mdns_host_list = NULL;
+static mdns_host_item_t _mdns_self_host;
+
+static const char *TAG = "MDNS";
+
+static volatile TaskHandle_t _mdns_service_task_handle = NULL;
+static SemaphoreHandle_t _mdns_service_semaphore = NULL;
+
+static void _mdns_search_finish_done(void);
+static mdns_search_once_t * _mdns_search_find_from(mdns_search_once_t * search, mdns_name_t * name, uint16_t type, mdns_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, esp_ip_addr_t * ip,
+ mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint32_t ttl);
+static void _mdns_search_result_add_srv(mdns_search_once_t *search, const char *hostname, uint16_t port,
+ mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint32_t ttl);
+static void _mdns_search_result_add_txt(mdns_search_once_t *search, mdns_txt_item_t *txt, uint8_t *txt_value_len,
+ size_t txt_count, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol,
+ uint32_t ttl);
+static mdns_result_t * _mdns_search_result_add_ptr(mdns_search_once_t * search, const char * instance,
+ const char * service_type, const char * proto, mdns_if_t tcpip_if,
+ mdns_ip_protocol_t ip_protocol, uint32_t ttl);
+static bool _mdns_append_host_list_in_services(mdns_out_answer_t ** destination, mdns_srv_item_t * services[], size_t services_len, bool flush, bool bye);
+static bool _mdns_append_host_list(mdns_out_answer_t ** destination, bool flush, bool bye);
+static void _mdns_remap_self_service_hostname(const char *old_hostname, const char *new_hostname);
+static esp_err_t mdns_post_custom_action_tcpip_if(mdns_if_t mdns_if, mdns_event_actions_t event_action);
+
+typedef enum {
+ MDNS_IF_STA = 0,
+ MDNS_IF_AP = 1,
+ MDNS_IF_ETH = 2,
+} mdns_predef_if_t;
+
+typedef struct mdns_interfaces mdns_interfaces_t;
+
+struct mdns_interfaces {
+ const bool predefined;
+ esp_netif_t * netif;
+ const mdns_predef_if_t predef_if;
+ mdns_if_t duplicate;
+};
+
+/*
+ * @brief Internal collection of mdns supported interfaces
+ *
+ */
+static mdns_interfaces_t s_esp_netifs[MDNS_MAX_INTERFACES] = {
+#if CONFIG_MDNS_PREDEF_NETIF_STA
+ { .predefined = true, .netif = NULL, .predef_if = MDNS_IF_STA, .duplicate = MDNS_MAX_INTERFACES },
+#endif
+#if CONFIG_MDNS_PREDEF_NETIF_AP
+ { .predefined = true, .netif = NULL, .predef_if = MDNS_IF_AP, .duplicate = MDNS_MAX_INTERFACES },
+#endif
+#if CONFIG_MDNS_PREDEF_NETIF_ETH
+ { .predefined = true, .netif = NULL, .predef_if = MDNS_IF_ETH, .duplicate = MDNS_MAX_INTERFACES },
+#endif
+};
+
+
+/**
+ * @brief Convert Predefined interface to the netif id from the internal netif list
+ * @param predef_if Predefined interface enum
+ * @return Ordinal number of internal list of mdns network interface.
+ * Returns MDNS_MAX_INTERFACES if the predefined interface wasn't found in the list
+ */
+static mdns_if_t mdns_if_from_preset_if(mdns_predef_if_t predef_if)
+{
+ for (int i=0; i "STA", "AP", "ETH"
+ * - if no entry in the list (NULL) -> check if the system added this netif
+ * - point to a custom netif -> just return the entry in the list
+ * - users is responsible for the lifetime of this netif (to be valid between mdns-init -> deinit)
+ *
+ * @param tcpip_if Ordinal number of the interface
+ * @return Pointer ot the esp_netif object if the interface is available, NULL otherwise
+ */
+esp_netif_t *_mdns_get_esp_netif(mdns_if_t tcpip_if)
+{
+ if (tcpip_if < MDNS_MAX_INTERFACES) {
+ if (s_esp_netifs[tcpip_if].netif == NULL && s_esp_netifs[tcpip_if].predefined) {
+ // If the local copy is NULL and this netif is predefined -> we can find it in the global netif list
+ s_esp_netifs[tcpip_if].netif = esp_netif_from_preset_if(s_esp_netifs[tcpip_if].predef_if);
+ // failing to find it means that the netif is *not* available -> return NULL
+ }
+ return s_esp_netifs[tcpip_if].netif;
+ }
+ return NULL;
+}
+
+
+/*
+ * @brief Clean internal mdns interface's pointer
+ */
+static inline void _mdns_clean_netif_ptr(mdns_if_t tcpip_if) {
+ if (tcpip_if < MDNS_MAX_INTERFACES) {
+ s_esp_netifs[tcpip_if].netif = NULL;
+ }
+}
+
+
+/*
+ * @brief Convert esp-netif handle to mdns if
+ */
+static mdns_if_t _mdns_get_if_from_esp_netif(esp_netif_t *esp_netif)
+{
+ for (int i=0; iservice, service) && !strcasecmp(srv->proto, proto) &&
+ (_str_null_or_empty(hostname) || !strcasecmp(srv->hostname, hostname));
+}
+
+/**
+ * @brief finds service from given service type
+ * @param server the server
+ * @param service service type to match
+ * @param proto proto to match
+ * @param hostname hostname of the service (if non-null)
+ *
+ * @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, const char * hostname)
+{
+ mdns_srv_item_t * s = _mdns_server->services;
+ while (s) {
+ if (_mdns_service_match(s->service, service, proto, hostname)) {
+ return s;
+ }
+ s = s->next;
+ }
+ return NULL;
+}
+
+static mdns_srv_item_t * _mdns_get_service_item_subtype(const char *subtype, const char * service, const char * proto)
+{
+ mdns_srv_item_t * s = _mdns_server->services;
+ while (s) {
+ if (_mdns_service_match(s->service, service, proto, NULL)) {
+ mdns_subtype_t *subtype_item = s->service->subtype;
+ while(subtype_item) {
+ if (!strcasecmp(subtype_item->subtype, subtype)) {
+ return s;
+ }
+ subtype_item = subtype_item->next;
+ }
+ }
+ s = s->next;
+ }
+ return NULL;
+}
+
+static mdns_host_item_t * mdns_get_host_item(const char * hostname)
+{
+ if (hostname == NULL || strcasecmp(hostname, _mdns_server->hostname) == 0) {
+ return &_mdns_self_host;
+ }
+ mdns_host_item_t * host = _mdns_host_list;
+ while (host != NULL) {
+ if (strcasecmp(host->hostname, hostname) == 0) {
+ return host;
+ }
+ host = host->next;
+ }
+ 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;
+
+ action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
+ if (!action) {
+ HOOK_MALLOC_FAILED;
+ return ESP_ERR_NO_MEM;
+ }
+
+ action->type = ACTION_RX_HANDLE;
+ action->data.rx_handle.packet = packet;
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+ return ESP_OK;
+}
+
+static const char *_mdns_get_default_instance_name(void)
+{
+ 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 Get the service name of a service
+ */
+static const char * _mdns_get_service_instance_name(const mdns_service_t * service)
+{
+ if (service && !_str_null_or_empty(service->instance)) {
+ return service->instance;
+ }
+
+ return _mdns_get_default_instance_name();
+}
+
+static bool _mdns_instance_name_match(const char *lhs, const char *rhs)
+{
+ if (lhs == NULL) {
+ lhs = _mdns_get_default_instance_name();
+ }
+ if (rhs == NULL) {
+ rhs = _mdns_get_default_instance_name();
+ }
+ return !strcasecmp(lhs, rhs);
+}
+
+static bool _mdns_service_match_instance(const mdns_service_t *srv, const char *instance, const char *service,
+ const char *proto, const char *hostname)
+{
+ // service and proto must be supplied, if not this instance won't match
+ if (!service || !proto) {
+ return false;
+ }
+ // instance==NULL -> _mdns_instance_name_match() will check the default instance
+ // hostname==NULL -> matches if instance, service and proto matches
+ return !strcasecmp(srv->service, service) && _mdns_instance_name_match(srv->instance, instance) &&
+ !strcasecmp(srv->proto, proto) && (_str_null_or_empty(hostname) || !strcasecmp(srv->hostname, hostname));
+}
+
+static mdns_srv_item_t *_mdns_get_service_item_instance(const char *instance, const char *service, const char *proto,
+ const char *hostname)
+{
+ mdns_srv_item_t *s = _mdns_server->services;
+ while (s) {
+ if (instance) {
+ if (_mdns_service_match_instance(s->service, instance, service, proto, hostname)) {
+ return s;
+ }
+ } else {
+ if (_mdns_service_match(s->service, service, proto, hostname)) {
+ return s;
+ }
+ }
+ s = s->next;
+ }
+ 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 packet_len)
+{
+ size_t index = 0;
+ const uint8_t * packet_end = packet + packet_len;
+ while (start + index < packet_end && start[index]) {
+ if (name->parts == 4) {
+ name->invalid = true;
+ }
+ 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; i= packet_end) {
+ return NULL;
+ }
+ buf[i] = start[index++];
+ }
+ buf[len] = '\0';
+ if (name->parts == 1 && buf[0] != '_'
+ && (strcasecmp(buf, MDNS_DEFAULT_DOMAIN) != 0)
+ && (strcasecmp(buf, "arpa") != 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 if (!name->invalid) {
+ 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, packet_len)) {
+ 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 mdns_class = MDNS_CLASS_IN;
+ if (flush) {
+ mdns_class = MDNS_CLASS_IN_FLUSH_CACHE;
+ }
+ if (type == MDNS_ANSWER_PTR) {
+ _mdns_append_u16(packet, index, MDNS_TYPE_PTR);
+ _mdns_append_u16(packet, index, mdns_class);
+ } else if (type == MDNS_ANSWER_TXT) {
+ _mdns_append_u16(packet, index, MDNS_TYPE_TXT);
+ _mdns_append_u16(packet, index, mdns_class);
+ } else if (type == MDNS_ANSWER_SRV) {
+ _mdns_append_u16(packet, index, MDNS_TYPE_SRV);
+ _mdns_append_u16(packet, index, mdns_class);
+ } else if (type == MDNS_ANSWER_A) {
+ _mdns_append_u16(packet, index, MDNS_TYPE_A);
+ _mdns_append_u16(packet, index, mdns_class);
+ } else if (type == MDNS_ANSWER_AAAA) {
+ _mdns_append_u16(packet, index, MDNS_TYPE_AAAA);
+ _mdns_append_u16(packet, index, mdns_class);
+ } else {
+ return 0;
+ }
+ _mdns_append_u32(packet, index, ttl);
+ _mdns_append_u16(packet, index, 0);
+ return 10;
+}
+
+static inline uint8_t _mdns_append_string_with_len(uint8_t * packet, uint16_t * index, const char * string, uint8_t len)
+{
+ 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 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 one TXT record ("key=value" or "key")
+ *
+ * @param packet MDNS packet
+ * @param index offset in the packet
+ * @param txt one txt record
+ *
+ * @return length of added data: length of the added txt value + 1 on success
+ * 0 if data won't fit the packet
+ * -1 if invalid TXT entry
+ */
+static inline int append_one_txt_record_entry(uint8_t * packet, uint16_t * index, mdns_txt_linked_item_t * txt)
+{
+ if (txt == NULL || txt->key == NULL) {
+ return -1;
+ }
+ size_t key_len = strlen(txt->key);
+ size_t len = key_len + txt->value_len + (txt->value ? 1 : 0);
+ if ((*index + len + 1) >= MDNS_MAX_PACKET_SIZE) {
+ return 0;
+ }
+ _mdns_append_u8(packet, index, len);
+ memcpy(packet + *index, txt->key, key_len);
+ if (txt->value) {
+ packet[*index + key_len] = '=';
+ memcpy(packet + *index + key_len + 1, txt->value, txt->value_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, size_t packet_len)
+{
+ 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, packet_len);
+ 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, MDNS_MAX_PACKET_SIZE);
+
+ 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, MDNS_MAX_PACKET_SIZE);
+ 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;
+
+ if (service == NULL) {
+ return 0;
+ }
+
+ 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, MDNS_MAX_PACKET_SIZE);
+ 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;
+
+ mdns_txt_linked_item_t * txt = service->txt;
+ while (txt) {
+ int l = append_one_txt_record_entry(packet, index, txt);
+ if (l > 0) {
+ data_len += l;
+ } else if (l == 0) { // TXT entry won't fit into the mdns packet
+ return 0;
+ }
+ 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;
+
+ if (service == NULL) {
+ return 0;
+ }
+
+ 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, MDNS_MAX_PACKET_SIZE);
+ 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;
+ }
+
+ if (service->hostname) {
+ str[0] = service->hostname;
+ } else {
+ 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, MDNS_MAX_PACKET_SIZE);
+ 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 hostname the hostname address to add
+ * @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, const char * hostname, uint32_t ip, bool flush, bool bye)
+{
+ const char * str[2];
+ uint16_t record_length = 0;
+ uint8_t part_length;
+
+ str[0] = hostname;
+ str[1] = MDNS_DEFAULT_DOMAIN;
+
+ if (_str_null_or_empty(str[0])) {
+ return 0;
+ }
+
+ part_length = _mdns_append_fqdn(packet, index, str, 2, MDNS_MAX_PACKET_SIZE);
+ 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;
+}
+
+#if CONFIG_LWIP_IPV6
+/**
+ * @brief appends AAAA record to a packet, incrementing the index
+ *
+ * @param packet MDNS packet
+ * @param index offset in the packet
+ * @param hostname the hostname address to add
+ * @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, const char * hostname, uint8_t * ipv6, bool flush, bool bye)
+{
+ const char * str[2];
+ uint16_t record_length = 0;
+ uint8_t part_length;
+
+ str[0] = hostname;
+ str[1] = MDNS_DEFAULT_DOMAIN;
+
+ if (_str_null_or_empty(str[0])) {
+ return 0;
+ }
+
+
+ part_length = _mdns_append_fqdn(packet, index, str, 2, MDNS_MAX_PACKET_SIZE);
+ 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 + MDNS_ANSWER_AAAA_SIZE) > MDNS_MAX_PACKET_SIZE) {
+ return 0;
+ }
+
+ part_length = MDNS_ANSWER_AAAA_SIZE;
+ 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;
+}
+#endif
+
+/**
+ * @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, MDNS_MAX_PACKET_SIZE);
+ 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 mdns_if_t _mdns_get_other_if (mdns_if_t tcpip_if)
+{
+ if (tcpip_if < MDNS_MAX_INTERFACES) {
+ return s_esp_netifs[tcpip_if].duplicate;
+ }
+ return MDNS_MAX_INTERFACES;
+}
+
+/**
+ * @brief Check if interface is duplicate (two interfaces on the same subnet)
+ */
+static bool _mdns_if_is_dup(mdns_if_t tcpip_if)
+{
+ mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
+ if (other_if == MDNS_MAX_INTERFACES) {
+ 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;
+}
+
+#if CONFIG_LWIP_IPV6
+/**
+ * @brief Check if IPv6 address is NULL
+ */
+static bool _ipv6_address_is_zero(esp_ip6_addr_t ip6)
+{
+ uint8_t i;
+ uint8_t * data = (uint8_t *)ip6.addr;
+ for (i=0; i<_MDNS_SIZEOF_IP6_ADDR; i++) {
+ if (data[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+#endif
+
+static uint8_t _mdns_append_host_answer(uint8_t * packet, uint16_t * index, mdns_host_item_t * host,
+ uint8_t address_type, bool flush, bool bye)
+{
+ mdns_ip_addr_t * addr = host->address_list;
+ uint8_t num_records = 0;
+
+ while (addr != NULL) {
+ if (addr->addr.type == address_type) {
+ if (address_type == ESP_IPADDR_TYPE_V4 &&
+ _mdns_append_a_record(packet, index, host->hostname, addr->addr.u_addr.ip4.addr, flush, bye) <= 0) {
+ break;
+ }
+#if CONFIG_LWIP_IPV6
+ if (address_type == ESP_IPADDR_TYPE_V6 &&
+ _mdns_append_aaaa_record(packet, index, host->hostname, (uint8_t *)addr->addr.u_addr.ip6.addr, flush,
+ bye) <= 0) {
+ break;
+ }
+#endif // CONFIG_LWIP_IPV6
+ num_records++;
+ }
+ addr = addr->next;
+ }
+ return num_records;
+}
+
+/**
+ * @brief Append PTR answers to packet
+ *
+ * @return number of answers added to the packet
+ */
+static uint8_t _mdns_append_service_ptr_answers(uint8_t *packet, uint16_t *index, mdns_service_t *service, bool flush,
+ bool bye)
+{
+ uint8_t appended_answers = 0;
+
+ if (_mdns_append_ptr_record(packet, index, _mdns_get_service_instance_name(service), service->service,
+ service->proto, flush, bye) <= 0) {
+ return appended_answers;
+ }
+ appended_answers++;
+
+ mdns_subtype_t *subtype = service->subtype;
+ while (subtype) {
+ appended_answers +=
+ (_mdns_append_subtype_ptr_record(packet, index, _mdns_get_service_instance_name(service), subtype->subtype,
+ service->service, service->proto, flush, bye) > 0);
+ subtype = subtype->next;
+ }
+
+ return appended_answers;
+}
+
+
+/**
+ * @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, mdns_if_t tcpip_if)
+{
+ if (answer->type == MDNS_TYPE_PTR) {
+ if (answer->service) {
+ return _mdns_append_service_ptr_answers(packet, index, answer->service, answer->flush, answer->bye);
+ } 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) {
+ if (answer->host == &_mdns_self_host) {
+ esp_netif_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 (esp_netif_get_ip_info(_mdns_get_esp_netif(tcpip_if), &if_ip_info)) {
+ return 0;
+ }
+ if (_mdns_append_a_record(packet, index, _mdns_server->hostname, if_ip_info.ip.addr, answer->flush, answer->bye) <= 0) {
+ return 0;
+ }
+ if (!_mdns_if_is_dup(tcpip_if)) {
+ return 1;
+ }
+ mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
+ if (esp_netif_get_ip_info(_mdns_get_esp_netif(other_if), &if_ip_info)) {
+ return 1;
+ }
+ if (_mdns_append_a_record(packet, index, _mdns_server->hostname, if_ip_info.ip.addr, answer->flush, answer->bye) > 0) {
+ return 2;
+ }
+ return 1;
+ } else if (answer->host != NULL) {
+ return _mdns_append_host_answer(packet, index, answer->host, ESP_IPADDR_TYPE_V4, answer->flush, answer->bye);
+ }
+ }
+#if CONFIG_LWIP_IPV6
+ else if (answer->type == MDNS_TYPE_AAAA) {
+ if (answer->host == &_mdns_self_host) {
+ struct esp_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 (esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(tcpip_if), &if_ip6)) {
+ return 0;
+ }
+ if (_ipv6_address_is_zero(if_ip6)) {
+ return 0;
+ }
+ if (_mdns_append_aaaa_record(packet, index, _mdns_server->hostname, (uint8_t*)if_ip6.addr, answer->flush, answer->bye) <= 0) {
+ return 0;
+ }
+ if (!_mdns_if_is_dup(tcpip_if)) {
+ return 1;
+ }
+ mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
+ if (esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(other_if), &if_ip6)) {
+ return 1;
+ }
+ if (_mdns_append_aaaa_record(packet, index, _mdns_server->hostname, (uint8_t*)if_ip6.addr, answer->flush, answer->bye) > 0) {
+ return 2;
+ }
+ return 1;
+ } else if (answer->host != NULL) {
+ return _mdns_append_host_answer(packet, index, answer->host, ESP_IPADDR_TYPE_V6, answer->flush, answer->bye);
+ }
+ }
+#endif
+ 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);
+ _mdns_set_u16(packet, MDNS_HEAD_ID_OFFSET, p->id);
+
+ 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 == ESP_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;
+ }
+ mdns_out_question_t * q = packet->questions;
+ while (q) {
+ mdns_out_question_t * next = q->next;
+ if (q->own_dynamic_memory) {
+ free((char *)q->host);
+ free((char *)q->service);
+ free((char *)q->proto);
+ free((char *)q->domain);
+ }
+ free(q);
+ q = next;
+ }
+ 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(void)
+{
+ 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(mdns_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(mdns_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(mdns_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 ** destination, uint16_t type, mdns_srv_item_t * service)
+{
+ mdns_out_answer_t * d = *destination;
+ if (!d) {
+ return;
+ }
+ mdns_srv_item_t s = {NULL, NULL};
+ if (!service) {
+ service = &s;
+ }
+ if (d->type == type && d->service == service->service) {
+ *destination = 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 ** destination, uint16_t type, mdns_service_t * service,
+ mdns_host_item_t * host, bool flush, bool bye)
+{
+ mdns_out_answer_t * d = *destination;
+ while (d) {
+ if (d->type == type && d->service == service && d->host == host) {
+ return true;
+ }
+ d = d->next;
+ }
+
+ mdns_out_answer_t * a = (mdns_out_answer_t *)malloc(sizeof(mdns_out_answer_t));
+ if (!a) {
+ HOOK_MALLOC_FAILED;
+ return false;
+ }
+ a->type = type;
+ a->service = service;
+ a->host = host;
+ a->custom_service = NULL;
+ a->bye = bye;
+ a->flush = flush;
+ a->next = NULL;
+ queueToEnd(mdns_out_answer_t, *destination, a);
+ return true;
+}
+
+/**
+ * @brief Allocate new packet for sending
+ */
+static mdns_tx_packet_t * _mdns_alloc_packet_default(mdns_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) {
+ HOOK_MALLOC_FAILED;
+ 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) {
+ esp_ip_addr_t addr = ESP_IP4ADDR_INIT(224, 0, 0, 251);
+ memcpy(&packet->dst, &addr, sizeof(esp_ip_addr_t));
+ }
+#if CONFIG_LWIP_IPV6
+ else {
+ esp_ip_addr_t addr = ESP_IP6ADDR_INIT(0x000002ff, 0, 0, 0xfb000000);
+ memcpy(&packet->dst, &addr, sizeof(esp_ip_addr_t));
+ }
+#endif
+ return packet;
+}
+
+static bool _mdns_create_answer_from_service(mdns_tx_packet_t * packet, mdns_service_t * service,
+ mdns_parsed_question_t * question, bool shared, bool send_flush)
+{
+ mdns_host_item_t * host = mdns_get_host_item(service->hostname);
+ if (question->type == MDNS_TYPE_PTR || question->type == MDNS_TYPE_ANY) {
+ if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, service, NULL, false, false) ||
+ !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, service, NULL, send_flush, false) ||
+ !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, service, NULL, send_flush, false) ||
+ !_mdns_alloc_answer(shared ? &packet->additional : &packet->answers, MDNS_TYPE_A, service, host, send_flush,
+ false) ||
+ !_mdns_alloc_answer(shared ? &packet->additional : &packet->answers, MDNS_TYPE_AAAA, service, host,
+ send_flush, false)) {
+ return false;
+ }
+ } else if (question->type == MDNS_TYPE_SRV) {
+ if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, service, NULL, send_flush, false) ||
+ !_mdns_alloc_answer(&packet->additional, MDNS_TYPE_A, service, host, send_flush, false) ||
+ !_mdns_alloc_answer(&packet->additional, MDNS_TYPE_AAAA, service, host, send_flush, false)) {
+ return false;
+ }
+ } else if (question->type == MDNS_TYPE_TXT) {
+ if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, service, NULL, send_flush, false)) {
+ return false;
+ }
+ } else if (question->type == MDNS_TYPE_SDPTR) {
+ shared = true;
+ if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SDPTR, service, NULL, false, false)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool _mdns_create_answer_from_hostname(mdns_tx_packet_t * packet, const char * hostname, bool send_flush)
+{
+ mdns_host_item_t * host = mdns_get_host_item(hostname);
+ if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_A, NULL, host, send_flush, false) ||
+ !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_AAAA, NULL, host, send_flush, false)) {
+ return false;
+ }
+ return true;
+}
+
+static bool _mdns_service_match_ptr_question(const mdns_service_t *service, const mdns_parsed_question_t *question)
+{
+ if (!_mdns_service_match(service, question->service, question->proto, NULL)) {
+ return false;
+ }
+ // The question parser stores anything before _type._proto in question->host
+ // So the question->host can be subtype or instance name based on its content
+ if (question->sub) {
+ mdns_subtype_t *subtype = service->subtype;
+ while (subtype) {
+ if (!strcasecmp(subtype->subtype, question->host)) {
+ return true;
+ }
+ subtype = subtype->next;
+ }
+ return false;
+ }
+ if (question->host) {
+ if (strcasecmp(_mdns_get_service_instance_name(service), question->host) != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * @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;
+ packet->id = parsed_packet->id;
+
+ mdns_parsed_question_t *q = parsed_packet->questions;
+ while (q) {
+ shared = q->type == MDNS_TYPE_PTR || q->type == MDNS_TYPE_SDPTR || !parsed_packet->probe;
+ if (q->type == MDNS_TYPE_SRV || q->type == MDNS_TYPE_TXT) {
+ mdns_srv_item_t *service = _mdns_get_service_item_instance(q->host, q->service, q->proto, NULL);
+ if (service == NULL || !_mdns_create_answer_from_service(packet, service->service, q, shared, send_flush)) {
+ _mdns_free_tx_packet(packet);
+ return;
+ }
+ } else if (q->service && q->proto) {
+ mdns_srv_item_t *service = _mdns_server->services;
+ while (service) {
+ if (_mdns_service_match_ptr_question(service->service, q)) {
+ if (!_mdns_create_answer_from_service(packet, service->service, q, shared, send_flush)) {
+ _mdns_free_tx_packet(packet);
+ return;
+ }
+ }
+ service = service->next;
+ }
+ } else if (q->type == MDNS_TYPE_A || q->type == MDNS_TYPE_AAAA) {
+ if (!_mdns_create_answer_from_hostname(packet, q->host, send_flush)) {
+ _mdns_free_tx_packet(packet);
+ return;
+ }
+ } else if (q->type == MDNS_TYPE_ANY) {
+ if (!_mdns_append_host_list(&packet->answers, send_flush, false)) {
+ _mdns_free_tx_packet(packet);
+ return;
+ }
+ } else if (!_mdns_alloc_answer(&packet->answers, q->type, NULL, NULL, send_flush, false)) {
+ _mdns_free_tx_packet(packet);
+ return;
+ }
+
+#ifdef MDNS_REPEAT_QUERY_IN_RESPONSE
+ if (parsed_packet->src_port != MDNS_SERVICE_PORT && // Repeat the queries only for "One-Shot mDNS queries"
+ (q->type == MDNS_TYPE_ANY || q->type == MDNS_TYPE_A || q->type == MDNS_TYPE_AAAA)) {
+ mdns_out_question_t * out_question = malloc(sizeof(mdns_out_question_t));
+ if (out_question == NULL) {
+ HOOK_MALLOC_FAILED;
+ _mdns_free_tx_packet(packet);
+ return;
+ }
+ out_question->type = q->type;
+ out_question->unicast = q->unicast;
+ out_question->host = q->host;
+ q->host = NULL;
+ out_question->service = q->service;
+ q->service = NULL;
+ out_question->proto = q->proto;
+ q->proto = NULL;
+ out_question->domain = q->domain;
+ q->domain = NULL;
+ out_question->next = NULL;
+ out_question->own_dynamic_memory = true;
+ queueToEnd(mdns_out_question_t, packet->questions, out_question);
+ }
+#endif // MDNS_REPEAT_QUERY_IN_RESPONSE
+ if (q->unicast) {
+ unicast = true;
+ }
+ q = q->next;
+ }
+ if (unicast || !send_flush) {
+ memcpy(&packet->dst, &parsed_packet->src, sizeof(esp_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;
+}
+
+static bool _mdns_append_host(mdns_out_answer_t ** destination, mdns_host_item_t * host, bool flush, bool bye)
+{
+ if (!_mdns_alloc_answer(destination, MDNS_TYPE_A, NULL, host, flush, bye)) {
+ return false;
+ }
+ if (!_mdns_alloc_answer(destination, MDNS_TYPE_AAAA, NULL, host, flush, bye)) {
+ return false;
+ }
+ return true;
+}
+
+static bool _mdns_append_host_list_in_services(mdns_out_answer_t ** destination, mdns_srv_item_t * services[],
+ size_t services_len, bool flush, bool bye)
+{
+ if (services == NULL) {
+ mdns_host_item_t * host = mdns_get_host_item(_mdns_server->hostname);
+ if (host != NULL) {
+ return _mdns_append_host(destination, host, flush, bye);
+ }
+ return true;
+ }
+ for (size_t i = 0; i < services_len; i++) {
+ mdns_host_item_t *host = mdns_get_host_item(services[i]->service->hostname);
+ if (!_mdns_append_host(destination, host, flush, bye)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool _mdns_append_host_list(mdns_out_answer_t ** destination, bool flush, bool bye)
+{
+ if (!_str_null_or_empty(_mdns_server->hostname)) {
+ mdns_host_item_t * self_host = mdns_get_host_item(_mdns_server->hostname);
+ if (!_mdns_append_host(destination, self_host, flush, bye)) {
+ return false;
+ }
+ }
+ mdns_host_item_t * host = _mdns_host_list;
+ while (host != NULL) {
+ host = host->next;
+ if (!_mdns_append_host(destination, host, flush, bye)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool _mdns_append_host_question(mdns_out_question_t **questions, const char *hostname, bool unicast)
+{
+ mdns_out_question_t *q = (mdns_out_question_t *)malloc(sizeof(mdns_out_question_t));
+ if (!q) {
+ HOOK_MALLOC_FAILED;
+ return false;
+ }
+ q->next = NULL;
+ q->unicast = unicast;
+ q->type = MDNS_TYPE_ANY;
+ q->host = hostname;
+ q->service = NULL;
+ q->proto = NULL;
+ q->domain = MDNS_DEFAULT_DOMAIN;
+ q->own_dynamic_memory = false;
+ if (_mdns_question_exists(q, *questions)) {
+ free(q);
+ } else {
+ queueToEnd(mdns_out_question_t, *questions, q);
+ }
+ return true;
+}
+
+static bool _mdns_append_host_questions_for_services(mdns_out_question_t **questions, mdns_srv_item_t *services[],
+ size_t len, bool unicast)
+{
+ if (!_str_null_or_empty(_mdns_server->hostname) &&
+ !_mdns_append_host_question(questions, _mdns_server->hostname, unicast)) {
+ return false;
+ }
+ for (size_t i = 0; i < len; i++) {
+ if (!_mdns_append_host_question(questions, services[i]->service->hostname, unicast)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * @brief Create probe packet for particular services on particular PCB
+ */
+static mdns_tx_packet_t * _mdns_create_probe_packet(mdns_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;
+ q->own_dynamic_memory = false;
+ if (!q->host || _mdns_question_exists(q, packet->questions)) {
+ free(q);
+ continue;
+ } else {
+ queueToEnd(mdns_out_question_t, packet->questions, q);
+ }
+
+ if (!q->host || !_mdns_alloc_answer(&packet->servers, MDNS_TYPE_SRV, services[i]->service, NULL, false, false)) {
+ _mdns_free_tx_packet(packet);
+ return NULL;
+ }
+ }
+
+ if (include_ip) {
+ if (!_mdns_append_host_questions_for_services(&packet->questions, services, len, first)) {
+ _mdns_free_tx_packet(packet);
+ return NULL;
+ }
+
+ if (!_mdns_append_host_list_in_services(&packet->servers, services, len, 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(mdns_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, NULL, false, false)
+ || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, services[i]->service, NULL, false, false)
+ || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, services[i]->service, NULL, true, false)
+ || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, services[i]->service, NULL, true, false)) {
+ _mdns_free_tx_packet(packet);
+ return NULL;
+ }
+ }
+ if (include_ip) {
+ if (!_mdns_append_host_list_in_services(&packet->servers, services, len, 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, NULL, false, false)
+ || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, s->service, NULL, false, false)
+ || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, s->service, NULL, true, false)
+ || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, s->service, NULL, true, false)) {
+ _mdns_free_tx_packet(packet);
+ return NULL;
+ }
+ mdns_host_item_t *host = mdns_get_host_item(s->service->hostname);
+ if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_A, NULL, host, true, false)
+ || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_AAAA, NULL, host, 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, s->host, 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(mdns_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;
+ }
+ packet->flags = MDNS_FLAGS_AUTHORITATIVE;
+ size_t i;
+ for (i=0; ianswers, MDNS_TYPE_PTR, services[i]->service, NULL, true, true)) {
+ _mdns_free_tx_packet(packet);
+ return;
+ }
+ }
+ if (include_ip) {
+ _mdns_append_host_list_in_services(&packet->answers, services, len, true, true);
+ }
+ _mdns_dispatch_tx_packet(packet);
+ _mdns_free_tx_packet(packet);
+}
+
+/**
+ * @brief Send probe for additional services on particular PCB
+ */
+static void _mdns_init_pcb_probe_new_service(mdns_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;
+
+ 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) {
+ HOOK_MALLOC_FAILED;
+ 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 Send probe for particular services on particular PCB
+ *
+ * Tests possible duplication on probing service structure and probes only for new entries.
+ * - If pcb probing then add only non-probing services and restarts probing
+ * - If pcb not probing, run probing for all specified services
+ */
+static void _mdns_init_pcb_probe(mdns_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];
+
+ _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)) {
+ // Looking for already probing services to resolve duplications
+ mdns_srv_item_t * new_probe_services[len];
+ int new_probe_service_len = 0;
+ bool found;
+ for (size_t j=0; j < len; ++j) {
+ found = false;
+ for (int i=0; i < pcb->probe_services_len; ++i) {
+ if (pcb->probe_services[i] == services[j]) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ new_probe_services[new_probe_service_len++] = services[j];
+ }
+ }
+ // init probing for newly added services
+ _mdns_init_pcb_probe_new_service(tcpip_if, ip_protocol,
+ new_probe_service_len?new_probe_services:NULL, new_probe_service_len, probe_ip);
+ } else {
+ // not probing, so init for all services
+ _mdns_init_pcb_probe_new_service(tcpip_if, ip_protocol, services, len, probe_ip);
+ }
+}
+
+/**
+ * @brief Restart the responder on particular PCB
+ */
+static void _mdns_restart_pcb(mdns_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((mdns_if_t)i, (mdns_ip_protocol_t)j, services, len, include_ip);
+ }
+ }
+ }
+}
+
+/**
+ * @brief Send announcement on particular PCB
+ */
+static void _mdns_announce_pcb(mdns_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, NULL, false, false)
+ || !_mdns_alloc_answer(&p->answers, MDNS_TYPE_PTR, services[i]->service, NULL, false, false)
+ || !_mdns_alloc_answer(&p->answers, MDNS_TYPE_SRV, services[i]->service, NULL, true, false)
+ || !_mdns_alloc_answer(&p->answers, MDNS_TYPE_TXT, services[i]->service, NULL, 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_append_host_list_in_services(&p->answers, services, len, 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((mdns_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) {
+ 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) {
+ 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(bool include_ip)
+{
+ 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, include_ip);
+}
+
+/**
+ * @brief Restart the responder on all services without instance
+ */
+static void _mdns_restart_all_pcbs_no_instance(void)
+{
+ 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(void)
+{
+ _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->value_len = strlen(new_item->value);
+ new_item->next = new_txt;
+ new_txt = new_item;
+ }
+ }
+ return new_txt;
+}
+
+/**
+ * @brief Deallocate the txt linked list
+ * @param txt pointer to the txt pointer to free, noop if txt==NULL
+ */
+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
+ * @param service service type
+ * @param proto service proto
+ * @param hostname service hostname
+ * @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, const char * hostname,
+ uint16_t port, const char * instance, size_t num_items,
+ mdns_txt_item_t txt[])
+{
+ mdns_service_t * s = (mdns_service_t *)calloc(1, sizeof(mdns_service_t));
+ if (!s) {
+ HOOK_MALLOC_FAILED;
+ return NULL;
+ }
+
+ mdns_txt_linked_item_t * new_txt = _mdns_allocate_txt(num_items, txt);
+ if (num_items && new_txt == NULL) {
+ goto fail;
+ }
+
+ 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->subtype = NULL;
+
+ if (hostname) {
+ s->hostname = strndup(hostname, MDNS_NAME_BUF_LEN - 1);
+ if (!s->hostname) {
+ goto fail;
+ }
+ } else {
+ s->hostname = NULL;
+ }
+
+ s->service = strndup(service, MDNS_NAME_BUF_LEN - 1);
+ if (!s->service) {
+ goto fail;
+ }
+
+ s->proto = strndup(proto, MDNS_NAME_BUF_LEN - 1);
+ if (!s->proto) {
+ goto fail;
+ }
+ return s;
+
+fail:
+ _mdns_free_linked_txt(s->txt);
+ free((char *)s->instance);
+ free((char *)s->service);
+ free((char *)s->proto);
+ free((char *)s->hostname);
+ free(s);
+
+ return NULL;
+}
+
+/**
+ * @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)
+{
+ if (!service) {
+ return;
+ }
+ 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;
+ }
+ free((char *)service->instance);
+ free((char *)service->service);
+ free((char *)service->proto);
+ free((char *)service->hostname);
+ 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);
+ }
+ while (service->subtype) {
+ mdns_subtype_t * next = service->subtype->next;
+ free((char *)service->subtype->subtype);
+ free(service->subtype);
+ service->subtype = next;
+ }
+ 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 = 0;
+ 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 += 1 /* record-len */ + strlen(txt->key) + txt->value_len + (txt->value ? 1 : 0 /* "=" */);
+ 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;
+
+ txt = service->txt;
+ while (txt) {
+ append_one_txt_record_entry(ours, &index, txt);
+ 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(mdns_if_t tcpip_if)
+{
+ uint8_t i;
+ mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
+ if (other_if == MDNS_MAX_INTERFACES) {
+ return; // no other interface found
+ }
+ 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(esp_ip4_addr_t * ip, mdns_if_t tcpip_if)
+{
+ esp_netif_ip_info_t if_ip_info;
+ esp_netif_ip_info_t other_ip_info;
+ if (!ip->addr) {
+ return 1;//denial! they win
+ }
+ if (esp_netif_get_ip_info(_mdns_get_esp_netif(tcpip_if), &if_ip_info)) {
+ return 1;//they win
+ }
+
+ int ret = memcmp((uint8_t*)&if_ip_info.ip.addr, (uint8_t*)&ip->addr, sizeof(esp_ip4_addr_t));
+ if (ret > 0) {
+ return -1;//we win
+ } else if (ret < 0) {
+ //is it the other interface?
+ mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
+ if (other_if == MDNS_MAX_INTERFACES) {
+ return 1;//AP interface! They win
+ }
+ if (esp_netif_get_ip_info(_mdns_get_esp_netif(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
+}
+
+#if CONFIG_LWIP_IPV6
+/**
+ * @brief Detect IPv6 address collision
+ */
+static int _mdns_check_aaaa_collision(esp_ip6_addr_t * ip, mdns_if_t tcpip_if)
+{
+ struct esp_ip6_addr if_ip6;
+ struct esp_ip6_addr other_ip6;
+ if (_ipv6_address_is_zero(*ip)) {
+ return 1;//denial! they win
+ }
+ if (esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(tcpip_if), &if_ip6)) {
+ return 1;//they win
+ }
+ int ret = memcmp((uint8_t*)&if_ip6.addr, (uint8_t*)ip->addr, _MDNS_SIZEOF_IP6_ADDR);
+ if (ret > 0) {
+ return -1;//we win
+ } else if (ret < 0) {
+ //is it the other interface?
+ mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
+ if (other_if == MDNS_MAX_INTERFACES) {
+ return 1;//AP interface! They win
+ }
+ if (esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(other_if), &other_ip6)) {
+ return 1;//IPv6 not active! They win
+ }
+ if (memcmp((uint8_t*)&other_ip6.addr, (uint8_t*)ip->addr, _MDNS_SIZEOF_IP6_ADDR)) {
+ return 1;//IPv6 not ours! They win
+ }
+ _mdns_dup_interface(tcpip_if);
+ return 2;//they win
+ }
+ return 0;//same
+}
+#endif
+
+static bool _hostname_is_ours(const char * hostname)
+{
+ if (strcasecmp(hostname, _mdns_server->hostname) == 0) {
+ return true;
+ }
+ mdns_host_item_t * host = _mdns_host_list;
+ while (host != NULL) {
+ if (strcasecmp(hostname, host->hostname) == 0) {
+ return true;
+ }
+ host = host->next;
+ }
+ return false;
+}
+
+/**
+ * @brief Adds a delegated hostname to the linked list
+ * @param hostname Host name pointer
+ * @param address_list Address list
+ * @return true on success
+ * false if the host wasn't attached (this is our hostname, or alloc failure) so we have to free the structs
+ */
+static bool _mdns_delegate_hostname_add(const char * hostname, mdns_ip_addr_t * address_list)
+{
+ if (_hostname_is_ours(hostname)) {
+ return false;
+ }
+
+ mdns_host_item_t * host = (mdns_host_item_t *)malloc(sizeof(mdns_host_item_t));
+
+ if (host == NULL) {
+ return false;
+ }
+ host->address_list = address_list;
+ host->hostname = hostname;
+ host->next = _mdns_host_list;
+ _mdns_host_list = host;
+ return true;
+}
+
+static void free_address_list(mdns_ip_addr_t * address_list)
+{
+ while (address_list != NULL) {
+ mdns_ip_addr_t * next = address_list->next;
+ free(address_list);
+ address_list = next;
+ }
+}
+
+static mdns_ip_addr_t * copy_address_list(const mdns_ip_addr_t * address_list)
+{
+ mdns_ip_addr_t * head = NULL;
+ mdns_ip_addr_t * tail = NULL;
+ while (address_list != NULL) {
+ mdns_ip_addr_t * addr = (mdns_ip_addr_t *)malloc(sizeof(mdns_ip_addr_t));
+ if (addr == NULL) {
+ free_address_list(head);
+ return NULL;
+ }
+ addr->addr = address_list->addr;
+ addr->next = NULL;
+ if (head == NULL) {
+ head = addr;
+ tail = addr;
+ } else {
+ tail->next = addr;
+ tail = tail->next;
+ }
+ address_list = address_list->next;
+ }
+ return head;
+}
+
+static void free_delegated_hostnames(void)
+{
+ mdns_host_item_t * host = _mdns_host_list;
+ while (host != NULL) {
+ free_address_list(host->address_list);
+ free((char *)host->hostname);
+ mdns_host_item_t *item = host;
+ host = host->next;
+ free(item);
+ }
+}
+
+static bool _mdns_delegate_hostname_remove(const char * hostname)
+{
+ mdns_srv_item_t * srv = _mdns_server->services;
+ mdns_srv_item_t * prev_srv = NULL;
+ while (srv) {
+ if (strcasecmp(srv->service->hostname, hostname) == 0) {
+ mdns_srv_item_t * to_free = srv;
+ _mdns_send_bye(&srv, 1, false);
+ _mdns_remove_scheduled_service_packets(srv->service);
+ if (prev_srv == NULL) {
+ _mdns_server->services = srv->next;
+ srv = srv->next;
+ } else {
+ prev_srv->next = srv->next;
+ srv = srv->next;
+ }
+ _mdns_free_service(to_free->service);
+ free(to_free);
+ } else {
+ prev_srv = srv;
+ srv = srv->next;
+ }
+ }
+ mdns_host_item_t * host = _mdns_host_list;
+ mdns_host_item_t * prev_host = NULL;
+ while (host != NULL) {
+ if (strcasecmp(hostname, host->hostname) == 0) {
+ if (prev_host == NULL) {
+ _mdns_host_list = host->next;
+ } else {
+ prev_host->next = host->next;
+ }
+ free_address_list(host->address_list);
+ free((char *)host->hostname);
+ free(host);
+ break;
+ } else {
+ prev_host = host;
+ host = host->next;
+ }
+ }
+ return true;
+}
+
+/**
+ * @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)
+ && _hostname_is_ours(name->host))
+ {
+ 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;
+ if (name->sub) {
+ service = _mdns_get_service_item_subtype(name->host, name->service, name->proto);
+ } else if (_str_null_or_empty(name->host)) {
+ service = _mdns_get_service_item(name->service, name->proto, NULL);
+ } else {
+ service = _mdns_get_service_item_instance(name->host, name->service, name->proto, NULL);
+ }
+ if (!service) {
+ return false;
+ }
+
+ //if query is PTR query and we have service, we have success
+ if (name->sub || _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, size_t packet_len)
+{
+ name->parts = 0;
+ name->sub = 0;
+ name->host[0] = 0;
+ name->service[0] = 0;
+ name->proto[0] = 0;
+ name->domain[0] = 0;
+ name->invalid = false;
+
+ static char buf[MDNS_NAME_BUF_LEN];
+
+ const uint8_t * next_data = (uint8_t*)_mdns_read_fqdn(packet, start, name, buf, packet_len);
+ if (!next_data) {
+ return 0;
+ }
+ if (!name->parts || name->invalid) {
+ 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;
+ }
+ name->invalid = true; // mark the current name invalid, but continue with other question
+ return next_data;
+}
+
+/**
+ * @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 (question->service && question->proto && question->domain
+ && !strcasecmp(service->service->service, question->service)
+ && !strcasecmp(service->service->proto, question->proto)
+ && !strcasecmp(MDNS_DEFAULT_DOMAIN, question->domain)) {
+ return true;
+ }
+ } else if (service && (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT)) {
+ const char * name = _mdns_get_service_instance_name(service->service);
+ if (name && question->host && question->service && question->proto && question->domain
+ && !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)
+{
+ if (*data == '=') {
+ return -1;
+ }
+ for (size_t 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, uint8_t **out_value_len,
+ 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);
+ if (!txt) {
+ HOOK_MALLOC_FAILED;
+ return;
+ }
+ uint8_t * txt_value_len = (uint8_t *)malloc(num_items);
+ if (!txt_value_len) {
+ free(txt);
+ HOOK_MALLOC_FAILED;
+ return;
+ }
+ memset(txt, 0, sizeof(mdns_txt_item_t) * num_items);
+ memset(txt_value_len, 0, 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) {
+ HOOK_MALLOC_FAILED;
+ goto handle_error;//error
+ }
+
+ mdns_txt_item_t * t = &txt[txt_num];
+ uint8_t * value_len = &txt_value_len[txt_num];
+ txt_num++;
+
+ memcpy(key, data + i, name_len);
+ key[name_len] = 0;
+ i += name_len + 1;
+ t->key = key;
+
+ int new_value_len = partLen - name_len - 1;
+ if (new_value_len > 0) {
+ char * value = (char *)malloc(new_value_len + 1);
+ if (!value) {
+ HOOK_MALLOC_FAILED;
+ goto handle_error;//error
+ }
+ memcpy(value, data + i, new_value_len);
+ value[new_value_len] = 0;
+ *value_len = new_value_len;
+ i += new_value_len;
+ t->value = value;
+ }
+ }
+
+ *out_txt = txt;
+ *out_count = txt_num;
+ *out_value_len = txt_value_len;
+ return;
+
+handle_error :
+ for (y=0; ykey);
+ free((char *)t->value);
+ }
+ free(txt_value_len);
+ 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 = _mdns_get_packet_data(packet);
+ size_t len = _mdns_get_packet_len(packet);
+ 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 == ESP_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
+
+ // Check if the packet wasn't sent by us
+ if (packet->ip_protocol == MDNS_IP_PROTOCOL_V4) {
+ esp_netif_ip_info_t if_ip_info;
+ if (esp_netif_get_ip_info(_mdns_get_esp_netif(packet->tcpip_if), &if_ip_info) == ESP_OK &&
+ memcmp(&if_ip_info.ip.addr, &packet->src.u_addr.ip4.addr, sizeof(esp_ip4_addr_t)) == 0) {
+ return;
+ }
+#if CONFIG_LWIP_IPV6
+ } else {
+ struct esp_ip6_addr if_ip6;
+ if (esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(packet->tcpip_if), &if_ip6) == ESP_OK &&
+ memcmp(&if_ip6, &packet->src.u_addr.ip6, sizeof(esp_ip6_addr_t)) == 0) {
+ return;
+ }
+#endif
+ }
+
+ // Check for the minimum size of mdns packet
+ if (len <= MDNS_HEAD_ADDITIONAL_OFFSET) {
+ return;
+ }
+
+ mdns_parsed_packet_t * parsed_packet = (mdns_parsed_packet_t *)malloc(sizeof(mdns_parsed_packet_t));
+ if (!parsed_packet) {
+ HOOK_MALLOC_FAILED;
+ 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 && !header.answers && _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;
+ parsed_packet->id = header.id;
+ esp_netif_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, len);
+ if (!content) {
+ header.answers = 0;
+ header.additional = 0;
+ header.servers = 0;
+ goto clear_rx_packet;//error
+ }
+
+ if (content + MDNS_CLASS_OFFSET + 1 >= data + len) {
+ goto clear_rx_packet; // malformed packet, won't read behind it
+ }
+ uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET);
+ uint16_t mdns_class = _mdns_read_u16(content, MDNS_CLASS_OFFSET);
+ bool unicast = !!(mdns_class & 0x8000);
+ mdns_class &= 0x7FFF;
+ content = content + 4;
+
+ if (mdns_class != 0x0001 || name->invalid) {//bad class or invalid name for this question entry
+ 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 *)calloc(1, sizeof(mdns_parsed_question_t));
+ if (!question) {
+ HOOK_MALLOC_FAILED;
+ 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;
+ }
+ if (!_mdns_name_is_ours(name)) {
+ continue;
+ }
+
+ if (type == MDNS_TYPE_ANY && !_str_null_or_empty(name->host)) {
+ parsed_packet->probe = true;
+ }
+
+ mdns_parsed_question_t * question = (mdns_parsed_question_t *)calloc(1, sizeof(mdns_parsed_question_t));
+ if (!question) {
+ HOOK_MALLOC_FAILED;
+ goto clear_rx_packet;
+ }
+ question->next = parsed_packet->questions;
+ parsed_packet->questions = question;
+
+ question->unicast = unicast;
+ question->type = type;
+ question->sub = name->sub;
+ 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 && !header.answers) {
+ 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, len);
+ if (!content) {
+ goto clear_rx_packet;//error
+ }
+
+ if (content + MDNS_LEN_OFFSET + 1 >= data + len) {
+ goto clear_rx_packet; // malformed packet, won't read behind it
+ }
+ uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET);
+ uint16_t mdns_class = _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;
+ mdns_class &= 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_record_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, NULL);
+ }
+ } else {
+ if (!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, len)) {
+ continue;//error
+ }
+ if (search_result) {
+ _mdns_search_result_add_ptr(search_result, name->host, name->service, name->proto,
+ packet->tcpip_if, packet->ip_protocol, ttl);
+ } else if ((discovery || ours) && !name->sub && _mdns_name_is_ours(name)) {
+ if (discovery && (service = _mdns_get_service_item(name->service, name->proto, NULL))) {
+ _mdns_remove_parsed_question(parsed_packet, MDNS_TYPE_SDPTR, service);
+ } else if (service && parsed_packet->questions && !parsed_packet->probe) {
+ _mdns_remove_parsed_question(parsed_packet, type, service);
+ } else if (service) {
+ //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 (_mdns_get_esp_netif(packet->tcpip_if) == result->esp_netif
+ && 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, name->service, name->proto,
+ packet->tcpip_if, packet->ip_protocol, ttl);
+ if (!result) {
+ continue;//error
+ }
+ }
+ }
+
+ if (!_mdns_parse_fqdn(data, data_ptr + MDNS_SRV_FQDN_OFFSET, name, len)) {
+ continue;//error
+ }
+ if (data_ptr + MDNS_SRV_PORT_OFFSET + 1 >= data + len) {
+ goto clear_rx_packet; // malformed packet, won't read behind it
+ }
+ 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) {
+ if (!result->hostname) { // assign host/port for this entry only if not previously set
+ 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, ttl);
+ }
+ } 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 (mdns_class > 1) {
+ col = 1;
+ } else if (!mdns_class) {
+ col = -1;
+ } else if (service) { // only detect srv collision if service existed
+ col = _mdns_check_srv_collision(service->service, priority, weight, port, name->host, name->domain);
+ }
+ if (service && 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) {
+ _mdns_remap_self_service_hostname(_mdns_server->hostname, new_host);
+ free((char *)_mdns_server->hostname);
+ _mdns_server->hostname = new_host;
+ _mdns_self_host.hostname = new_host;
+ }
+ _mdns_restart_all_pcbs();
+ }
+ } else if (service) {
+ _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;
+ uint8_t *txt_value_len = NULL;
+ size_t txt_count = 0;
+
+ mdns_result_t * result = NULL;
+ if (search_result->type == MDNS_TYPE_PTR) {
+ result = search_result->result;
+ while (result) {
+ if (_mdns_get_esp_netif(packet->tcpip_if) == result->esp_netif
+ && 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, name->service, name->proto,
+ packet->tcpip_if, packet->ip_protocol, ttl);
+ if (!result) {
+ continue;//error
+ }
+ }
+ if (!result->txt) {
+ _mdns_result_txt_create(data_ptr, data_len, &txt, &txt_value_len, &txt_count);
+ if (txt_count) {
+ result->txt = txt;
+ result->txt_count = txt_count;
+ result->txt_value_len = txt_value_len;
+ }
+ }
+ } else {
+ _mdns_result_txt_create(data_ptr, data_len, &txt, &txt_value_len, &txt_count);
+ if (txt_count) {
+ _mdns_search_result_add_txt(search_result, txt, txt_value_len, txt_count, packet->tcpip_if, packet->ip_protocol, ttl);
+ }
+ }
+ } 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 (mdns_class > 1) {
+ col = 1;
+ } else if (!mdns_class) {
+ col = -1;
+ } else if (service) { // only detect txt collision if service existed
+ 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 && service) {
+ 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);
+ }
+ }
+
+ }
+#if CONFIG_LWIP_IPV6
+ else if (type == MDNS_TYPE_AAAA) {//ipv6
+ esp_ip_addr_t ip6;
+ ip6.type = ESP_IPADDR_TYPE_V6;
+ memcpy(ip6.u_addr.ip6.addr, data_ptr, MDNS_ANSWER_AAAA_SIZE);
+ 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, ttl);
+ 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 (mdns_class > 1) {
+ col = 1;
+ } else if (!mdns_class) {
+ 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) {
+ _mdns_remap_self_service_hostname(_mdns_server->hostname, new_host);
+ free((char *)_mdns_server->hostname);
+ _mdns_server->hostname = new_host;
+ _mdns_self_host.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);
+ }
+ }
+
+ }
+#endif
+ else if (type == MDNS_TYPE_A) {
+ esp_ip_addr_t ip;
+ ip.type = ESP_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, ttl);
+ 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 (mdns_class > 1) {
+ col = 1;
+ } else if (!mdns_class) {
+ 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) {
+ _mdns_remap_self_service_hostname(_mdns_server->hostname, new_host);
+ free((char *)_mdns_server->hostname);
+ _mdns_server->hostname = new_host;
+ _mdns_self_host.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;
+ if (question->host) {
+ free(question->host);
+ }
+ if (question->service) {
+ free(question->service);
+ }
+ if (question->proto) {
+ free(question->proto);
+ }
+ if (question->domain) {
+ free(question->domain);
+ }
+ free(question);
+ }
+ free(parsed_packet);
+}
+
+/**
+ * @brief Enable mDNS interface
+ */
+void _mdns_enable_pcb(mdns_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(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
+{
+ _mdns_clean_netif_ptr(tcpip_if);
+
+ 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);
+ mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
+ if (other_if != MDNS_MAX_INTERFACES && _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 Performs interface changes based on system events or custom commands
+ */
+static void perform_event_action(mdns_if_t mdns_if, mdns_event_actions_t action)
+{
+ if (!_mdns_server || mdns_if >= MDNS_MAX_INTERFACES) {
+ return;
+ }
+ if (action & MDNS_EVENT_ENABLE_IP4) {
+ _mdns_enable_pcb(mdns_if, MDNS_IP_PROTOCOL_V4);
+ }
+ if (action & MDNS_EVENT_ENABLE_IP6) {
+ _mdns_enable_pcb(mdns_if, MDNS_IP_PROTOCOL_V6);
+ }
+ if (action & MDNS_EVENT_DISABLE_IP4) {
+ _mdns_disable_pcb(mdns_if, MDNS_IP_PROTOCOL_V4);
+ }
+ if (action & MDNS_EVENT_DISABLE_IP6) {
+ _mdns_disable_pcb(mdns_if, MDNS_IP_PROTOCOL_V6);
+ }
+ if (action & MDNS_EVENT_ANNOUNCE_IP4) {
+ _mdns_announce_pcb(mdns_if, MDNS_IP_PROTOCOL_V4, NULL, 0, true);
+ }
+ if (action & MDNS_EVENT_ANNOUNCE_IP6) {
+ _mdns_announce_pcb(mdns_if, MDNS_IP_PROTOCOL_V6, NULL, 0, true);
+ }
+}
+
+/**
+ * @brief Dispatch interface changes based on system events
+ */
+static inline void post_mdns_disable_pcb(mdns_predef_if_t preset_if, mdns_ip_protocol_t protocol)
+{
+ mdns_post_custom_action_tcpip_if(mdns_if_from_preset_if(preset_if), protocol == MDNS_IP_PROTOCOL_V4 ? MDNS_EVENT_DISABLE_IP4 : MDNS_EVENT_DISABLE_IP6);
+}
+
+static inline void post_mdns_enable_pcb(mdns_predef_if_t preset_if, mdns_ip_protocol_t protocol)
+{
+ mdns_post_custom_action_tcpip_if(mdns_if_from_preset_if(preset_if), protocol == MDNS_IP_PROTOCOL_V4 ? MDNS_EVENT_ENABLE_IP4 : MDNS_EVENT_ENABLE_IP6);
+}
+
+static inline void post_mdns_announce_pcb(mdns_predef_if_t preset_if, mdns_ip_protocol_t protocol)
+{
+ mdns_post_custom_action_tcpip_if(mdns_if_from_preset_if(preset_if), protocol == MDNS_IP_PROTOCOL_V4 ? MDNS_EVENT_ANNOUNCE_IP4 : MDNS_EVENT_ANNOUNCE_IP6);
+}
+
+void mdns_preset_if_handle_system_event(void *arg, esp_event_base_t event_base,
+ int32_t event_id, void *event_data)
+{
+ if (!_mdns_server) {
+ return;
+ }
+
+ esp_netif_dhcp_status_t dcst;
+ if (event_base == WIFI_EVENT) {
+ switch(event_id) {
+ case WIFI_EVENT_STA_CONNECTED:
+ if (!esp_netif_dhcpc_get_status(esp_netif_from_preset_if(MDNS_IF_STA), &dcst)) {
+ if (dcst == ESP_NETIF_DHCP_STOPPED) {
+ post_mdns_enable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V4);
+ }
+ }
+ break;
+ case WIFI_EVENT_STA_DISCONNECTED:
+ post_mdns_disable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V4);
+ post_mdns_disable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V6);
+ break;
+ case WIFI_EVENT_AP_START:
+ post_mdns_enable_pcb(MDNS_IF_AP, MDNS_IP_PROTOCOL_V4);
+ break;
+ case WIFI_EVENT_AP_STOP:
+ post_mdns_disable_pcb(MDNS_IF_AP, MDNS_IP_PROTOCOL_V4);
+ post_mdns_disable_pcb(MDNS_IF_AP, MDNS_IP_PROTOCOL_V6);
+ break;
+ default:
+ break;
+ }
+ }
+#if CONFIG_ETH_ENABLED
+ else if (event_base == ETH_EVENT) {
+ switch (event_id) {
+ case ETHERNET_EVENT_CONNECTED:
+ if (!esp_netif_dhcpc_get_status(esp_netif_from_preset_if(MDNS_IF_ETH), &dcst)) {
+ if (dcst == ESP_NETIF_DHCP_STOPPED) {
+ post_mdns_enable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V4);
+ }
+ }
+ break;
+ case ETHERNET_EVENT_DISCONNECTED:
+ post_mdns_disable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V4);
+ post_mdns_disable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V6);
+ break;
+ default:
+ break;
+ }
+ }
+#endif
+ else if (event_base == IP_EVENT) {
+ switch (event_id) {
+ case IP_EVENT_STA_GOT_IP:
+ post_mdns_enable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V4);
+ post_mdns_announce_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V6);
+ break;
+#if CONFIG_ETH_ENABLED
+ case IP_EVENT_ETH_GOT_IP:
+ post_mdns_enable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V4);
+ break;
+#endif
+ case IP_EVENT_GOT_IP6:
+ {
+ ip_event_got_ip6_t* event = (ip_event_got_ip6_t*) event_data;
+ mdns_if_t mdns_if = _mdns_get_if_from_esp_netif(event->esp_netif);
+ if (mdns_if < MDNS_MAX_INTERFACES) {
+ post_mdns_enable_pcb(mdns_if, MDNS_IP_PROTOCOL_V6);
+ post_mdns_announce_pcb(mdns_if, MDNS_IP_PROTOCOL_V4);
+ }
+
+ }
+ 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->done_semaphore);
+ 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, bool unicast,
+ uint32_t timeout, uint8_t max_results, mdns_query_notify_t notifier)
+{
+ mdns_search_once_t * search = (mdns_search_once_t *)malloc(sizeof(mdns_search_once_t));
+ if (!search) {
+ HOOK_MALLOC_FAILED;
+ return NULL;
+ }
+ memset(search, 0, sizeof(mdns_search_once_t));
+
+ search->done_semaphore = xSemaphoreCreateBinary();
+ if (!search->done_semaphore) {
+ 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->unicast = unicast;
+ 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->notifier = notifier;
+ search->next = NULL;
+
+ 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);
+ if (search->notifier) {
+ search->notifier(search);
+ }
+ xSemaphoreGive(search->done_semaphore);
+}
+
+/**
+ * @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(void)
+{
+ 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(esp_ip_addr_t * ip)
+{
+ mdns_ip_addr_t * a = (mdns_ip_addr_t *)malloc(sizeof(mdns_ip_addr_t));
+ if (!a) {
+ HOOK_MALLOC_FAILED;
+ return NULL;
+ }
+ memset(a, 0 , sizeof(mdns_ip_addr_t));
+ a->addr.type = ip->type;
+ if (ip->type == ESP_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;
+}
+
+static inline void _mdns_result_update_ttl(mdns_result_t * r, uint32_t ttl)
+{
+ r->ttl = r->ttl < ttl ? r->ttl : ttl;
+}
+
+/**
+ * @brief Chain new IP to search result
+ */
+static void _mdns_result_add_ip(mdns_result_t * r, esp_ip_addr_t * ip)
+{
+ mdns_ip_addr_t * a = r->addr;
+ while (a) {
+ if (a->addr.type == ip->type) {
+ if (a->addr.type == ESP_IPADDR_TYPE_V4 && a->addr.u_addr.ip4.addr == ip->u_addr.ip4.addr) {
+ return;
+ }
+ if (a->addr.type == ESP_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, esp_ip_addr_t * ip,
+ mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint32_t ttl)
+{
+ mdns_result_t * r = NULL;
+ mdns_ip_addr_t * a = NULL;
+
+ if ((search->type == MDNS_TYPE_A && ip->type == ESP_IPADDR_TYPE_V4)
+ || (search->type == MDNS_TYPE_AAAA && ip->type == ESP_IPADDR_TYPE_V6)
+ || search->type == MDNS_TYPE_ANY) {
+ r = search->result;
+ while (r) {
+ if (r->esp_netif == _mdns_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol) {
+ _mdns_result_add_ip(r, ip);
+ _mdns_result_update_ttl(r, ttl);
+ 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) {
+ HOOK_MALLOC_FAILED;
+ 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->hostname = strdup(hostname);
+ r->addr = a;
+ r->esp_netif = _mdns_get_esp_netif(tcpip_if);
+ r->ip_protocol = ip_protocol;
+ r->next = search->result;
+ r->ttl = ttl;
+ search->result = r;
+ search->num_results++;
+ }
+ } else if (search->type == MDNS_TYPE_PTR || search->type == MDNS_TYPE_SRV) {
+ r = search->result;
+ while (r) {
+ if (r->esp_netif == _mdns_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->hostname) && !strcasecmp(hostname, r->hostname)) {
+ _mdns_result_add_ip(r, ip);
+ _mdns_result_update_ttl(r, ttl);
+ 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,
+ const char * service_type, const char * proto, mdns_if_t tcpip_if,
+ mdns_ip_protocol_t ip_protocol, uint32_t ttl)
+{
+ mdns_result_t * r = search->result;
+ while (r) {
+ if (r->esp_netif == _mdns_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->instance_name) && !strcasecmp(instance, r->instance_name)) {
+ _mdns_result_update_ttl(r, ttl);
+ 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) {
+ HOOK_MALLOC_FAILED;
+ return NULL;
+ }
+
+ memset(r, 0 , sizeof(mdns_result_t));
+ r->instance_name = strdup(instance);
+ r->service_type = strdup(service_type);
+ r->proto = strdup(proto);
+ if (!r->instance_name) {
+ free(r);
+ return NULL;
+ }
+
+ r->esp_netif = _mdns_get_esp_netif(tcpip_if);
+ r->ip_protocol = ip_protocol;
+ r->ttl = ttl;
+ 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,
+ mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint32_t ttl)
+{
+ mdns_result_t * r = search->result;
+ while (r) {
+ if (r->esp_netif == _mdns_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->hostname) && !strcasecmp(hostname, r->hostname)) {
+ _mdns_result_update_ttl(r, ttl);
+ 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) {
+ HOOK_MALLOC_FAILED;
+ return;
+ }
+
+ memset(r, 0 , sizeof(mdns_result_t));
+ r->hostname = strdup(hostname);
+ if (!r->hostname) {
+ free(r);
+ return;
+ }
+ if (search->instance) {
+ r->instance_name = strdup(search->instance);
+ }
+ r->service_type = strdup(search->service);
+ r->proto = strdup(search->proto);
+ r->port = port;
+ r->esp_netif = _mdns_get_esp_netif(tcpip_if);
+ r->ip_protocol = ip_protocol;
+ r->ttl = ttl;
+ 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, uint8_t *txt_value_len,
+ size_t txt_count, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol,
+ uint32_t ttl)
+{
+ mdns_result_t * r = search->result;
+ while (r) {
+ if (r->esp_netif == _mdns_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol) {
+ if (r->txt) {
+ goto free_txt;
+ }
+ r->txt = txt;
+ r->txt_value_len = txt_value_len;
+ r->txt_count = txt_count;
+ _mdns_result_update_ttl(r, ttl);
+ 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) {
+ HOOK_MALLOC_FAILED;
+ goto free_txt;
+ }
+
+ memset(r, 0 , sizeof(mdns_result_t));
+ r->txt = txt;
+ r->txt_value_len = txt_value_len;
+ r->txt_count = txt_count;
+ r->esp_netif = _mdns_get_esp_netif(tcpip_if);
+ r->ip_protocol = ip_protocol;
+ r->ttl = ttl;
+ r->next = search->result;
+ search->result = r;
+ search->num_results++;
+ }
+ return;
+
+free_txt:
+ for (size_t i=0; istate == 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->type != MDNS_TYPE_SRV))
+ {
+ s = s->next;
+ continue;
+ }
+ if (s->type != MDNS_TYPE_PTR && s->type != MDNS_TYPE_SRV) {
+ if (!strcasecmp(name->host, s->instance)) {
+ return s;
+ }
+ s = s->next;
+ continue;
+ }
+ r = s->result;
+ while (r) {
+ if (r->esp_netif == _mdns_get_esp_netif(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 (s->instance && strcasecmp(name->host, s->instance) == 0) {
+ 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 particular interface
+ */
+static mdns_tx_packet_t * _mdns_create_search_packet(mdns_search_once_t * search, mdns_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) {
+ HOOK_MALLOC_FAILED;
+ _mdns_free_tx_packet(packet);
+ return NULL;
+ }
+ q->next = NULL;
+ q->unicast = search->unicast;
+ q->type = search->type;
+ q->host = search->instance;
+ q->service = search->service;
+ q->proto = search->proto;
+ q->domain = MDNS_DEFAULT_DOMAIN;
+ q->own_dynamic_memory = false;
+ 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->esp_netif != _mdns_get_esp_netif(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) {
+ HOOK_MALLOC_FAILED;
+ _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, mdns_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_INIT) {
+ 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)
+{
+ mdns_search_once_t* queue = _mdns_server->search_once;
+ bool found = false;
+ // looking for this search in active searches
+ while (queue) {
+ if (queue == search) {
+ found = true;
+ break;
+ }
+ queue = queue->next;
+ }
+
+ if (!found) {
+ // no longer active -> skip sending this search
+ return;
+ }
+
+ 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;
+ }
+}
+
+static void _mdns_remap_self_service_hostname(const char * old_hostname, const char * new_hostname)
+{
+ mdns_srv_item_t * service = _mdns_server->services;
+
+ while (service) {
+ if (strcmp(service->service->hostname, old_hostname) == 0) {
+ free((char *)service->service->hostname);
+ service->service->hostname = strdup(new_hostname);
+ }
+ service = service->next;
+ }
+}
+
+/**
+ * @brief Free action data
+ */
+static void _mdns_free_action(mdns_action_t * action)
+{
+ switch(action->type) {
+ case ACTION_HOSTNAME_SET:
+ free(action->data.hostname_set.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:
+ _mdns_free_linked_txt(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_SERVICE_SUBTYPE_ADD:
+ free(action->data.srv_subtype_add.subtype);
+ 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:
+ _mdns_packet_free(action->data.rx_handle.packet);
+ break;
+ case ACTION_DELEGATE_HOSTNAME_ADD:
+ free((char *)action->data.delegate_hostname.hostname);
+ free_address_list(action->data.delegate_hostname.address_list);
+ break;
+ case ACTION_DELEGATE_HOSTNAME_REMOVE:
+ free((char *)action->data.delegate_hostname.hostname);
+ 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;
+ char *subtype;
+ mdns_subtype_t *subtype_item;
+ mdns_txt_linked_item_t * txt, * t;
+
+ switch(action->type) {
+ case ACTION_SYSTEM_EVENT:
+ perform_event_action(action->data.sys_event.interface, action->data.sys_event.event_action);
+ break;
+ case ACTION_HOSTNAME_SET:
+ _mdns_send_bye_all_pcbs_no_instance(true);
+ _mdns_remap_self_service_hostname(_mdns_server->hostname, action->data.hostname_set.hostname);
+ free((char*)_mdns_server->hostname);
+ _mdns_server->hostname = action->data.hostname_set.hostname;
+ _mdns_self_host.hostname = action->data.hostname_set.hostname;
+ _mdns_restart_all_pcbs();
+ xTaskNotifyGive(action->data.hostname_set.calling_task);
+ break;
+ case ACTION_INSTANCE_SET:
+ _mdns_send_bye_all_pcbs_no_instance(false);
+ 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;
+ _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;
+ 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;
+ txt->value_len = action->data.srv_txt_set.value_len;
+ break;
+ }
+ txt = txt->next;
+ }
+ if (!txt) {
+ txt = (mdns_txt_linked_item_t *)malloc(sizeof(mdns_txt_linked_item_t));
+ if (!txt) {
+ HOOK_MALLOC_FAILED;
+ _mdns_free_action(action);
+ return;
+ }
+ txt->key = key;
+ txt->value = value;
+ txt->value_len = action->data.srv_txt_set.value_len;
+ 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_SUBTYPE_ADD:
+ service = action->data.srv_subtype_add.service->service;
+ subtype = action->data.srv_subtype_add.subtype;
+ subtype_item = (mdns_subtype_t *)malloc(sizeof(mdns_subtype_t));
+ if (!subtype_item) {
+ HOOK_MALLOC_FAILED;
+ _mdns_free_action(action);
+ return;
+ }
+ subtype_item->subtype = subtype;
+ subtype_item->next = service->subtype;
+ service->subtype = subtype_item;
+ break;
+ case ACTION_SERVICE_DEL:
+ a = _mdns_server->services;
+ if (action->data.srv_del.service) {
+ if (_mdns_server->services == action->data.srv_del.service) {
+ _mdns_server->services = a->next;
+ _mdns_send_bye(&a, 1, false);
+ _mdns_remove_scheduled_service_packets(a->service);
+ _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_remove_scheduled_service_packets(b->service);
+ _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_remove_scheduled_service_packets(s->service);
+ _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_packet_t * p = _mdns_server->tx_queue_head;
+ // packet to be handled should be at tx head, but must be consistent with the one pushed to action queue
+ if (p && p==action->data.tx_handle.packet && p->queued) {
+ p->queued = false; // clearing, as the packet might be reused (pushed and transmitted again)
+ _mdns_server->tx_queue_head = p->next;
+ _mdns_tx_handle_packet(p);
+ } else {
+ ESP_LOGD(TAG, "Skipping transmit of an unexpected packet!");
+ }
+ }
+ break;
+ case ACTION_RX_HANDLE:
+ mdns_parse_packet(action->data.rx_handle.packet);
+ _mdns_packet_free(action->data.rx_handle.packet);
+ break;
+ case ACTION_DELEGATE_HOSTNAME_ADD:
+ if (!_mdns_delegate_hostname_add(action->data.delegate_hostname.hostname,
+ action->data.delegate_hostname.address_list)) {
+ free((char *)action->data.delegate_hostname.hostname);
+ free_address_list(action->data.delegate_hostname.address_list);
+ }
+ break;
+ case ACTION_DELEGATE_HOSTNAME_REMOVE:
+ _mdns_delegate_hostname_remove(action->data.delegate_hostname.hostname);
+ free((char *)action->data.delegate_hostname.hostname);
+ 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) {
+ HOOK_MALLOC_FAILED;
+ return ESP_ERR_NO_MEM;
+ }
+
+ action->type = type;
+ action->data.search_add.search = search;
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+ return ESP_OK;
+}
+
+/**
+ * @brief Called from timer task to run mDNS responder
+ *
+ * periodically checks first unqueued packet (from tx head).
+ * if it is scheduled to be transmitted, then pushes the packet to action queue to be handled.
+ *
+ */
+static void _mdns_scheduler_run(void)
+{
+ MDNS_SERVICE_LOCK();
+ mdns_tx_packet_t * p = _mdns_server->tx_queue_head;
+ mdns_action_t * action = NULL;
+
+ // find first unqueued packet
+ while (p && p->queued) {
+ p = p->next;
+ }
+ if (!p) {
+ MDNS_SERVICE_UNLOCK();
+ return;
+ }
+ if ((int32_t)(p->send_at - (xTaskGetTickCount() * portTICK_PERIOD_MS)) < 0) {
+ action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
+ if (action) {
+ action->type = ACTION_TX_HANDLE;
+ action->data.tx_handle.packet = p;
+ p->queued = true;
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
+ free(action);
+ p->queued = false;
+ }
+ } else {
+ HOOK_MALLOC_FAILED;
+ // continue
+ }
+ }
+ MDNS_SERVICE_UNLOCK();
+}
+
+/**
+ * @brief Called from timer task to run active searches
+ */
+static void _mdns_search_run(void)
+{
+ MDNS_SERVICE_LOCK();
+ mdns_search_once_t * s = _mdns_server->search_once;
+ uint32_t now = xTaskGetTickCount() * portTICK_PERIOD_MS;
+ if (!s) {
+ MDNS_SERVICE_UNLOCK();
+ return;
+ }
+ 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(void){
+ 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(void){
+ 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(void)
+{
+ 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, MDNS_TASK_PRIORITY,
+ (TaskHandle_t * const)(&_mdns_service_task_handle), MDNS_TASK_AFFINITY);
+ if (!_mdns_service_task_handle) {
+ _mdns_stop_timer();
+ MDNS_SERVICE_UNLOCK();
+ vSemaphoreDelete(_mdns_service_semaphore);
+ _mdns_service_semaphore = NULL;
+ 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(void)
+{
+ _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, (TickType_t)0) != pdPASS) {
+ vTaskDelete(_mdns_service_task_handle);
+ _mdns_service_task_handle = NULL;
+ }
+ while (_mdns_service_task_handle) {
+ vTaskDelay(10 / portTICK_PERIOD_MS);
+ }
+ }
+ vSemaphoreDelete(_mdns_service_semaphore);
+ _mdns_service_semaphore = NULL;
+ return ESP_OK;
+}
+
+static esp_err_t mdns_post_custom_action_tcpip_if(mdns_if_t mdns_if, mdns_event_actions_t event_action)
+{
+ if (!_mdns_server || mdns_if >= MDNS_MAX_INTERFACES) {
+ return ESP_ERR_INVALID_STATE;
+ }
+
+ mdns_action_t * action = (mdns_action_t *)calloc(1, sizeof(mdns_action_t));
+ if (!action) {
+ HOOK_MALLOC_FAILED;
+ return ESP_ERR_NO_MEM;
+ }
+ action->type = ACTION_SYSTEM_EVENT;
+ action->data.sys_event.event_action = event_action;
+ action->data.sys_event.interface = mdns_if;
+
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
+ free(action);
+ }
+ return ESP_OK;
+}
+
+static inline void set_default_duplicated_interfaces(void)
+{
+ mdns_if_t wifi_sta_if = MDNS_MAX_INTERFACES;
+ mdns_if_t eth_if = MDNS_MAX_INTERFACES;
+ for (mdns_if_t i=0; ilock = 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 CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP
+ if ((err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, mdns_preset_if_handle_system_event, NULL)) != ESP_OK) {
+ goto free_event_handlers;
+ }
+#endif
+#if CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP || CONFIG_MDNS_PREDEF_NETIF_ETH
+ if ((err = esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, mdns_preset_if_handle_system_event, NULL)) != ESP_OK) {
+ goto free_event_handlers;
+ }
+#endif
+#if defined(CONFIG_ETH_ENABLED) && CONFIG_MDNS_PREDEF_NETIF_ETH
+ if ((err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, mdns_preset_if_handle_system_event, NULL)) != ESP_OK) {
+ goto free_event_handlers;
+ }
+#endif
+
+#if CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP || CONFIG_MDNS_PREDEF_NETIF_ETH
+ set_default_duplicated_interfaces();
+#endif
+
+ uint8_t i;
+#if CONFIG_LWIP_IPV6
+ esp_ip6_addr_t tmp_addr6;
+#endif
+ esp_netif_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(void)
+{
+ uint8_t i, j;
+ if (!_mdns_server) {
+ return;
+ }
+
+ // Unregister handlers before destroying the mdns internals to avoid receiving async events while deinit
+ unregister_predefined_handlers();
+
+ mdns_service_remove_all();
+ free_delegated_hostnames();
+ _mdns_service_task_stop();
+ 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->done_semaphore);
+ 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) {
+ HOOK_MALLOC_FAILED;
+ free(new_hostname);
+ return ESP_ERR_NO_MEM;
+ }
+ action->type = ACTION_HOSTNAME_SET;
+ action->data.hostname_set.hostname = new_hostname;
+ action->data.hostname_set.calling_task = xTaskGetCurrentTaskHandle();
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
+ free(new_hostname);
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+ xTaskNotifyWait(0, 0x01, NULL, portMAX_DELAY);
+ return ESP_OK;
+}
+
+esp_err_t mdns_delegate_hostname_add(const char * hostname, const mdns_ip_addr_t * address_list)
+{
+ if (!_mdns_server) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ if (_str_null_or_empty(hostname) || strlen(hostname) > (MDNS_NAME_BUF_LEN - 1) || address_list == NULL) {
+ 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) {
+ HOOK_MALLOC_FAILED;
+ free(new_hostname);
+ return ESP_ERR_NO_MEM;
+ }
+ action->type = ACTION_DELEGATE_HOSTNAME_ADD;
+ action->data.delegate_hostname.hostname = new_hostname;
+ action->data.delegate_hostname.address_list = copy_address_list(address_list);
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
+ free(new_hostname);
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+ return ESP_OK;
+}
+
+esp_err_t mdns_delegate_hostname_remove(const char * hostname)
+{
+ if (!_mdns_server) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ 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) {
+ HOOK_MALLOC_FAILED;
+ free(new_hostname);
+ return ESP_ERR_NO_MEM;
+ }
+ action->type = ACTION_DELEGATE_HOSTNAME_REMOVE;
+ action->data.delegate_hostname.hostname = new_hostname;
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
+ free(new_hostname);
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+ return ESP_OK;
+}
+
+bool mdns_hostname_exists(const char * hostname)
+{
+ return _hostname_is_ours(hostname);
+}
+
+esp_err_t mdns_instance_name_set(const char * instance)
+{
+ if (!_mdns_server) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ 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) {
+ HOOK_MALLOC_FAILED;
+ 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, (TickType_t)0) != pdPASS) {
+ free(new_instance);
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+ return ESP_OK;
+}
+
+/*
+ * MDNS SERVICES
+ * */
+
+esp_err_t mdns_service_add_for_host(const char * instance, const char * service, const char * proto, const char * hostname,
+ 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;
+ }
+
+ if (!_mdns_can_add_more_services()) {
+ return ESP_ERR_NO_MEM;
+ }
+
+ mdns_srv_item_t * item = _mdns_get_service_item_instance(instance, service, proto, hostname);
+ if (item) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ mdns_service_t * s = _mdns_create_service(service, proto, hostname, 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) {
+ HOOK_MALLOC_FAILED;
+ _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) {
+ HOOK_MALLOC_FAILED;
+ _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, (TickType_t)0) != pdPASS) {
+ _mdns_free_service(s);
+ free(item);
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+
+ size_t start = xTaskGetTickCount();
+ size_t timeout_ticks = pdMS_TO_TICKS(MDNS_SERVICE_ADD_TIMEOUT_MS);
+ while (_mdns_get_service_item_instance(instance, service, proto, hostname) == NULL) {
+ uint32_t expired = xTaskGetTickCount() - start;
+ if (expired >= timeout_ticks) {
+ return ESP_FAIL; // Timeout
+ }
+ vTaskDelay(MIN(10 / portTICK_PERIOD_MS, timeout_ticks - expired));
+ }
+
+ return ESP_OK;
+}
+
+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) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ return mdns_service_add_for_host(instance, service, proto, _mdns_server->hostname, port, txt, num_items);
+}
+
+bool mdns_service_exists(const char * service_type, const char * proto, const char * hostname)
+{
+ return _mdns_get_service_item(service_type, proto, hostname) != NULL;
+}
+
+bool mdns_service_exists_with_instance(const char *instance, const char *service_type, const char *proto,
+ const char *hostname)
+{
+ return _mdns_get_service_item_instance(instance, service_type, proto, hostname) != NULL;
+}
+
+esp_err_t mdns_service_port_set_for_host(const char *instance, const char * service, const char * proto, const char * hostname, 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_instance(instance, service, proto, hostname);
+ if (!s) {
+ return ESP_ERR_NOT_FOUND;
+ }
+
+ mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
+ if (!action) {
+ HOOK_MALLOC_FAILED;
+ 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, (TickType_t)0) != pdPASS) {
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+ return ESP_OK;
+}
+
+esp_err_t mdns_service_port_set(const char * service, const char * proto, uint16_t port)
+{
+ if (!_mdns_server) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ return mdns_service_port_set_for_host(NULL, service, proto, _mdns_server->hostname, port);
+}
+
+esp_err_t mdns_service_txt_set_for_host(const char * instance, const char * service, const char * proto, const char * hostname,
+ 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_instance(instance, service, proto, hostname);
+ if (!s) {
+ return ESP_ERR_NOT_FOUND;
+ }
+
+ mdns_txt_linked_item_t * new_txt = NULL;
+ if (num_items){
+ new_txt = _mdns_allocate_txt(num_items, txt);
+ if (!new_txt) {
+ return ESP_ERR_NO_MEM;
+ }
+ }
+
+ mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
+ if (!action) {
+ HOOK_MALLOC_FAILED;
+ _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.txt = new_txt;
+
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
+ _mdns_free_linked_txt(new_txt);
+ 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) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ return mdns_service_txt_set_for_host(NULL, service, proto, _mdns_server->hostname, txt, num_items);
+}
+
+esp_err_t mdns_service_txt_item_set_for_host_with_explicit_value_len(const char * instance, const char *service, const char *proto,
+ const char *hostname, const char *key,
+ const char *value, uint8_t value_len)
+{
+ if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) ||
+ _str_null_or_empty(key) || (!value && value_len)) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ mdns_srv_item_t *s = _mdns_get_service_item_instance(instance, service, proto, hostname);
+ if (!s) {
+ return ESP_ERR_NOT_FOUND;
+ }
+ mdns_action_t *action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
+ if (!action) {
+ HOOK_MALLOC_FAILED;
+ 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;
+ }
+ if (value_len > 0) {
+ action->data.srv_txt_set.value = (char *)malloc(value_len);
+ if (!action->data.srv_txt_set.value) {
+ free(action->data.srv_txt_set.key);
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+ memcpy(action->data.srv_txt_set.value, value, value_len);
+ action->data.srv_txt_set.value_len = value_len;
+ } else {
+ action->data.srv_txt_set.value = NULL;
+ action->data.srv_txt_set.value_len = 0;
+ }
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)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_set_for_host(const char * instance, const char *service, const char *proto, const char *hostname,
+ const char *key, const char *value)
+{
+ return mdns_service_txt_item_set_for_host_with_explicit_value_len(instance, service, proto, hostname, key, value,
+ strlen(value));
+}
+
+
+esp_err_t mdns_service_txt_item_set(const char *service, const char *proto, const char *key, const char *value)
+{
+ if (!_mdns_server) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ return mdns_service_txt_item_set_for_host_with_explicit_value_len(NULL, service, proto, _mdns_server->hostname, key,
+ value, strlen(value));
+}
+
+esp_err_t mdns_service_txt_item_set_with_explicit_value_len(const char *service, const char *proto, const char *key,
+ const char *value, uint8_t value_len)
+{
+ if (!_mdns_server) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ return mdns_service_txt_item_set_for_host_with_explicit_value_len(NULL, service, proto, _mdns_server->hostname, key,
+ value, value_len);
+}
+
+esp_err_t mdns_service_txt_item_remove_for_host(const char * instance, const char * service, const char * proto, const char * hostname,
+ 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_instance(instance, service, proto, hostname);
+ if (!s) {
+ return ESP_ERR_NOT_FOUND;
+ }
+ mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
+ if (!action) {
+ HOOK_MALLOC_FAILED;
+ 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, (TickType_t)0) != pdPASS) {
+ free(action->data.srv_txt_del.key);
+ 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) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ return mdns_service_txt_item_remove_for_host(NULL, service, proto, _mdns_server->hostname, key);
+}
+
+esp_err_t mdns_service_subtype_add_for_host(const char *instance_name, const char *service, const char *proto,
+ const char *hostname, const char *subtype)
+{
+ if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) ||
+ _str_null_or_empty(subtype)) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ mdns_srv_item_t * s = _mdns_get_service_item_instance(instance_name, service, proto, hostname);
+ if (!s) {
+ return ESP_ERR_NOT_FOUND;
+ }
+ mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
+ if (!action) {
+ HOOK_MALLOC_FAILED;
+ return ESP_ERR_NO_MEM;
+ }
+
+ action->type = ACTION_SERVICE_SUBTYPE_ADD;
+ action->data.srv_subtype_add.service = s;
+ action->data.srv_subtype_add.subtype = strdup(subtype);
+
+ if (!action->data.srv_subtype_add.subtype) {
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
+ free(action->data.srv_subtype_add.subtype);
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+ return ESP_OK;
+}
+
+esp_err_t mdns_service_instance_name_set_for_host(const char * instance_old, const char * service, const char * proto, const char * hostname,
+ 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_instance(instance_old, service, proto, hostname);
+ 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) {
+ HOOK_MALLOC_FAILED;
+ 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, (TickType_t)0) != pdPASS) {
+ free(new_instance);
+ 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) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ return mdns_service_instance_name_set_for_host(NULL, service, proto, _mdns_server->hostname, instance);
+}
+
+esp_err_t mdns_service_remove_for_host(const char * instance, const char * service, const char * proto, const char * hostname)
+{
+ 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_instance(instance, service, proto, hostname);
+ if (!s) {
+ return ESP_ERR_NOT_FOUND;
+ }
+
+ mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
+ if (!action) {
+ HOOK_MALLOC_FAILED;
+ return ESP_ERR_NO_MEM;
+ }
+ action->type = ACTION_SERVICE_DEL;
+ action->data.srv_del.service = s;
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
+ free(action);
+ return ESP_ERR_NO_MEM;
+ }
+ return ESP_OK;
+}
+
+esp_err_t mdns_service_remove(const char * service_type, const char * proto)
+{
+ if (!_mdns_server) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ return mdns_service_remove_for_host(NULL, service_type, proto, _mdns_server->hostname);
+}
+
+esp_err_t mdns_service_remove_all(void)
+{
+ 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) {
+ HOOK_MALLOC_FAILED;
+ return ESP_ERR_NO_MEM;
+ }
+ action->type = ACTION_SERVICES_CLEAR;
+ if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)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;
+
+ while (results) {
+ r = results;
+
+ free((char *)(r->hostname));
+ free((char *)(r->instance_name));
+ free((char *)(r->service_type));
+ free((char *)(r->proto));
+
+ for (size_t i=0; itxt_count; i++) {
+ free((char *)(r->txt[i].key));
+ free((char *)(r->txt[i].value));
+ }
+ free(r->txt);
+ free(r->txt_value_len);
+
+ while (r->addr) {
+ a = r->addr;
+ r->addr = r->addr->next;
+ free(a);
+ }
+
+ results = results->next;
+ free(r);
+ }
+}
+
+esp_err_t mdns_query_async_delete(mdns_search_once_t* search)
+{
+ if (!search) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ if (search->state != SEARCH_OFF) {
+ return ESP_ERR_INVALID_STATE;
+ }
+
+ MDNS_SERVICE_LOCK();
+ _mdns_search_free(search);
+ MDNS_SERVICE_UNLOCK();
+
+ return ESP_OK;
+}
+
+bool mdns_query_async_get_results(mdns_search_once_t* search, uint32_t timeout, mdns_result_t ** results, uint8_t * num_results)
+{
+ if (xSemaphoreTake(search->done_semaphore, pdMS_TO_TICKS(timeout)) == pdTRUE) {
+ if (results) {
+ *results = search->result;
+ }
+ if (num_results) {
+ *num_results = search->num_results;
+ }
+ return true;
+ }
+ return false;
+}
+
+mdns_search_once_t *mdns_query_async_new(const char *name, const char *service, const char *proto, uint16_t type,
+ uint32_t timeout, size_t max_results, mdns_query_notify_t notifier)
+{
+ mdns_search_once_t *search = NULL;
+
+ if (!_mdns_server || !timeout || _str_null_or_empty(service) != _str_null_or_empty(proto)) {
+ return NULL;
+ }
+
+ search = _mdns_search_init(name, service, proto, type, type != MDNS_TYPE_PTR, timeout, max_results, notifier);
+ if (!search) {
+ return NULL;
+ }
+
+ if (_mdns_send_search_action(ACTION_SEARCH_ADD, search)) {
+ _mdns_search_free(search);
+ return NULL;
+ }
+
+ return search;
+}
+
+esp_err_t mdns_query_generic(const char * name, const char * service, const char * proto, uint16_t type, mdns_query_transmission_type_t transmission_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, transmission_type == MDNS_QUERY_UNICAST, timeout, max_results, NULL);
+ 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->done_semaphore, portMAX_DELAY);
+
+ *results = search->result;
+ _mdns_search_free(search);
+
+ return ESP_OK;
+}
+
+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)
+{
+ return mdns_query_generic(name, service_type, proto, type, type != MDNS_TYPE_PTR, timeout, max_results, results);
+}
+
+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, esp_ip4_addr_t * addr)
+{
+ mdns_result_t * result = NULL;
+ esp_err_t err;
+
+ if (_str_null_or_empty(name)) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ if (strstr(name, ".local")) {
+ ESP_LOGW(TAG, "Please note that hostname must not contain domain name, as mDNS uses '.local' domain");
+ }
+
+ 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 == ESP_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;
+}
+
+#if CONFIG_LWIP_IPV6
+esp_err_t mdns_query_aaaa(const char * name, uint32_t timeout, esp_ip6_addr_t * addr)
+{
+ mdns_result_t * result = NULL;
+ esp_err_t err;
+
+ if (_str_null_or_empty(name)) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ if (strstr(name, ".local")) {
+ ESP_LOGW(TAG, "Please note that hostname must not contain domain name, as mDNS uses '.local' domain");
+ }
+
+ 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 == ESP_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;
+}
+#endif
+
+#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, len);
+ if (!content || content + MDNS_CLASS_OFFSET + 1 >= data + len) {
+ header.answers = 0;
+ header.additional = 0;
+ header.servers = 0;
+ _mdns_dbg_printf("ERROR: parse header questions\n");
+ break;
+ }
+
+ uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET);
+ uint16_t mdns_class = _mdns_read_u16(content, MDNS_CLASS_OFFSET);
+ bool unicast = !!(mdns_class & 0x8000);
+ mdns_class &= 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 (mdns_class == 0x0001) {
+ _mdns_dbg_printf("IN");
+ } else {
+ _mdns_dbg_printf("%04X", mdns_class);
+ }
+ _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, len);
+ if (!content) {
+ _mdns_dbg_printf("ERROR: parse mdns records\n");
+ break;
+ }
+
+ uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET);
+ uint16_t mdns_class = _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 = !!(mdns_class & 0x8000);
+ mdns_class &= 0x7FFF;
+
+ content = data_ptr + data_len;
+ if (content > (data + len)) {
+ _mdns_dbg_printf("ERROR: content length overflow\n");
+ break;
+ }
+
+ mdns_parsed_record_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 (mdns_class == 0x0001) {
+ _mdns_dbg_printf("IN ");
+ } else {
+ _mdns_dbg_printf("%04X ", mdns_class);
+ }
+ 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, len)) {
+ _mdns_dbg_printf("ERROR: parse PTR\n");
+ 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, len)) {
+ _mdns_dbg_printf("ERROR: parse SRV\n");
+ 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: parse TXT\n");
+ 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 * 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;
+ while (r) {
+ printf("%d: Interface: %s, Type: %s\n", i++, esp_netif_get_ifkey(r->esp_netif), 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 (size_t 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 == ESP_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 esp_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(void)
+{
+ 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) );
+}
+
+#if CONFIG_LWIP_IPV6
+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 esp_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(void)
+{
+ 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) );
+}
+#endif
+
+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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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(void)
+{
+ 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();
+#if CONFIG_LWIP_IPV6
+ register_mdns_query_aaaa();
+#endif
+ 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_lwip.c b/components/mdns/mdns_networking_lwip.c
new file mode 100644
index 000000000..370661d12
--- /dev/null
+++ b/components/mdns/mdns_networking_lwip.c
@@ -0,0 +1,370 @@
+
+/*
+ * MDNS Server Networking
+ *
+ */
+#include
+#include "esp_log.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_system.h"
+#include "esp_event.h"
+#include "mdns_networking.h"
+#include "esp_netif_net_stack.h"
+
+extern mdns_server_t * _mdns_server;
+
+/*
+ * MDNS Server Networking
+ *
+ */
+static const char *TAG = "MDNS_Networking";
+
+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(void)
+{
+ 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 = 255;
+ _pcb_main->remote_port = MDNS_SERVICE_PORT;
+ ip_addr_copy(_pcb_main->remote_ip, *(IP_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(void)
+{
+ 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(mdns_if_t if_inx, mdns_ip_protocol_t ip_protocol, bool join)
+{
+ struct netif * netif = NULL;
+ esp_netif_t *tcpip_if = _mdns_get_esp_netif(if_inx);
+
+ if (!esp_netif_is_netif_up(tcpip_if)) {
+ // Network interface went down before event propagated, skipping IGMP config
+ return ESP_ERR_INVALID_STATE;
+ }
+
+ netif = esp_netif_get_netif_impl(tcpip_if);
+ assert(netif);
+
+ if (ip_protocol == MDNS_IP_PROTOCOL_V4) {
+ ip4_addr_t multicast_addr;
+ IP4_ADDR(&multicast_addr, 224, 0, 0, 251);
+
+ if(join){
+ if (igmp_joingroup_netif(netif, &multicast_addr)) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ } else {
+ if (igmp_leavegroup_netif(netif, &multicast_addr)) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ }
+ }
+#if CONFIG_LWIP_IPV6
+ 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;
+ }
+ }
+ }
+#endif
+ return ESP_OK;
+}
+
+/**
+ * @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) {
+ HOOK_MALLOC_FAILED;
+ //missed packet - no memory
+ pbuf_free(this_pb);
+ continue;
+ }
+
+ packet->tcpip_if = MDNS_MAX_INTERFACES;
+ packet->pb = this_pb;
+ packet->src_port = rport;
+#if CONFIG_LWIP_IPV6
+ packet->src.type = raddr->type;
+ memcpy(&packet->src.u_addr, &raddr->u_addr, sizeof(raddr->u_addr));
+#else
+ packet->src.type = IPADDR_TYPE_V4;
+ memcpy(&packet->src.u_addr.ip4, &raddr->addr, sizeof(ip_addr_t));
+#endif
+ 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;
+ packet->multicast = ip4_addr_ismulticast(&(packet->dest.u_addr.ip4));
+ }
+#if CONFIG_LWIP_IPV6
+ 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 = ip6_addr_ismulticast(&(packet->dest.u_addr.ip6));
+ }
+#endif
+
+ //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;
+ struct udp_pcb * pcb = NULL;
+ for (i=0; iinterfaces[i].pcbs[packet->ip_protocol].pcb;
+ netif = esp_netif_get_netif_impl(_mdns_get_esp_netif(i));
+ if (pcb && netif && netif == ip_current_input_netif ()) {
+ if (packet->src.type == IPADDR_TYPE_V4) {
+#if CONFIG_LWIP_IPV6
+ 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)) {
+#else
+ if ((packet->src.u_addr.ip4.addr & netif->netmask.addr) != (netif->ip_addr.addr & netif->netmask.addr)) {
+#endif //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 Check if any of the interfaces is up
+ */
+static bool _udp_pcb_is_in_use(void){
+ int i, p;
+ for (i=0; iinterfaces[i].pcbs[p].pcb){
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * @brief Stop PCB Main code
+ */
+static void _udp_pcb_deinit(mdns_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) {
+ 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 Main code
+ */
+static esp_err_t _udp_pcb_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
+{
+ if (!_mdns_server || _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb) {
+ return ESP_ERR_INVALID_STATE;
+ }
+
+ 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_data call;
+ mdns_if_t tcpip_if;
+ mdns_ip_protocol_t ip_protocol;
+ struct pbuf *pbt;
+ const ip_addr_t *ip;
+ uint16_t port;
+ 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_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);
+ return msg->err;
+}
+
+/**
+ * @brief Stop PCB from LwIP thread
+ */
+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);
+ 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(mdns_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, &msg.call);
+ return msg.err;
+}
+
+esp_err_t _mdns_pcb_deinit(mdns_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, &msg.call);
+ return msg.err;
+}
+
+static err_t _mdns_udp_pcb_write_api(struct tcpip_api_call_data *api_call_msg)
+{
+ void * nif = NULL;
+ mdns_api_call_t * msg = (mdns_api_call_t *)api_call_msg;
+ mdns_pcb_t * _pcb = &_mdns_server->interfaces[msg->tcpip_if].pcbs[msg->ip_protocol];
+ nif = esp_netif_get_netif_impl(_mdns_get_esp_netif(msg->tcpip_if));
+ if (!nif) {
+ pbuf_free(msg->pbt);
+ msg->err = ERR_IF;
+ return ERR_IF;
+ }
+ esp_err_t err = udp_sendto_if (_pcb->pcb, msg->pbt, msg->ip, msg->port, (struct netif *)nif);
+ pbuf_free(msg->pbt);
+ msg->err = err;
+ return err;
+}
+
+size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const esp_ip_addr_t *ip, uint16_t port, uint8_t * data, size_t len)
+{
+ struct pbuf* pbt = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
+ if (pbt == NULL) {
+ return 0;
+ }
+ memcpy((uint8_t *)pbt->payload, data, len);
+
+ mdns_api_call_t msg = {
+ .tcpip_if = tcpip_if,
+ .ip_protocol = ip_protocol,
+ .pbt = pbt,
+ .ip = (ip_addr_t *)ip,
+ .port = port
+ };
+ tcpip_api_call(_mdns_udp_pcb_write_api, &msg.call);
+
+ if (msg.err) {
+ return 0;
+ }
+ return len;
+}
+
+void* _mdns_get_packet_data(mdns_rx_packet_t *packet)
+{
+ return packet->pb->payload;
+}
+
+size_t _mdns_get_packet_len(mdns_rx_packet_t *packet)
+{
+ return packet->pb->len;
+}
+
+void _mdns_packet_free(mdns_rx_packet_t *packet)
+{
+ pbuf_free(packet->pb);
+ free(packet);
+}
diff --git a/components/mdns/mdns_networking_socket.c b/components/mdns/mdns_networking_socket.c
new file mode 100644
index 000000000..0f393cbc0
--- /dev/null
+++ b/components/mdns/mdns_networking_socket.c
@@ -0,0 +1,495 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @brief MDNS Server Networking module implemented using BSD sockets
+ */
+
+#include
+#include "esp_event.h"
+#include "mdns_networking.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "esp_log.h"
+
+#if defined(CONFIG_IDF_TARGET_LINUX)
+#include
+#include
+#endif
+
+extern mdns_server_t * _mdns_server;
+
+static const char *TAG = "MDNS_Networking";
+static bool s_run_sock_recv_task = false;
+static int create_socket(esp_netif_t *netif);
+static int join_mdns_multicast_group(int sock, esp_netif_t *netif, mdns_ip_protocol_t ip_protocol);
+
+#if defined(CONFIG_IDF_TARGET_LINUX)
+// Need to define packet buffer struct on linux
+struct pbuf {
+ struct pbuf * next;
+ void * payload;
+ size_t tot_len;
+ size_t len;
+};
+#else
+// Compatibility define to access sock-addr struct the same way for lwip and linux
+#define s6_addr32 un.u32_addr
+#endif // CONFIG_IDF_TARGET_LINUX
+
+static void delete_socket(int sock)
+{
+ close(sock);
+}
+
+static struct udp_pcb* sock_to_pcb(int sock)
+{
+ if (sock < 0) {
+ return NULL;
+ }
+ // Note: sock=0 is a valid descriptor, so save it as +1 ("1" is a valid pointer)
+ intptr_t sock_plus_one = sock + 1;
+ return (struct udp_pcb*)sock_plus_one;
+}
+
+static int pcb_to_sock(struct udp_pcb* pcb)
+{
+ if (pcb == NULL) {
+ return -1;
+ }
+ intptr_t sock_plus_one = (intptr_t)pcb;
+ return sock_plus_one - 1;
+}
+
+void* _mdns_get_packet_data(mdns_rx_packet_t *packet)
+{
+ return packet->pb->payload;
+}
+
+size_t _mdns_get_packet_len(mdns_rx_packet_t *packet)
+{
+ return packet->pb->len;
+}
+
+void _mdns_packet_free(mdns_rx_packet_t *packet)
+{
+ free(packet->pb->payload);
+ free(packet->pb);
+ free(packet);
+}
+
+esp_err_t _mdns_pcb_deinit(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
+{
+ struct udp_pcb * pcb = _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb;
+ _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb = NULL;
+ if (_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb == NULL &&
+ _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb == NULL) {
+ // if the interface for both protocol uninitialized, close the interface socket
+ int sock = pcb_to_sock(pcb);
+ if (sock >= 0) {
+ delete_socket(sock);
+ }
+ }
+
+ for (int i=0; iinterfaces[i].pcbs[j].pcb)
+ // If any of the interfaces/protocol initialized
+ return ESP_OK;
+ }
+ }
+
+ // no interface alive, stop the rx task
+ s_run_sock_recv_task = false;
+ vTaskDelay(pdMS_TO_TICKS(500));
+ return ESP_OK;
+}
+
+#if defined(CONFIG_IDF_TARGET_LINUX)
+#ifdef CONFIG_LWIP_IPV6
+static char* inet6_ntoa_r(struct in6_addr addr, char* ptr, size_t size)
+{
+ inet_ntop(AF_INET6, &(addr.s6_addr32[0]), ptr, size);
+ return ptr;
+}
+#endif // CONFIG_LWIP_IPV6
+static char* inet_ntoa_r(struct in_addr addr, char* ptr, size_t size)
+{
+ char * res = inet_ntoa(addr);
+ if (res && strlen(res) < size) {
+ strcpy(ptr, res);
+ }
+ return res;
+}
+#endif // CONFIG_IDF_TARGET_LINUX
+
+static inline char* get_string_address(struct sockaddr_storage *source_addr)
+{
+ static char address_str[40]; // 40=(8*4+7+term) is the max size of ascii IPv6 addr "XXXX:XX...XX:XXXX"
+ char *res = NULL;
+ // Convert ip address to string
+ if (source_addr->ss_family == PF_INET) {
+ res = inet_ntoa_r(((struct sockaddr_in *)source_addr)->sin_addr, address_str, sizeof(address_str));
+ }
+#ifdef CONFIG_LWIP_IPV6
+ else if (source_addr->ss_family == PF_INET6) {
+ res = inet6_ntoa_r(((struct sockaddr_in6 *)source_addr)->sin6_addr, address_str, sizeof(address_str));
+ }
+#endif
+ if (!res) {
+ address_str[0] = '\0'; // Returns empty string if conversion didn't succeed
+ }
+ return address_str;
+}
+
+
+static inline size_t espaddr_to_inet(const esp_ip_addr_t *addr, const uint16_t port, const mdns_ip_protocol_t ip_protocol, struct sockaddr_storage *in_addr)
+{
+ size_t ss_addr_len = 0;
+ memset(in_addr, 0, sizeof(struct sockaddr_storage));
+ if (ip_protocol == MDNS_IP_PROTOCOL_V4 && addr->type == ESP_IPADDR_TYPE_V4) {
+ in_addr->ss_family = PF_INET;
+#if !defined(CONFIG_IDF_TARGET_LINUX)
+ in_addr->s2_len = sizeof(struct sockaddr_in);
+#endif
+ ss_addr_len = sizeof(struct sockaddr_in);
+ struct sockaddr_in *in_addr_ip4 = (struct sockaddr_in *) in_addr;
+ in_addr_ip4->sin_port = port;
+ in_addr_ip4->sin_addr.s_addr = addr->u_addr.ip4.addr;
+ }
+#if CONFIG_LWIP_IPV6
+ else if (ip_protocol == MDNS_IP_PROTOCOL_V6 && addr->type == ESP_IPADDR_TYPE_V6) {
+ memset(in_addr, 0, sizeof(struct sockaddr_storage));
+ in_addr->ss_family = PF_INET6;
+#if !defined(CONFIG_IDF_TARGET_LINUX)
+ in_addr->s2_len = sizeof(struct sockaddr_in6);
+#endif
+ ss_addr_len = sizeof(struct sockaddr_in6);
+ struct sockaddr_in6 * in_addr_ip6 = (struct sockaddr_in6 *)in_addr;
+ uint32_t *u32_addr = in_addr_ip6->sin6_addr.s6_addr32;
+ in_addr_ip6->sin6_port = port;
+ u32_addr[0] = addr->u_addr.ip6.addr[0];
+ u32_addr[1] = addr->u_addr.ip6.addr[1];
+ u32_addr[2] = addr->u_addr.ip6.addr[2];
+ u32_addr[3] = addr->u_addr.ip6.addr[3];
+ }
+#endif // CONFIG_LWIP_IPV6
+ return ss_addr_len;
+}
+
+size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const esp_ip_addr_t *ip, uint16_t port, uint8_t * data, size_t len)
+{
+ int sock = pcb_to_sock(_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb);
+ if (sock < 0) {
+ return 0;
+ }
+ struct sockaddr_storage in_addr;
+ size_t ss_size = espaddr_to_inet(ip, htons(port), ip_protocol, &in_addr);
+ if (!ss_size) {
+ ESP_LOGE(TAG, "espaddr_to_inet() failed: Mismatch of IP protocols");
+ return 0;
+ }
+ ESP_LOGD(TAG, "[sock=%d]: Sending to IP %s port %d", sock, get_string_address(&in_addr), port);
+ ssize_t actual_len = sendto(sock, data, len, 0, (struct sockaddr *)&in_addr, ss_size);
+ if (actual_len < 0) {
+ ESP_LOGE(TAG, "[sock=%d]: _mdns_udp_pcb_write sendto() has failed\n errno=%d: %s", sock, errno, strerror(errno));
+ }
+ return actual_len;
+}
+
+static inline void inet_to_espaddr(const struct sockaddr_storage *in_addr, esp_ip_addr_t *addr, uint16_t *port)
+{
+ if (in_addr->ss_family == PF_INET) {
+ struct sockaddr_in * in_addr_ip4 = (struct sockaddr_in *)in_addr;
+ memset(addr, 0, sizeof(esp_ip_addr_t));
+ *port = in_addr_ip4->sin_port;
+ addr->u_addr.ip4.addr = in_addr_ip4->sin_addr.s_addr;
+ addr->type = ESP_IPADDR_TYPE_V4;
+ }
+#if CONFIG_LWIP_IPV6
+ else if (in_addr->ss_family == PF_INET6) {
+ struct sockaddr_in6 * in_addr_ip6 = (struct sockaddr_in6 *)in_addr;
+ memset(addr, 0, sizeof(esp_ip_addr_t));
+ *port = in_addr_ip6->sin6_port;
+ uint32_t *u32_addr = in_addr_ip6->sin6_addr.s6_addr32;
+ if (u32_addr[0] == 0 && u32_addr[1] == 0 && u32_addr[2] == esp_netif_htonl(0x0000FFFFUL)) {
+ // Mapped IPv4 address, convert directly to IPv4
+ addr->type = ESP_IPADDR_TYPE_V4;
+ addr->u_addr.ip4.addr = u32_addr[3];
+ } else {
+ addr->type = ESP_IPADDR_TYPE_V6;
+ addr->u_addr.ip6.addr[0] = u32_addr[0];
+ addr->u_addr.ip6.addr[1] = u32_addr[1];
+ addr->u_addr.ip6.addr[2] = u32_addr[2];
+ addr->u_addr.ip6.addr[3] = u32_addr[3];
+ }
+ }
+#endif // CONFIG_LWIP_IPV6
+}
+
+void sock_recv_task(void* arg)
+{
+ while (s_run_sock_recv_task) {
+ struct timeval tv = {
+ .tv_sec = 1,
+ .tv_usec = 0,
+ };
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ int max_sock = -1;
+ for (int i=0; iinterfaces[i].pcbs[j].pcb);
+ if (sock >= 0) {
+ FD_SET(sock, &rfds);
+ max_sock = MAX(max_sock, sock);
+ }
+ }
+ }
+ if (max_sock < 0) {
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ ESP_LOGI(TAG, "No sock!");
+ continue;
+ }
+
+ int s = select(max_sock + 1, &rfds, NULL, NULL, &tv);
+ if (s < 0) {
+ ESP_LOGE(TAG, "Select failed. errno=%d: %s", errno, strerror(errno));
+ break;
+ } else if (s > 0) {
+ for (int tcpip_if=0; tcpip_ifinterfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb);
+ if (sock < 0) {
+ sock = pcb_to_sock(_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb);
+ }
+ if (sock < 0) {
+ continue;
+ }
+ if (FD_ISSET(sock, &rfds)) {
+ static char recvbuf[MDNS_MAX_PACKET_SIZE];
+ uint16_t port = 0;
+
+ struct sockaddr_storage raddr; // Large enough for both IPv4 or IPv6
+ socklen_t socklen = sizeof(struct sockaddr_storage);
+ esp_ip_addr_t addr = {0};
+ int len = recvfrom(sock, recvbuf, sizeof(recvbuf), 0,
+ (struct sockaddr *) &raddr, &socklen);
+ if (len < 0) {
+ ESP_LOGE(TAG, "multicast recvfrom failed. errno=%d: %s", errno, strerror(errno));
+ break;
+ }
+ ESP_LOGD(TAG, "[sock=%d]: Received from IP:%s", sock, get_string_address(&raddr));
+ ESP_LOG_BUFFER_HEXDUMP(TAG, recvbuf, len, ESP_LOG_VERBOSE);
+ inet_to_espaddr(&raddr, &addr, &port);
+
+ // Allocate the packet structure and pass it to the mdns main engine
+ mdns_rx_packet_t *packet = (mdns_rx_packet_t *) calloc(1, sizeof(mdns_rx_packet_t));
+ struct pbuf *packet_pbuf = calloc(1, sizeof(struct pbuf));
+ uint8_t *buf = malloc(len);
+ if (packet == NULL || packet_pbuf == NULL || buf == NULL ) {
+ free(buf);
+ free(packet_pbuf);
+ free(packet);
+ HOOK_MALLOC_FAILED;
+ ESP_LOGE(TAG, "Failed to allocate the mdns packet");
+ continue;
+ }
+ memcpy(buf, recvbuf, len);
+ packet_pbuf->next = NULL;
+ packet_pbuf->payload = buf;
+ packet_pbuf->tot_len = len;
+ packet_pbuf->len = len;
+ packet->tcpip_if = tcpip_if;
+ packet->pb = packet_pbuf;
+ packet->src_port = ntohs(port);
+ memcpy(&packet->src, &addr, sizeof(esp_ip_addr_t));
+ // TODO(IDF-3651): Add the correct dest addr -- for mdns to decide multicast/unicast
+ // Currently it's enough to assume the packet is multicast and mdns to check the source port of the packet
+ memset(&packet->dest, 0, sizeof(esp_ip_addr_t));
+ packet->multicast = 1;
+ packet->dest.type = packet->src.type;
+ packet->ip_protocol =
+ packet->src.type == ESP_IPADDR_TYPE_V4 ? MDNS_IP_PROTOCOL_V4 : MDNS_IP_PROTOCOL_V6;
+ if (!_mdns_server || !_mdns_server->action_queue || _mdns_send_rx_action(packet) != ESP_OK) {
+ ESP_LOGE(TAG, "_mdns_send_rx_action failed!");
+ free(packet->pb->payload);
+ free(packet->pb);
+ free(packet);
+ }
+ }
+ }
+ }
+ }
+ vTaskDelete(NULL);
+}
+
+static void mdns_networking_init(void)
+{
+ if (s_run_sock_recv_task == false) {
+ s_run_sock_recv_task = true;
+ xTaskCreate( sock_recv_task, "mdns recv task", 3*1024, NULL, 5, NULL );
+ }
+}
+
+static struct udp_pcb* create_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
+{
+ if (_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb) {
+ return _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb;
+ }
+ mdns_ip_protocol_t other_ip_proto = ip_protocol==MDNS_IP_PROTOCOL_V4?MDNS_IP_PROTOCOL_V6:MDNS_IP_PROTOCOL_V4;
+ esp_netif_t *netif = _mdns_get_esp_netif(tcpip_if);
+ if (_mdns_server->interfaces[tcpip_if].pcbs[other_ip_proto].pcb) {
+ struct udp_pcb* other_pcb = _mdns_server->interfaces[tcpip_if].pcbs[other_ip_proto].pcb;
+ int err = join_mdns_multicast_group(pcb_to_sock(other_pcb), netif, ip_protocol);
+ if (err < 0) {
+ ESP_LOGE(TAG, "Failed to add ipv6 multicast group for protocol %d", ip_protocol);
+ return NULL;
+ }
+ return other_pcb;
+ }
+ int sock = create_socket(netif);
+ if (sock < 0) {
+ ESP_LOGE(TAG, "Failed to create the socket!");
+ return NULL;
+ }
+ int err = join_mdns_multicast_group(sock, netif, ip_protocol);
+ if (err < 0) {
+ ESP_LOGE(TAG, "Failed to add ipv6 multicast group for protocol %d", ip_protocol);
+ }
+ return sock_to_pcb(sock);
+}
+
+esp_err_t _mdns_pcb_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
+{
+ ESP_LOGI(TAG, "_mdns_pcb_init(tcpip_if=%d, ip_protocol=%d)", tcpip_if, ip_protocol);
+ _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb = create_pcb(tcpip_if, ip_protocol);
+ _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].failed_probes = 0;
+
+ mdns_networking_init();
+ return ESP_OK;
+}
+
+static int create_socket(esp_netif_t *netif)
+{
+#if CONFIG_LWIP_IPV6
+ int sock = socket(PF_INET6, SOCK_DGRAM, 0);
+#else
+ int sock = socket(PF_INET, SOCK_DGRAM, 0);
+#endif
+ if (sock < 0) {
+ ESP_LOGE(TAG, "Failed to create socket. errno=%d: %s", errno, strerror(errno));
+ return -1;
+ }
+
+ int on = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ) < 0) {
+ ESP_LOGE(TAG, "Failed setsockopt() to set SO_REUSEADDR. errno=%d: %s\n", errno, strerror(errno));
+ }
+ // Bind the socket to any address
+#if CONFIG_LWIP_IPV6
+ struct sockaddr_in6 saddr = { INADDR_ANY };
+ saddr.sin6_family = AF_INET6;
+ saddr.sin6_port = htons(5353);
+ bzero(&saddr.sin6_addr.s6_addr, sizeof(saddr.sin6_addr.s6_addr));
+ int err = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in6));
+ if (err < 0) {
+ ESP_LOGE(TAG, "Failed to bind socket. errno=%d: %s", errno, strerror(errno));
+ goto err;
+ }
+#else
+ struct sockaddr_in saddr = { 0 };
+ saddr.sin_family = AF_INET;
+ saddr.sin_port = htons(5353);
+ bzero(&saddr.sin_addr.s_addr, sizeof(saddr.sin_addr.s_addr));
+ int err = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
+ if (err < 0) {
+ ESP_LOGE(TAG, "Failed to bind socket. errno=%d: %s", errno, strerror(errno));
+ goto err;
+ }
+#endif // CONFIG_LWIP_IPV6
+ struct ifreq ifr;
+ esp_netif_get_netif_impl_name(netif, ifr.ifr_name);
+ int ret = setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(struct ifreq));
+ if (ret < 0) {
+ ESP_LOGE(TAG, "\"%s\" Unable to bind socket to specified interface. errno=%d: %s", esp_netif_get_desc(netif), errno, strerror(errno));
+ goto err;
+ }
+
+ return sock;
+
+err:
+ close(sock);
+ return -1;
+}
+
+#if CONFIG_LWIP_IPV6
+static int socket_add_ipv6_multicast_group(int sock, esp_netif_t *netif)
+{
+ int ifindex = esp_netif_get_netif_impl_index(netif);
+ int err = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex));
+ if (err < 0) {
+ ESP_LOGE(TAG, "Failed to set IPV6_MULTICAST_IF. errno=%d: %s", errno, strerror(errno));
+ return err;
+ }
+
+ struct ipv6_mreq v6imreq = { 0 };
+ esp_ip_addr_t multi_addr = ESP_IP6ADDR_INIT(0x000002ff, 0, 0, 0xfb000000);
+ memcpy(&v6imreq.ipv6mr_multiaddr, &multi_addr.u_addr.ip6.addr, sizeof(v6imreq.ipv6mr_multiaddr));
+ v6imreq.ipv6mr_interface = ifindex;
+ err = setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &v6imreq, sizeof(struct ipv6_mreq));
+ if (err < 0) {
+ ESP_LOGE(TAG, "Failed to set IPV6_ADD_MEMBERSHIP. errno=%d: %s", errno, strerror(errno));
+ return err;
+ }
+ return err;
+}
+#endif // CONFIG_LWIP_IPV6
+
+static int socket_add_ipv4_multicast_group(int sock, esp_netif_t *netif)
+{
+ struct ip_mreq imreq = { 0 };
+ int err = 0;
+ esp_netif_ip_info_t ip_info = { 0 };
+
+ if (esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to esp_netif_get_ip_info()");
+ goto err;
+ }
+ imreq.imr_interface.s_addr = ip_info.ip.addr;
+
+ esp_ip_addr_t multicast_addr = ESP_IP4ADDR_INIT(224, 0, 0, 251);
+ imreq.imr_multiaddr.s_addr = multicast_addr.u_addr.ip4.addr;
+
+ err = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(struct ip_mreq));
+ if (err < 0) {
+ ESP_LOGE(TAG, "[sock=%d] Failed to set IP_ADD_MEMBERSHIP. errno=%d: %s", sock, errno, strerror(errno));
+ goto err;
+ }
+
+ err:
+ return err;
+}
+
+static int join_mdns_multicast_group(int sock, esp_netif_t *netif, mdns_ip_protocol_t ip_protocol)
+{
+ if (ip_protocol == MDNS_IP_PROTOCOL_V4) {
+ return socket_add_ipv4_multicast_group(sock, netif);
+ }
+#if CONFIG_LWIP_IPV6
+ if (ip_protocol == MDNS_IP_PROTOCOL_V6) {
+ return socket_add_ipv6_multicast_group(sock, netif);
+ }
+#endif // CONFIG_LWIP_IPV6
+ return -1;
+}
diff --git a/components/mdns/private_include/mdns_networking.h b/components/mdns/private_include/mdns_networking.h
new file mode 100644
index 000000000..53772c835
--- /dev/null
+++ b/components/mdns/private_include/mdns_networking.h
@@ -0,0 +1,53 @@
+#ifndef ESP_MDNS_NETWORKING_H_
+#define ESP_MDNS_NETWORKING_H_
+
+/*
+ * MDNS Server Networking -- private include
+ *
+ */
+#include "mdns.h"
+#include "mdns_private.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(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol);
+
+/**
+ * @brief Stop PCB
+ */
+esp_err_t _mdns_pcb_deinit(mdns_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(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const esp_ip_addr_t *ip, uint16_t port, uint8_t * data, size_t len);
+
+/**
+ * @brief Gets data pointer to the mDNS packet
+ */
+void* _mdns_get_packet_data(mdns_rx_packet_t *packet);
+
+/**
+ * @brief Gets data length of c
+ */
+size_t _mdns_get_packet_len(mdns_rx_packet_t *packet);
+
+/**
+ * @brief Free the mDNS packet
+ */
+void _mdns_packet_free(mdns_rx_packet_t *packet);
+
+#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 000000000..bf1d6d8bf
--- /dev/null
+++ b/components/mdns/private_include/mdns_private.h
@@ -0,0 +1,493 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef MDNS_PRIVATE_H_
+#define MDNS_PRIVATE_H_
+
+#include "sdkconfig.h"
+#include "mdns.h"
+#include "esp_task.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "freertos/semphr.h"
+#include "esp_timer.h"
+
+//#define MDNS_ENABLE_DEBUG
+
+#ifdef MDNS_ENABLE_DEBUG
+#define _mdns_dbg_printf(...) printf(__VA_ARGS__)
+#endif
+
+/** mDNS strict mode: Set this to 1 for the mDNS library to strictly follow the RFC6762:
+ * Strict features:
+ * - to do not set original questions in response packets per RFC6762, sec 6
+ *
+ * The actual configuration is 0, i.e. non-strict mode, since some implementations,
+ * such as lwIP mdns resolver (used by standard POSIX API like getaddrinfo, gethostbyname)
+ * could not correctly resolve advertised names.
+ */
+#ifndef CONFIG_MDNS_STRICT_MODE
+#define MDNS_STRICT_MODE 0
+#else
+#define MDNS_STRICT_MODE 1
+#endif
+
+#if !MDNS_STRICT_MODE
+/* mDNS responders sometimes repeat queries in responses
+ * but according to RFC6762, sec 6: Responses MUST NOT contain
+ * any item in question field */
+#define MDNS_REPEAT_QUERY_IN_RESPONSE 1
+#endif
+
+/** Number of predefined interfaces */
+#ifndef CONFIG_MDNS_PREDEF_NETIF_STA
+#define CONFIG_MDNS_PREDEF_NETIF_STA 0
+#endif
+#ifndef CONFIG_MDNS_PREDEF_NETIF_AP
+#define CONFIG_MDNS_PREDEF_NETIF_AP 0
+#endif
+#ifndef CONFIG_MDNS_PREDEF_NETIF_ETH
+#define CONFIG_MDNS_PREDEF_NETIF_ETH 0
+#endif
+#define MDNS_MAX_PREDEF_INTERFACES (CONFIG_MDNS_PREDEF_NETIF_STA + CONFIG_MDNS_PREDEF_NETIF_AP + CONFIG_MDNS_PREDEF_NETIF_ETH)
+
+/** Number of configured interfaces */
+#if MDNS_MAX_PREDEF_INTERFACES > CONFIG_MDNS_MAX_INTERFACES
+#warning Number of configured interfaces is less then number of predefined interfaces. Please update CONFIG_MDNS_MAX_INTERFACES.
+#define MDNS_MAX_INTERFACES (MDNS_MAX_PREDEF_INTERFACES)
+#else
+#define MDNS_MAX_INTERFACES (CONFIG_MDNS_MAX_INTERFACES)
+#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
+#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_ANSWER_AAAA_SIZE 16
+
+#define MDNS_SERVICE_PORT 5353 // UDP port that the server runs on
+#define MDNS_SERVICE_STACK_DEPTH CONFIG_MDNS_TASK_STACK_SIZE
+#define MDNS_TASK_PRIORITY CONFIG_MDNS_TASK_PRIORITY
+#if (MDNS_TASK_PRIORITY > ESP_TASK_PRIO_MAX)
+#error "mDNS task priority is higher than ESP_TASK_PRIO_MAX"
+#elif (MDNS_TASK_PRIORITY > ESP_TASKD_EVENT_PRIO)
+#warning "mDNS task priority is higher than ESP_TASKD_EVENT_PRIO, mDNS library might not work correctly"
+#endif
+#define MDNS_TASK_AFFINITY CONFIG_MDNS_TASK_AFFINITY
+#define MDNS_SERVICE_ADD_TIMEOUT_MS CONFIG_MDNS_SERVICE_ADD_TIMEOUT_MS
+
+#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 (CONFIG_MDNS_TIMER_PERIOD_MS*1000)
+
+#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)
+
+#ifndef HOOK_MALLOC_FAILED
+#define HOOK_MALLOC_FAILED ESP_LOGE(TAG, "Cannot allocate memory (line: %d, free heap: %d bytes)", __LINE__, esp_get_free_heap_size());
+#endif
+
+typedef size_t mdns_if_t;
+
+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_record_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_SERVICE_SUBTYPE_ADD,
+ ACTION_SERVICES_CLEAR,
+ ACTION_SEARCH_ADD,
+ ACTION_SEARCH_SEND,
+ ACTION_SEARCH_END,
+ ACTION_TX_HANDLE,
+ ACTION_RX_HANDLE,
+ ACTION_TASK_STOP,
+ ACTION_DELEGATE_HOSTNAME_ADD,
+ ACTION_DELEGATE_HOSTNAME_REMOVE,
+ 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]; // hostname for A/AAAA records, instance name for SRV records
+ char service[MDNS_NAME_BUF_LEN];
+ char proto[MDNS_NAME_BUF_LEN];
+ char domain[MDNS_NAME_BUF_LEN];
+ uint8_t parts;
+ uint8_t sub;
+ bool invalid;
+} mdns_name_t;
+
+typedef struct mdns_parsed_question_s {
+ struct mdns_parsed_question_s * next;
+ uint16_t type;
+ bool sub;
+ 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_record_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 {
+ mdns_if_t tcpip_if;
+ mdns_ip_protocol_t ip_protocol;
+ esp_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;
+ uint16_t id;
+} mdns_parsed_packet_t;
+
+typedef struct {
+ mdns_if_t tcpip_if;
+ mdns_ip_protocol_t ip_protocol;
+ struct pbuf *pb;
+ esp_ip_addr_t src;
+ esp_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 */
+ char * value; /*!< item value string */
+ uint8_t value_len; /*!< item value length */
+ 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 mdns_subtype_s {
+ const char *subtype; /*!< subtype */
+ struct mdns_subtype_s * next; /*!< next result, or NULL for the last result in the list */
+} mdns_subtype_t;
+
+typedef struct {
+ const char * instance;
+ const char * service;
+ const char * proto;
+ const char * hostname;
+ uint16_t priority;
+ uint16_t weight;
+ uint16_t port;
+ mdns_txt_linked_item_t * txt;
+ mdns_subtype_t *subtype;
+} 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;
+ bool unicast;
+ const char * host;
+ const char * service;
+ const char * proto;
+ const char * domain;
+ bool own_dynamic_memory;
+} mdns_out_question_t;
+
+typedef struct mdns_host_item_t {
+ const char * hostname;
+ mdns_ip_addr_t *address_list;
+ struct mdns_host_item_t *next;
+} mdns_host_item_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;
+ mdns_host_item_t* host;
+ 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;
+ mdns_if_t tcpip_if;
+ mdns_ip_protocol_t ip_protocol;
+ esp_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;
+ bool queued;
+ uint16_t id;
+} 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;
+ mdns_query_notify_t notifier;
+ SemaphoreHandle_t done_semaphore;
+ uint16_t type;
+ bool unicast;
+ 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[MDNS_MAX_INTERFACES];
+ 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 {
+ struct {
+ char * hostname;
+ TaskHandle_t calling_task;
+ } hostname_set;
+ char * instance;
+ struct {
+ mdns_if_t interface;
+ mdns_event_actions_t event_action;
+ } 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;
+ mdns_txt_linked_item_t * txt;
+ } srv_txt_replace;
+ struct {
+ mdns_srv_item_t * service;
+ char * key;
+ char * value;
+ uint8_t value_len;
+ } srv_txt_set;
+ struct {
+ mdns_srv_item_t * service;
+ char * key;
+ } srv_txt_del;
+ struct {
+ mdns_srv_item_t * service;
+ char * subtype;
+ } srv_subtype_add;
+ struct {
+ mdns_search_once_t * search;
+ } search_add;
+ struct {
+ mdns_tx_packet_t * packet;
+ } tx_handle;
+ struct {
+ mdns_rx_packet_t * packet;
+ } rx_handle;
+ struct {
+ const char * hostname;
+ mdns_ip_addr_t *address_list;
+ } delegate_hostname;
+ } data;
+} mdns_action_t;
+
+/*
+ * @brief Convert mnds if to esp-netif handle
+ *
+ * @param tcpip_if mdns supported interface as internal enum
+ *
+ * @return
+ * - ptr to esp-netif on success
+ * - NULL if no available netif for current interface index
+ */
+esp_netif_t *_mdns_get_esp_netif(mdns_if_t tcpip_if);
+
+
+#endif /* MDNS_PRIVATE_H_ */
diff --git a/components/mdns/tests/host_test/CMakeLists.txt b/components/mdns/tests/host_test/CMakeLists.txt
new file mode 100644
index 000000000..132e436ea
--- /dev/null
+++ b/components/mdns/tests/host_test/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+set(COMPONENTS main)
+project(mdns_host)
diff --git a/components/mdns/tests/host_test/README.md b/components/mdns/tests/host_test/README.md
new file mode 100644
index 000000000..ab916520d
--- /dev/null
+++ b/components/mdns/tests/host_test/README.md
@@ -0,0 +1,25 @@
+# Setup dummy network interfaces
+```
+sudo ip link add eth2 type dummy
+sudo ip addr add 192.168.1.200/24 dev eth2
+sudo ip link set eth2 up
+sudo ifconfig eth2 multicast
+```
+
+# Dig on a specified interface
+
+```
+dig +short -b 192.168.1.200 -p 5353 @224.0.0.251 myesp.local
+```
+
+# Run avahi to browse services
+
+Avahi needs the netif to have the "multicast" flag set
+
+```bash
+david@david-comp:~/esp/idf (feature/mdns_networking_socket)$ avahi-browse -a -r -p
++;eth2;IPv6;myesp-service2;Web Site;local
++;eth2;IPv4;myesp-service2;Web Site;local
+=;eth2;IPv6;myesp-service2;Web Site;local;myesp.local;192.168.1.200;80;"board=esp32" "u=user" "p=password"
+=;eth2;IPv4;myesp-service2;Web Site;local;myesp.local;192.168.1.200;80;"board=esp32" "u=user" "p=password"
+```
diff --git a/components/mdns/tests/host_test/components/esp_event_mock/CMakeLists.txt b/components/mdns/tests/host_test/components/esp_event_mock/CMakeLists.txt
new file mode 100644
index 000000000..606c16f80
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_event_mock/CMakeLists.txt
@@ -0,0 +1,3 @@
+idf_component_register(SRCS esp_event_mock.c
+ INCLUDE_DIRS include
+ REQUIRES esp_system_protocols_linux)
diff --git a/components/mdns/tests/host_test/components/esp_event_mock/esp_event_mock.c b/components/mdns/tests/host_test/components/esp_event_mock/esp_event_mock.c
new file mode 100644
index 000000000..cd6b079b3
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_event_mock/esp_event_mock.c
@@ -0,0 +1,29 @@
+// Copyright 2021 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 "esp_err.h"
+#include "esp_event.h"
+
+const char * WIFI_EVENT = "WIFI_EVENT";
+const char * IP_EVENT = "IP_EVENT";
+
+esp_err_t esp_event_handler_register(const char * event_base, int32_t event_id, void* event_handler, void* event_handler_arg)
+{
+ return ESP_OK;
+}
+
+esp_err_t esp_event_handler_unregister(const char * event_base, int32_t event_id, void* event_handler)
+{
+ return ESP_OK;
+}
diff --git a/components/mdns/tests/host_test/components/esp_event_mock/include/esp_event.h b/components/mdns/tests/host_test/components/esp_event_mock/include/esp_event.h
new file mode 100644
index 000000000..18801e4b9
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_event_mock/include/esp_event.h
@@ -0,0 +1,32 @@
+// Copyright 2021 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.
+#pragma once
+
+#include "stdbool.h"
+#include "esp_err.h"
+#include "esp_event_base.h"
+#include "bsd_strings.h"
+
+#define ESP_EVENT_DECLARE_BASE(x)
+#define ESP_EVENT_ANY_ID (-1)
+
+typedef void * esp_event_base_t;
+typedef void * system_event_t;
+
+const char* WIFI_EVENT;
+const char* IP_EVENT;
+
+esp_err_t esp_event_handler_register(const char * event_base, int32_t event_id, void* event_handler, void* event_handler_arg);
+
+esp_err_t esp_event_handler_unregister(const char * event_base, int32_t event_id, void* event_handler);
diff --git a/components/mdns/tests/host_test/components/esp_event_mock/include/esp_event_base.h b/components/mdns/tests/host_test/components/esp_event_mock/include/esp_event_base.h
new file mode 100644
index 000000000..740b71270
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_event_mock/include/esp_event_base.h
@@ -0,0 +1,21 @@
+// Copyright 2021 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.
+#pragma once
+
+typedef enum {
+ WIFI_EVENT_STA_CONNECTED, /**< ESP32 station connected to AP */
+ WIFI_EVENT_STA_DISCONNECTED, /**< ESP32 station disconnected from AP */
+ WIFI_EVENT_AP_START, /**< ESP32 soft-AP start */
+ WIFI_EVENT_AP_STOP, /**< ESP32 soft-AP stop */
+} mdns_used_event_t;
diff --git a/components/mdns/tests/host_test/components/esp_netif_linux/CMakeLists.txt b/components/mdns/tests/host_test/components/esp_netif_linux/CMakeLists.txt
new file mode 100644
index 000000000..086034b08
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_netif_linux/CMakeLists.txt
@@ -0,0 +1,3 @@
+idf_component_register(SRCS esp_netif_linux.c
+ INCLUDE_DIRS include
+ REQUIRES esp_system_protocols_linux)
diff --git a/components/mdns/tests/host_test/components/esp_netif_linux/Kconfig b/components/mdns/tests/host_test/components/esp_netif_linux/Kconfig
new file mode 100644
index 000000000..f91ec201b
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_netif_linux/Kconfig
@@ -0,0 +1,9 @@
+menu "LWIP-MOCK-CONFIG"
+
+ config LWIP_IPV6
+ bool "Enable IPv6"
+ default y
+ help
+ Enable/disable IPv6
+
+endmenu
diff --git a/components/mdns/tests/host_test/components/esp_netif_linux/esp_netif_linux.c b/components/mdns/tests/host_test/components/esp_netif_linux/esp_netif_linux.c
new file mode 100644
index 000000000..bbde59073
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_netif_linux/esp_netif_linux.c
@@ -0,0 +1,163 @@
+// Copyright 2021 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
+#include
+
+#include "esp_netif.h"
+#include "esp_err.h"
+#include //strlen
+#include
+#include //inet_addr
+#include
+#include
+#include
+#include "esp_netif_types.h"
+
+#define MAX_NETIFS 4
+
+static esp_netif_t* s_netif_list[MAX_NETIFS] = { 0 };
+
+struct esp_netif_obj
+{
+ const char *if_key;
+ const char *if_desc;
+};
+
+esp_netif_t *esp_netif_get_handle_from_ifkey(const char *if_key)
+{
+ for (int i=0; iif_key, if_key) == 0) {
+ return s_netif_list[i];
+ }
+ }
+ return NULL;
+}
+
+esp_err_t esp_netif_get_ip_info(esp_netif_t *esp_netif, esp_netif_ip_info_t *ip_info)
+{
+ if (esp_netif == NULL) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ struct ifaddrs *addrs, *tmp;
+ getifaddrs(&addrs);
+ tmp = addrs;
+
+ while (tmp) {
+ if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) {
+ char addr[20];
+ struct sockaddr_in *pAddr = (struct sockaddr_in *) tmp->ifa_addr;
+ inet_ntop(AF_INET, &pAddr->sin_addr, addr, sizeof(addr) );
+ if (strcmp(esp_netif->if_desc, tmp->ifa_name) == 0) {
+ printf("AF_INET: %s: %s\n", tmp->ifa_name, addr);
+ memcpy(&ip_info->ip.addr, &pAddr->sin_addr, 4);
+ break;
+ }
+ }
+ tmp = tmp->ifa_next;
+ }
+ return ESP_OK;
+}
+
+esp_err_t esp_netif_dhcpc_get_status(esp_netif_t *esp_netif, esp_netif_dhcp_status_t *status)
+{
+ return ESP_OK;
+}
+
+
+esp_err_t esp_netif_get_ip6_linklocal(esp_netif_t *esp_netif, esp_ip6_addr_t *if_ip6)
+{
+ if (esp_netif == NULL) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ struct ifaddrs *addrs, *tmp;
+ getifaddrs(&addrs);
+ tmp = addrs;
+
+ while (tmp)
+ {
+ if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET6) {
+ char addr[64];
+ struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)tmp->ifa_addr;
+ inet_ntop(AF_INET6, &pAddr->sin6_addr, addr, sizeof(addr) );
+ if (strcmp(esp_netif->if_desc, tmp->ifa_name) == 0) {
+ printf("AF_INET6: %s: %s\n", tmp->ifa_name, addr);
+ memcpy(if_ip6->addr, &pAddr->sin6_addr, 4*4);
+ break;
+ }
+ }
+ tmp = tmp->ifa_next;
+ }
+
+ freeifaddrs(addrs);
+ return ESP_OK;
+}
+
+
+int esp_netif_get_netif_impl_index(esp_netif_t *esp_netif)
+{
+ if (esp_netif == NULL) {
+ return -1;
+ }
+ uint32_t interfaceIndex = if_nametoindex(esp_netif->if_desc);
+ return interfaceIndex;
+}
+
+esp_err_t esp_netif_get_netif_impl_name(esp_netif_t *esp_netif, char* name)
+{
+ if (esp_netif == NULL) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ strcpy(name, esp_netif->if_desc);
+ return ESP_OK;
+}
+
+const char *esp_netif_get_desc(esp_netif_t *esp_netif)
+{
+ if (esp_netif == NULL) {
+ return NULL;
+ }
+ return esp_netif->if_desc;
+}
+
+esp_netif_t *esp_netif_new(const esp_netif_config_t *config)
+{
+ if (esp_netif_get_handle_from_ifkey(config->base->if_key)) {
+ return NULL;
+ }
+ esp_netif_t* netif = calloc(1, sizeof(struct esp_netif_obj));
+ if (netif) {
+ netif->if_desc = config->base->if_desc;
+ netif->if_key = config->base->if_key;
+ }
+
+ for (int i=0; i
+
+void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression)
+{
+ ESP_LOGE("ESP_ERROR_CHECK", "Failed with esp_err_t: 0x%x", rc);
+ ESP_LOGE("ESP_ERROR_CHECK", "Expression: %s", expression);
+ ESP_LOGE("ESP_ERROR_CHECK", "Functions: %s %s(%d)", function, file, line);
+ abort();
+}
+
+void esp_log_buffer_hexdump_internal(const char *tag, const void *buffer, uint16_t buff_len, esp_log_level_t log_level)
+{
+ if ( LOG_LOCAL_LEVEL >= log_level ) {
+ ESP_LOG_LEVEL(log_level, tag, "Buffer:%p length:%d", buffer, buff_len);
+ for (int i=0; i
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "string.h"
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(dst, src, siz)
+ char *dst;
+ const char *src;
+ size_t siz;
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src)); /* count does not include NUL */
+}
diff --git a/components/mdns/tests/host_test/components/esp_timer_linux/CMakeLists.txt b/components/mdns/tests/host_test/components/esp_timer_linux/CMakeLists.txt
new file mode 100644
index 000000000..a2bec32aa
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_timer_linux/CMakeLists.txt
@@ -0,0 +1,9 @@
+idf_component_register(SRCS esp_timer_linux.c timer_task.cpp
+ INCLUDE_DIRS include
+ REQUIRES esp_system_protocols_linux freertos_linux)
+
+set_target_properties(${COMPONENT_LIB} PROPERTIES
+ CXX_STANDARD 17
+ CXX_STANDARD_REQUIRED ON
+ CXX_EXTENSIONS ON
+)
diff --git a/components/mdns/tests/host_test/components/esp_timer_linux/esp_timer_linux.c b/components/mdns/tests/host_test/components/esp_timer_linux/esp_timer_linux.c
new file mode 100644
index 000000000..bb2326985
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_timer_linux/esp_timer_linux.c
@@ -0,0 +1,47 @@
+// Copyright 2021 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 "esp_err.h"
+#include "esp_timer.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+void * create_tt(esp_timer_cb_t cb);
+
+void destroy_tt(void* tt);
+
+void set_tout(void* tt, uint32_t ms);
+
+esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args,
+ esp_timer_handle_t* out_handle)
+{
+ *out_handle = (esp_timer_handle_t)create_tt(create_args->callback);
+ return ESP_OK;
+}
+
+esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period)
+{
+ set_tout(timer, period/1000);
+ return ESP_OK;
+}
+
+esp_err_t esp_timer_stop(esp_timer_handle_t timer)
+{
+ return ESP_OK;
+}
+
+esp_err_t esp_timer_delete(esp_timer_handle_t timer)
+{
+ destroy_tt(timer);
+ return ESP_OK;
+}
diff --git a/components/mdns/tests/host_test/components/esp_timer_linux/include/esp_timer.h b/components/mdns/tests/host_test/components/esp_timer_linux/include/esp_timer.h
new file mode 100644
index 000000000..669163049
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_timer_linux/include/esp_timer.h
@@ -0,0 +1,41 @@
+// Copyright 2021 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.
+#pragma once
+
+#include
+#include
+
+typedef struct esp_timer* esp_timer_handle_t;
+
+typedef void (*esp_timer_cb_t)(void* arg);
+
+typedef enum {
+ ESP_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
+ bool skip_unhandled_events; //!< Skip unhandled events for periodic timers
+} esp_timer_create_args_t;
+
+esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args,
+ esp_timer_handle_t* out_handle);
+esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period);
+
+esp_err_t esp_timer_stop(esp_timer_handle_t timer);
+
+esp_err_t esp_timer_delete(esp_timer_handle_t timer);
diff --git a/components/mdns/tests/host_test/components/esp_timer_linux/timer_task.cpp b/components/mdns/tests/host_test/components/esp_timer_linux/timer_task.cpp
new file mode 100644
index 000000000..bd971b3f0
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_timer_linux/timer_task.cpp
@@ -0,0 +1,38 @@
+// Copyright 2021 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 "timer_task.hpp"
+#include
+#include
+#include
+#include
+
+extern "C" void * create_tt(cb_t cb)
+{
+ auto * tt = new TimerTaskMock(cb);
+ return tt;
+}
+
+extern "C" void destroy_tt(void* tt)
+{
+ auto * timer_task = static_cast(tt);
+ delete(timer_task);
+}
+
+
+extern "C" void set_tout(void* tt, uint32_t ms)
+{
+ auto * timer_task = static_cast(tt);
+ timer_task->SetTimeout(ms);
+}
diff --git a/components/mdns/tests/host_test/components/esp_timer_linux/timer_task.hpp b/components/mdns/tests/host_test/components/esp_timer_linux/timer_task.hpp
new file mode 100644
index 000000000..8d32a6bd0
--- /dev/null
+++ b/components/mdns/tests/host_test/components/esp_timer_linux/timer_task.hpp
@@ -0,0 +1,61 @@
+// Copyright 2021 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.
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+typedef void (*cb_t)(void* arg);
+
+class TimerTaskMock
+{
+public:
+ TimerTaskMock(cb_t cb): cb(cb), t(run_static, this), active(false), ms(INT32_MAX) {}
+ ~TimerTaskMock(void) { active = false; t.join(); }
+
+ void SetTimeout(uint32_t m)
+ {
+ ms = m;
+ active = true;
+ }
+
+private:
+
+ static void run_static(TimerTaskMock* timer)
+ {
+ timer->run();
+ }
+
+ void run(void)
+ {
+ while (!active.load()) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ while (active.load()) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(ms));
+ cb(nullptr);
+ }
+ }
+
+ cb_t cb;
+ std::thread t;
+ std::atomic active;
+ uint32_t ms;
+
+};
diff --git a/components/mdns/tests/host_test/components/freertos_linux/CMakeLists.txt b/components/mdns/tests/host_test/components/freertos_linux/CMakeLists.txt
new file mode 100644
index 000000000..cd1686154
--- /dev/null
+++ b/components/mdns/tests/host_test/components/freertos_linux/CMakeLists.txt
@@ -0,0 +1,13 @@
+idf_component_register(SRCS freertos_linux.c queue_unique_ptr.cpp
+ INCLUDE_DIRS include
+ REQUIRES esp_system_protocols_linux)
+
+set(THREADS_PREFER_PTHREAD_FLAG ON)
+find_package(Threads REQUIRED)
+target_link_libraries(${COMPONENT_LIB} PRIVATE Threads::Threads)
+
+set_target_properties(${COMPONENT_LIB} PROPERTIES
+ CXX_STANDARD 17
+ CXX_STANDARD_REQUIRED ON
+ CXX_EXTENSIONS ON
+)
diff --git a/components/mdns/tests/host_test/components/freertos_linux/Kconfig b/components/mdns/tests/host_test/components/freertos_linux/Kconfig
new file mode 100644
index 000000000..fa57dab7e
--- /dev/null
+++ b/components/mdns/tests/host_test/components/freertos_linux/Kconfig
@@ -0,0 +1,7 @@
+menu "FreeRTOS"
+
+ config FREERTOS_NO_AFFINITY
+ hex
+ default 0x7FFFFFFF
+
+endmenu
diff --git a/components/mdns/tests/host_test/components/freertos_linux/freertos_linux.c b/components/mdns/tests/host_test/components/freertos_linux/freertos_linux.c
new file mode 100644
index 000000000..763364504
--- /dev/null
+++ b/components/mdns/tests/host_test/components/freertos_linux/freertos_linux.c
@@ -0,0 +1,192 @@
+// Copyright 2021 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
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include
+#include
+#include
+#include
+
+void * create_q(void);
+
+void destroy_q(void* q);
+
+bool send_q(void* q, uint8_t *data, size_t len);
+
+bool recv_q(void* q, uint8_t *data, size_t len, uint32_t ms);
+
+static uint64_t s_semaphore_data = 0;
+
+struct queue_handle {
+ size_t item_size;
+ void * q;
+};
+
+QueueHandle_t xQueueCreate( uint32_t uxQueueLength, uint32_t uxItemSize )
+{
+ struct queue_handle * h = calloc(1, sizeof(struct queue_handle));
+ h->item_size = uxItemSize;
+ h->q = create_q();
+ return (QueueHandle_t)h;
+}
+
+uint32_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait)
+{
+ struct queue_handle * h = xQueue;
+ return send_q(h->q, (uint8_t*)pvItemToQueue, h->item_size) ? pdTRUE : pdFAIL;
+}
+
+uint32_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)
+{
+ struct queue_handle * h = xQueue;
+ return recv_q(h->q, (uint8_t*)pvBuffer, h->item_size, xTicksToWait) ? pdTRUE : pdFAIL;
+}
+
+BaseType_t xSemaphoreGive( QueueHandle_t xQueue)
+{
+ return xQueueSend(xQueue, &s_semaphore_data, portMAX_DELAY);
+}
+
+BaseType_t xSemaphoreTake( QueueHandle_t xQueue, TickType_t pvTask )
+{
+ return xQueueReceive(xQueue, &s_semaphore_data, portMAX_DELAY);
+}
+
+void vQueueDelete( QueueHandle_t xQueue )
+{
+ struct queue_handle * h = xQueue;
+ if (h->q) {
+ destroy_q(h->q);
+ }
+ free(xQueue);
+}
+
+QueueHandle_t xSemaphoreCreateBinary(void)
+{
+ QueueHandle_t sempaphore = xQueueCreate(1, 1);
+ return sempaphore;
+}
+
+QueueHandle_t xSemaphoreCreateMutex(void)
+{
+ QueueHandle_t sempaphore = xQueueCreate(1, 1);
+ if (sempaphore) {
+ xSemaphoreGive(sempaphore);
+ }
+ return sempaphore;
+}
+
+void vTaskDelete(TaskHandle_t *task)
+{
+ if (task == NULL) {
+ pthread_exit(0);
+ }
+ void *thread_rval = NULL;
+ pthread_join((pthread_t)task, &thread_rval);
+}
+
+TickType_t xTaskGetTickCount( void )
+{
+ struct timespec spec;
+ clock_gettime(CLOCK_REALTIME, &spec);
+ return spec.tv_nsec / 1000000 + spec.tv_sec * 1000;
+}
+
+void vTaskDelay( const TickType_t xTicksToDelay )
+{
+ usleep(xTicksToDelay*1000);
+}
+
+void * pthread_task(void * params)
+{
+ struct {
+ void * const param;
+ TaskFunction_t task;
+ bool started;
+ } *pthread_params = params;
+
+ void * const param = pthread_params->param;
+ TaskFunction_t task = pthread_params->task;
+ pthread_params->started = true;
+
+ task(param);
+
+ return NULL;
+}
+
+BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pvTaskCode,
+ const char * const pcName,
+ const uint32_t usStackDepth,
+ void * const pvParameters,
+ UBaseType_t uxPriority,
+ TaskHandle_t * const pvCreatedTask,
+ const BaseType_t xCoreID)
+{
+ xTaskCreate(pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pvCreatedTask);
+ return pdTRUE;
+}
+
+
+void xTaskCreate(TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pvCreatedTask)
+{
+ pthread_t new_thread = (pthread_t)NULL;
+ pthread_attr_t attr;
+ struct {
+ void * const param;
+ TaskFunction_t task;
+ bool started;
+ } pthread_params = { .param = pvParameters, .task = pvTaskCode};
+ int res = pthread_attr_init(&attr);
+ assert(res == 0);
+ res = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+ assert(res == 0);
+ res = pthread_create(&new_thread, &attr, pthread_task, &pthread_params);
+ assert(res == 0);
+
+ if (pvCreatedTask) {
+ *pvCreatedTask = (void*)new_thread;
+ }
+
+ // just wait till the task started so we can unwind params from the stack
+ while (pthread_params.started == false) {
+ usleep(1000);
+ }
+}
+
+uint32_t esp_get_free_heap_size(void)
+{
+ return 0;
+}
+
+uint32_t esp_random(void)
+{
+ return rand();
+}
+
+void xTaskNotifyGive(TaskHandle_t task)
+{
+
+}
+
+BaseType_t xTaskNotifyWait(uint32_t bits_entry_clear, uint32_t bits_exit_clear, uint32_t *value, TickType_t wait_time )
+{
+ return true;
+}
+
+TaskHandle_t xTaskGetCurrentTaskHandle(void)
+{
+ return NULL;
+}
diff --git a/components/mdns/tests/host_test/components/freertos_linux/include/esp_task.h b/components/mdns/tests/host_test/components/freertos_linux/include/esp_task.h
new file mode 100644
index 000000000..797b416b6
--- /dev/null
+++ b/components/mdns/tests/host_test/components/freertos_linux/include/esp_task.h
@@ -0,0 +1,20 @@
+// Copyright 2021 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.
+#pragma once
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#define ESP_TASK_PRIO_MAX 25
+#define ESP_TASKD_EVENT_PRIO 5
diff --git a/components/mdns/tests/host_test/components/freertos_linux/include/freertos/FreeRTOS.h b/components/mdns/tests/host_test/components/freertos_linux/include/freertos/FreeRTOS.h
new file mode 100644
index 000000000..2c4bcbfca
--- /dev/null
+++ b/components/mdns/tests/host_test/components/freertos_linux/include/freertos/FreeRTOS.h
@@ -0,0 +1,34 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#define portTICK_PERIOD_MS 1
+#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
+
+typedef void * SemaphoreHandle_t;
+typedef void * QueueHandle_t;
+typedef void * TaskHandle_t;
+typedef uint32_t TickType_t;
+
+typedef void (*TaskFunction_t)( void * );
+typedef unsigned int UBaseType_t;
+typedef int BaseType_t;
+
+#define pdFALSE ( ( BaseType_t ) 0 )
+#define pdTRUE ( ( BaseType_t ) 1 )
+
+#define pdPASS ( pdTRUE )
+#define pdFAIL ( pdFALSE )
+
+#define pdMS_TO_TICKS(tick) (tick)
+
+uint32_t esp_get_free_heap_size(void);
+uint32_t esp_random(void);
diff --git a/components/mdns/tests/host_test/components/freertos_linux/include/freertos/task.h b/components/mdns/tests/host_test/components/freertos_linux/include/freertos/task.h
new file mode 100644
index 000000000..39b7e19c4
--- /dev/null
+++ b/components/mdns/tests/host_test/components/freertos_linux/include/freertos/task.h
@@ -0,0 +1,50 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include "freertos/FreeRTOS.h"
+
+#define TaskHandle_t TaskHandle_t
+#define vSemaphoreDelete( xSemaphore ) vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )
+
+void vTaskDelay( const TickType_t xTicksToDelay );
+
+void xTaskNotifyGive(TaskHandle_t task);
+
+TaskHandle_t xTaskGetCurrentTaskHandle(void);
+
+BaseType_t xTaskNotifyWait(uint32_t bits_entry_clear, uint32_t bits_exit_clear, uint32_t *value, TickType_t wait_time );
+
+BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pvTaskCode,
+ const char * const pcName,
+ const uint32_t usStackDepth,
+ void * const pvParameters,
+ UBaseType_t uxPriority,
+ TaskHandle_t * const pvCreatedTask,
+ const BaseType_t xCoreID);
+
+void xTaskCreate(TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pvCreatedTask);
+
+TickType_t xTaskGetTickCount( void );
+
+void vQueueDelete( QueueHandle_t xQueue );
+
+QueueHandle_t xSemaphoreCreateBinary(void);
+
+QueueHandle_t xSemaphoreCreateMutex(void);
+
+BaseType_t xSemaphoreGive( QueueHandle_t xQueue);
+
+BaseType_t xSemaphoreTake( QueueHandle_t xQueue, TickType_t pvTask );
+
+void vTaskDelete(TaskHandle_t *task);
+
+QueueHandle_t xQueueCreate( uint32_t uxQueueLength,
+ uint32_t uxItemSize );
+
+uint32_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);
+
+uint32_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
diff --git a/components/mdns/tests/host_test/components/freertos_linux/queue_unique_ptr.cpp b/components/mdns/tests/host_test/components/freertos_linux/queue_unique_ptr.cpp
new file mode 100644
index 000000000..60c4a8a28
--- /dev/null
+++ b/components/mdns/tests/host_test/components/freertos_linux/queue_unique_ptr.cpp
@@ -0,0 +1,51 @@
+// Copyright 2021 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 "queue_unique_ptr.hpp"
+#include
+#include
+#include
+#include
+
+extern "C" void * create_q(void)
+{
+ auto * q = new QueueMock>();
+ return q;
+}
+
+extern "C" void destroy_q(void* q)
+{
+ auto * queue = static_cast> *>(q);
+ delete(queue);
+}
+
+extern "C" bool send_q(void* q, uint8_t *data, size_t len)
+{
+ auto v = std::make_unique>(len);
+ v->assign(data, data+len);
+ auto queue = static_cast> *>(q);
+ queue->send(std::move(v));
+ return true;
+}
+
+extern "C" bool recv_q(void* q, uint8_t *data, size_t len, uint32_t ms)
+{
+ auto queue = static_cast> *>(q);
+ auto v = queue->receive(ms);
+ if (v == nullptr) {
+ return false;
+ }
+ memcpy(data, (void *)v->data(), len);
+ return true;
+}
diff --git a/components/mdns/tests/host_test/components/freertos_linux/queue_unique_ptr.hpp b/components/mdns/tests/host_test/components/freertos_linux/queue_unique_ptr.hpp
new file mode 100644
index 000000000..fe722b97f
--- /dev/null
+++ b/components/mdns/tests/host_test/components/freertos_linux/queue_unique_ptr.hpp
@@ -0,0 +1,55 @@
+// Copyright 2021 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.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+template
+class QueueMock
+{
+public:
+ QueueMock(void): q(), m(), c() {}
+ ~QueueMock(void) {}
+
+void send(std::unique_ptr t)
+{
+ std::lock_guard lock(m);
+ q.push(std::move(t));
+ c.notify_one();
+}
+
+std::unique_ptr receive(uint32_t ms)
+{
+ std::unique_lock lock(m);
+ while(q.empty()) {
+ if (c.wait_for(lock, std::chrono::milliseconds(ms)) == std::cv_status::timeout) {
+ return nullptr;
+ }
+ }
+ std::unique_ptr val = std::move(q.front());
+ q.pop();
+ return val;
+}
+
+private:
+ std::queue> q;
+ mutable std::mutex m;
+ std::condition_variable c;
+};
diff --git a/components/mdns/tests/host_test/main/CMakeLists.txt b/components/mdns/tests/host_test/main/CMakeLists.txt
new file mode 100644
index 000000000..8d5202d64
--- /dev/null
+++ b/components/mdns/tests/host_test/main/CMakeLists.txt
@@ -0,0 +1,4 @@
+idf_component_register(SRCS "main.c"
+ INCLUDE_DIRS
+ "."
+ REQUIRES mdns)
diff --git a/components/mdns/tests/host_test/main/main.c b/components/mdns/tests/host_test/main/main.c
new file mode 100644
index 000000000..f3883b8c3
--- /dev/null
+++ b/components/mdns/tests/host_test/main/main.c
@@ -0,0 +1,59 @@
+#include
+#include "mdns.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+static const char *TAG = "mdns-test";
+
+static void query_mdns_host(const char * host_name)
+{
+ ESP_LOGI(TAG, "Query A: %s.local", host_name);
+
+ struct esp_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, "%x: Host was not found!", (err));
+ return;
+ }
+ ESP_LOGE(TAG, "Query Failed: %x", (err));
+ return;
+ }
+
+ ESP_LOGI(TAG, "Query A: %s.local resolved to: " IPSTR, host_name, IP2STR(&addr));
+}
+
+int main(int argc , char *argv[])
+{
+
+ setvbuf(stdout, NULL, _IONBF, 0);
+ const esp_netif_inherent_config_t base_cg = { .if_key = "WIFI_STA_DEF", .if_desc = "eth2" };
+ esp_netif_config_t cfg = { .base = &base_cg };
+ esp_netif_t *sta = esp_netif_new(&cfg);
+
+ mdns_init();
+
+ mdns_hostname_set("myesp");
+ ESP_LOGI(TAG, "mdns hostname set to: [%s]", "myesp");
+ //set default mDNS instance name
+ mdns_instance_name_set("myesp-inst");
+ //structure with TXT records
+ mdns_txt_item_t serviceTxtData[3] = {
+ {"board","esp32"},
+ {"u","user"},
+ {"p","password"}
+ };
+ vTaskDelay(1000);
+ ESP_ERROR_CHECK(mdns_service_add("myesp-service2", "_http", "_tcp", 80, serviceTxtData, 3));
+ vTaskDelay(2000);
+
+ query_mdns_host("david-comp");
+ vTaskDelay(2000);
+ esp_netif_destroy(sta);
+ mdns_free();
+ ESP_LOGI(TAG, "Exit");
+ return 0;
+}
diff --git a/components/mdns/tests/test_afl_fuzz_host/CMakeLists.txt b/components/mdns/tests/test_afl_fuzz_host/CMakeLists.txt
new file mode 100644
index 000000000..d65845194
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/CMakeLists.txt
@@ -0,0 +1,7 @@
+# The following four lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+
+project(fuzz_test_update)
diff --git a/components/mdns/tests/test_afl_fuzz_host/Makefile b/components/mdns/tests/test_afl_fuzz_host/Makefile
new file mode 100644
index 000000000..313b21ac5
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/Makefile
@@ -0,0 +1,83 @@
+TEST_NAME=test
+FUZZ=afl-fuzz
+COMPONENTS_DIR=$(IDF_PATH)/components
+COMPILER_ICLUDE_DIR=$(shell echo `which xtensa-esp32-elf-gcc | xargs dirname | xargs dirname`/xtensa-esp32-elf)
+
+CFLAGS=-g -Wno-unused-value -Wno-missing-declarations -Wno-pointer-bool-conversion -Wno-macro-redefined -Wno-int-to-void-pointer-cast -DHOOK_MALLOC_FAILED -DESP_EVENT_H_ -D__ESP_LOG_H__ \
+ -I. -I../.. -I../../include -I../../private_include -I ./build/config \
+ -I$(COMPONENTS_DIR) \
+ -I$(COMPONENTS_DIR)/driver/include \
+ -I$(COMPONENTS_DIR)/esp_common/include \
+ -I$(COMPONENTS_DIR)/esp_event/include \
+ -I$(COMPONENTS_DIR)/esp_eth/include \
+ -I$(COMPONENTS_DIR)/esp_hw_support/include \
+ -I$(COMPONENTS_DIR)/esp_netif/include \
+ -I$(COMPONENTS_DIR)/esp_netif/private_include \
+ -I$(COMPONENTS_DIR)/esp_netif/lwip \
+ -I$(COMPONENTS_DIR)/esp_rom/include \
+ -I$(COMPONENTS_DIR)/esp_system/include \
+ -I$(COMPONENTS_DIR)/esp_timer/include \
+ -I$(COMPONENTS_DIR)/esp_wifi/include \
+ -I$(COMPONENTS_DIR)/freertos/FreeRTOS-Kernel \
+ -I$(COMPONENTS_DIR)/freertos/FreeRTOS-Kernel/include \
+ -I$(COMPONENTS_DIR)/freertos/esp_additions/include/freertos \
+ -I$(COMPONENTS_DIR)/hal/include \
+ -I$(COMPONENTS_DIR)/hal/esp32/include \
+ -I$(COMPONENTS_DIR)/heap/include \
+ -I$(COMPONENTS_DIR)/log/include \
+ -I$(COMPONENTS_DIR)/lwip/lwip/src/include \
+ -I$(COMPONENTS_DIR)/lwip/port/esp32/include \
+ -I$(COMPONENTS_DIR)/lwip/lwip/src/include/lwip/apps \
+ -I$(COMPONENTS_DIR)/newlib/platform_include \
+ -I$(COMPONENTS_DIR)/soc/include \
+ -I$(COMPONENTS_DIR)/soc/include \
+ -I$(COMPONENTS_DIR)/soc/esp32/include \
+ -I$(COMPONENTS_DIR)/soc/src/esp32/include \
+ -I$(COMPONENTS_DIR)/xtensa/include \
+ -I$(COMPONENTS_DIR)/xtensa/esp32/include \
+ -I$(COMPILER_ICLUDE_DIR)/include
+
+
+MDNS_C_DEPENDENCY_INJECTION=-include mdns_di.h
+ifeq ($(MDNS_NO_SERVICES),on)
+ CFLAGS+=-DMDNS_NO_SERVICES
+endif
+
+ifeq ($(INSTR),off)
+ CC=gcc
+ CFLAGS+=-DINSTR_IS_OFF
+ TEST_NAME=test_sim
+else
+ CC=afl-clang-fast
+endif
+CPP=$(CC)
+LD=$(CC)
+OBJECTS=esp32_mock.o mdns.o test.o esp_netif_mock.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) -include mdns_mock.h $(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/tests/test_afl_fuzz_host/README.md b/components/mdns/tests/test_afl_fuzz_host/README.md
new file mode 100644
index 000000000..11f9c5e6d
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/README.md
@@ -0,0 +1,80 @@
+## 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 actual 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.The output of the parser before fuzzing can be found in [input_packets.txt](input_packets.txt)
+
+## Building and running the tests using AFL
+To build and run the tests using AFL(afl-clang-fast) instrumentation
+
+```bash
+cd $IDF_PATH/components/mdns/test_afl_host
+make fuzz
+```
+
+(Please note you have to install AFL instrumentation first, check `Installing AFL` section)
+
+## Building the tests using GCC INSTR(off)
+
+To build the tests without AFL instrumentations and instead of that use GCC compiler(In this case it will only check for compilation issues and will not run AFL tests).
+
+```bash
+cd $IDF_PATH/components/mdns/test_afl_host
+make INSTR=off
+```
+
+Note, that this setup is useful if we want to reproduce issues reported by fuzzer tests executed in the CI, or to simulate how the packet parser treats the input packets on the host machine.
+
+## Installing AFL
+To run the test yourself, you need to download 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 install 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-4.0(or <=4.0) llvm-4.0(or <=4.0) libbsd-dev
+ ```
+
+Please note that if specified package version can't be installed(due to system is the latest), you can download, build and install it manually.
+
+### 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"
+```
+
+Please note LLVM must be <=4.0.0, otherwise afl does not compile, as there are some limitations with building AFL on MacOS/Linux with the latest LLVM. Also, Windows build on cygwin is not fully supported.
+
+## Additional info
+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
+```
diff --git a/components/mdns/tests/test_afl_fuzz_host/esp32_mock.c b/components/mdns/tests/test_afl_fuzz_host/esp32_mock.c
new file mode 100644
index 000000000..08ac305c5
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/esp32_mock.c
@@ -0,0 +1,111 @@
+#include
+#include
+#include
+#include
+#include
+#include "esp32_mock.h"
+
+void* g_queue;
+int g_queue_send_shall_fail = 0;
+int g_size = 0;
+
+const char * WIFI_EVENT = "wifi_event";
+const char * ETH_EVENT = "eth_event";
+
+esp_err_t esp_event_handler_register(const char * event_base,
+ int32_t event_id,
+ void* event_handler,
+ void* event_handler_arg)
+{
+ return ESP_OK;
+}
+
+esp_err_t esp_event_handler_unregister(const char * event_base, int32_t event_id, void* event_handler)
+{
+ return ESP_OK;
+}
+
+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(void)
+{
+ static uint32_t tick = 0;
+ return tick++;
+}
+
+/// 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(void)
+{
+ g_queue_send_shall_fail = 1;
+}
+
+TaskHandle_t xTaskGetCurrentTaskHandle(void)
+{
+ return NULL;
+}
+
+void xTaskNotifyGive(TaskHandle_t task)
+{
+ return;
+}
+
+BaseType_t xTaskNotifyWait(uint32_t bits_entry_clear, uint32_t bits_exit_clear, uint32_t * value, TickType_t wait_time)
+{
+ return pdTRUE;
+}
diff --git a/components/mdns/tests/test_afl_fuzz_host/esp32_mock.h b/components/mdns/tests/test_afl_fuzz_host/esp32_mock.h
new file mode 100644
index 000000000..e76a17ab6
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/esp32_mock.h
@@ -0,0 +1,146 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef _ESP32_COMPAT_H_
+#define _ESP32_COMPAT_H_
+
+// Skip these include files
+#define ESP_MDNS_NETWORKING_H_
+#define INC_FREERTOS_H
+#define QUEUE_H
+#define SEMAPHORE_H
+#define _ESP_TASK_H_
+
+#ifdef USE_BSD_STRING
+#include
+#include
+#endif
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "esp_timer.h"
+
+#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 ESP_LOGW(a,b)
+#define ESP_LOGD(a,b)
+#define ESP_LOGE(a,b,c)
+#define ESP_LOGV(a,b,c,d)
+
+#define LWIP_HDR_PBUF_H
+#define __ESP_RANDOM_H__
+#define INC_TASK_H
+
+#define pdMS_TO_TICKS(a) a
+#define portTICK_PERIOD_MS 10
+#define xSemaphoreTake(s,d) true
+#define xTaskDelete(a)
+#define vTaskDelete(a) free(a)
+#define xSemaphoreGive(s)
+#define xQueueCreateMutex(s)
+#define _mdns_pcb_init(a,b) true
+#define _mdns_pcb_deinit(a,b) true
+#define xSemaphoreCreateMutex() malloc(1)
+#define xSemaphoreCreateBinary() malloc(1)
+#define vSemaphoreDelete(s) free(s)
+#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U
+#define xTaskCreatePinnedToCore(a,b,c,d,e,f,g) *(f) = malloc(1)
+#define vTaskDelay(m) usleep((m)*0)
+#define esp_random() (rand()%UINT32_MAX)
+
+
+#define ESP_TASK_PRIO_MAX 25
+#define ESP_TASKD_EVENT_PRIO 5
+#define _mdns_udp_pcb_write(tcpip_if, ip_protocol, ip, port, data, len) len
+#define TaskHandle_t TaskHandle_t
+
+
+typedef int32_t esp_err_t;
+
+typedef void * SemaphoreHandle_t;
+typedef void * QueueHandle_t;
+typedef void * TaskHandle_t;
+typedef int BaseType_t;
+typedef uint32_t TickType_t;
+
+
+extern const char * WIFI_EVENT;
+extern const char * IP_EVENT;
+extern const char * ETH_EVENT;
+
+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 void* system_event_t;
+
+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;
+};
+
+uint32_t xTaskGetTickCount(void);
+typedef void (*esp_timer_cb_t)(void* arg);
+
+// 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(void);
+
+esp_err_t esp_event_handler_register(const char * event_base, int32_t event_id, void* event_handler, void* event_handler_arg);
+
+esp_err_t esp_event_handler_unregister(const char * event_base, int32_t event_id, void* event_handler);
+
+
+TaskHandle_t xTaskGetCurrentTaskHandle(void);
+void xTaskNotifyGive(TaskHandle_t task);
+BaseType_t xTaskNotifyWait(uint32_t bits_entry_clear, uint32_t bits_exit_clear, uint32_t *value, TickType_t wait_time );
+
+#endif //_ESP32_COMPAT_H_
diff --git a/components/mdns/tests/test_afl_fuzz_host/esp_attr.h b/components/mdns/tests/test_afl_fuzz_host/esp_attr.h
new file mode 100644
index 000000000..49bb7515d
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/esp_attr.h
@@ -0,0 +1,9 @@
+#pragma once
+#define IRAM_ATTR
+#define FLAG_ATTR(TYPE)
+#define QUEUE_H
+#define __ARCH_CC_H__
+#define __XTENSA_API_H__
+#define SSIZE_MAX INT_MAX
+#define LWIP_HDR_IP6_ADDR_H
+#define LWIP_HDR_IP4_ADDR_H
diff --git a/components/mdns/tests/test_afl_fuzz_host/esp_netif_mock.c b/components/mdns/tests/test_afl_fuzz_host/esp_netif_mock.c
new file mode 100644
index 000000000..36118199b
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/esp_netif_mock.c
@@ -0,0 +1,63 @@
+// Copyright 2020 Espressif Systems (Shanghai) CO 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
+#include
+#include
+#include
+#include
+#include "esp32_mock.h"
+
+typedef struct esp_netif_s esp_netif_t;
+typedef struct esp_netif_ip_info esp_netif_ip_info_t;
+typedef struct esp_netif_dhcp_status esp_netif_dhcp_status_t;
+
+
+const char * IP_EVENT = "IP_EVENT";
+
+
+esp_err_t esp_netif_add_to_list(esp_netif_t *netif)
+{
+ return ESP_OK;
+}
+
+esp_err_t esp_netif_remove_from_list(esp_netif_t *netif)
+{
+ return ESP_ERR_NOT_FOUND;
+}
+
+esp_netif_t* esp_netif_next(esp_netif_t* netif)
+{
+ return NULL;
+}
+
+esp_netif_t* esp_netif_next_unsafe(esp_netif_t* netif)
+{
+ return NULL;
+}
+
+esp_netif_t *esp_netif_get_handle_from_ifkey(const char *if_key)
+{
+ return NULL;
+}
+
+esp_err_t esp_netif_get_ip_info(esp_netif_t *esp_netif, esp_netif_ip_info_t *ip_info)
+{
+ return ESP_ERR_NOT_SUPPORTED;
+}
+
+esp_err_t esp_netif_dhcpc_get_status(esp_netif_t *esp_netif, esp_netif_dhcp_status_t *status)
+{
+ return ESP_ERR_NOT_SUPPORTED;
+}
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/file2.bin b/components/mdns/tests/test_afl_fuzz_host/in/file2.bin
new file mode 100644
index 000000000..a7ce859cc
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/file2.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/minif_4a_txt.bin b/components/mdns/tests/test_afl_fuzz_host/in/minif_4a_txt.bin
new file mode 100644
index 000000000..37f280350
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/minif_4a_txt.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/minif_aaaa.bin b/components/mdns/tests/test_afl_fuzz_host/in/minif_aaaa.bin
new file mode 100644
index 000000000..6ddb7acc8
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/minif_aaaa.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/minif_any.bin b/components/mdns/tests/test_afl_fuzz_host/in/minif_any.bin
new file mode 100644
index 000000000..99a6d18d8
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/minif_any.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/minif_disc.bin b/components/mdns/tests/test_afl_fuzz_host/in/minif_disc.bin
new file mode 100644
index 000000000..5f3ba62e2
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/minif_disc.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/minif_ptr.bin b/components/mdns/tests/test_afl_fuzz_host/in/minif_ptr.bin
new file mode 100644
index 000000000..67da5edb0
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/minif_ptr.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/minif_query.bin b/components/mdns/tests/test_afl_fuzz_host/in/minif_query.bin
new file mode 100644
index 000000000..d21c0d29e
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/minif_query.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/minif_query2.bin b/components/mdns/tests/test_afl_fuzz_host/in/minif_query2.bin
new file mode 100644
index 000000000..986118d3c
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/minif_query2.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/sub_fritz_m.bin b/components/mdns/tests/test_afl_fuzz_host/in/sub_fritz_m.bin
new file mode 100644
index 000000000..68e1d752c
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/sub_fritz_m.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/telnet_ptr.bin b/components/mdns/tests/test_afl_fuzz_host/in/telnet_ptr.bin
new file mode 100644
index 000000000..1cd8e85d8
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/telnet_ptr.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-14.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-14.bin
new file mode 100644
index 000000000..b9d059d82
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-14.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-15.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-15.bin
new file mode 100644
index 000000000..3250de543
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-15.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-16.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-16.bin
new file mode 100644
index 000000000..19915d89c
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-16.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-28.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-28.bin
new file mode 100644
index 000000000..537352ae6
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-28.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-29.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-29.bin
new file mode 100644
index 000000000..837737b32
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-29.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-31.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-31.bin
new file mode 100644
index 000000000..690b73558
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-31.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-53.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-53.bin
new file mode 100644
index 000000000..73181ea8c
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-53.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-56.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-56.bin
new file mode 100644
index 000000000..980b6516a
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-56.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-63.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-63.bin
new file mode 100644
index 000000000..883d444ba
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-63.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-83.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-83.bin
new file mode 100644
index 000000000..c6cc159a2
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-83.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-88.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-88.bin
new file mode 100644
index 000000000..fcbb38443
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-88.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-89.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-89.bin
new file mode 100644
index 000000000..d60a987ca
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-89.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-95.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-95.bin
new file mode 100644
index 000000000..26553090f
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-95.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/in/test-96.bin b/components/mdns/tests/test_afl_fuzz_host/in/test-96.bin
new file mode 100644
index 000000000..fabf8b793
Binary files /dev/null and b/components/mdns/tests/test_afl_fuzz_host/in/test-96.bin differ
diff --git a/components/mdns/tests/test_afl_fuzz_host/input_packets.txt b/components/mdns/tests/test_afl_fuzz_host/input_packets.txt
new file mode 100644
index 000000000..15c5df2ba
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/input_packets.txt
@@ -0,0 +1,166 @@
+Input: in/test-14.bin
+Packet Length: 568
+Questions: 18
+ Q: _airport._tcp.local. PTR IN
+ Q: _http._tcp.local. PTR IN
+ Q: _printer._tcp.local. PTR IN
+ Q: _sub._http._tcp.local. PTR IN
+ Q: _airplay._tcp.local. PTR IN
+ Q: _raop._tcp.local. PTR IN
+ Q: _uscan._tcp.local. PTR IN
+ Q: _uscans._tcp.local. PTR IN
+ Q: _ippusb._tcp.local. PTR IN
+ Q: _scanner._tcp.local. PTR IN
+ Q: _ipp._tcp.local. PTR IN
+ Q: _ipps._tcp.local. PTR IN
+ Q: _pdl-datastream._tcp.local. PTR IN
+ Q: _ptp._tcp.local. PTR IN
+ Q: _sleep-proxy._udp.local. PTR IN
+ Q: 9801A7E58FA1@Hristo's AirPort Express._raop._tcp.local. TXT IN
+ Q: Hristo's AirPort Express._airport._tcp.local. TXT IN
+ Q: Hristo's Time Capsule._airport._tcp.local. TXT IN
+Answers: 7 + 0
+ A: _airport._tcp.local. PTR IN 2272 [2] Hristo's AirPort Express._airport._tcp.local.
+ A: _airport._tcp.local. PTR IN 2272 [2] Hristo's Time Capsule._airport._tcp.local.
+ A: _http._tcp.local. PTR IN 2535 [23] HP LaserJet CP1025nw._http._tcp.local.
+ A: _printer._tcp.local. PTR IN 2535 [23] HP LaserJet CP1025nw._printer._tcp.local.
+ A: _ipp._tcp.local. PTR IN 2535 [23] HP LaserJet CP1025nw._ipp._tcp.local.
+ A: _pdl-datastream._tcp.local. PTR IN 2535 [23] HP LaserJet CP1025nw._pdl-datastream._tcp.local.
+ A: _sleep-proxy._udp.local. PTR IN 2535 [38] 50-34-10-70.1 Hristo's Time Capsule._sleep-proxy._udp.local.
+
+Input: in/test-15.bin
+Packet Length: 524
+Answers: 3 + 3
+ A: Hristo's AirPort Express._airport._tcp.local. TXT IN FLUSH 4500 [166] waMA=98-01-A7-E5-8F-A1,raMA=98-01-A7-E8-C2-2E,raM2=98-01-A7-E8-C2-2F,raNm=your-ssid,raCh=1,rCh2=52,raSt=0,raNA=1,syFl=0x8A0C,syAP=115,syVs=7.6.8,srcv=76800.1,bjSd=23
+ A: 9801A7E58FA1@Hristo's AirPort Express._raop._tcp.local. TXT IN FLUSH 4500 [134] txtvers=1; ch=2; cn=0,1; et=0,4; sv=false; da=true; sr=44100; ss=16; pw=false; vn=65537; tp=TCP,UDP; vs=105.1; am=AirPort10,115; fv=76800.1; sf=0x1
+ A: _raop._tcp.local. PTR IN 4500 [2] 9801A7E58FA1@Hristo's AirPort Express._raop._tcp.local.
+ A: 9801A7E58FA1@Hristo's AirPort Express._raop._tcp.local. SRV IN FLUSH 120 [32] 5000 Hristos-AirPort-Express.local.
+ A: Hristo's AirPort Express.local. NSEC IN FLUSH 4500 [9] Hristo's AirPort Express._airport._tcp.local. 00 05 00 00 80 00 40
+ A: 9801A7E58FA1@Hristo's AirPort Express.local. NSEC IN FLUSH 4500 [9] 9801A7E58FA1@Hristo's AirPort Express._raop._tcp.local. 00 05 00 00 80 00 40
+
+Input: in/test-16.bin
+Packet Length: 254
+Answers: 1 + 1
+ A: Hristo's Time Capsule._airport._tcp.local. TXT IN FLUSH 4500 [168] waMA=70-73-CB-B4-C9-B3,raMA=70-73-CB-BB-04-E7,raM2=70-73-CB-BB-04-E8,raNm=nbis-test,raCh=11,rCh2=132,raSt=0,raNA=0,syFl=0x820C,syAP=116,syVs=7.6.8,srcv=76800.1,bjSd=30
+ A: Hristo's Time Capsule.local. NSEC IN FLUSH 4500 [9] Hristo's Time Capsule._airport._tcp.local. 00 05 00 00 80 00 40
+
+Input: in/test-28.bin
+Packet Length: 62
+Questions: 1
+ Q: Hristo's Time Capsule._afpovertcp._tcp.local. SRV IN FLUSH
+
+Input: in/test-29.bin
+Packet Length: 39
+Questions: 2
+ Q: minifritz.local. A IN FLUSH
+ Q: minifritz.local. AAAA IN FLUSH
+
+Input: in/test-31.bin
+Packet Length: 91
+Answers: 2 + 1
+ A: minifritz.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:142e:54ff:b8c4:fd09
+ A: minifritz.local. A IN FLUSH 120 [4] 192.168.254.16
+ A: minifritz.local. NSEC IN FLUSH 120 [8] minifritz...local. 00 04 40 00 00 08
+
+Input: in/test-53.bin
+Packet Length: 140
+Questions: 2
+ Q: _smb._tcp.local. PTR IN
+ Q: Sofiya-Ivanovas-MacBook.local. A IN
+Answers: 2 + 0
+ A: _smb._tcp.local. PTR IN 3061 [29] Sofiya Ivanova’s MacBook._smb._tcp.local.
+ A: _smb._tcp.local. PTR IN 3062 [24] Hristo's Time Capsule._smb._tcp.local.
+
+Input: in/test-56.bin
+Packet Length: 262
+Answers: 2 + 6
+ A: Hristo’s Mac mini._device-info._tcp.local. TXT IN 4500 [28] model=Macmini6,2; osxvers=16
+ A: _smb._tcp.local. PTR IN 4500 [22] Hristo’s Mac mini._smb._tcp.local.
+ A: Hristo’s Mac mini._smb._tcp.local. TXT IN FLUSH 4500 [1]
+ A: Hristo’s Mac mini._smb._tcp.local. SRV IN FLUSH 120 [18] 445 minifritz.local.
+ A: minifritz.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:142e:54ff:b8c4:fd09
+ A: minifritz.local. A IN FLUSH 120 [4] 192.168.254.16
+ A: Hristo’s Mac mini.local. NSEC IN FLUSH 4500 [9] Hristo’s Mac mini._smb._tcp.local. 00 05 00 00 80 00 40
+ A: minifritz.local. NSEC IN FLUSH 120 [8] minifritz...local. 00 04 40 00 00 08
+
+Input: in/test-63.bin
+Packet Length: 147
+Questions: 2
+ Q: _afpovertcp._tcp.local. PTR IN
+ Q: Sofiya-Ivanovas-MacBook.local. A IN
+Answers: 2 + 0
+ A: _afpovertcp._tcp.local. PTR IN 2881 [29] Sofiya Ivanova’s MacBook._afpovertcp._tcp.local.
+ A: _afpovertcp._tcp.local. PTR IN 2881 [24] Hristo's Time Capsule._afpovertcp._tcp.local.
+
+Input: in/test-66.bin
+Packet Length: 269
+Answers: 2 + 6
+ A: Hristo’s Mac mini._device-info._tcp.local. TXT IN 4500 [28] model=Macmini6,2; osxvers=16
+ A: _afpovertcp._tcp.local. PTR IN 4500 [22] Hristo’s Mac mini._afpovertcp._tcp.local.
+ A: Hristo’s Mac mini._afpovertcp._tcp.local. TXT IN FLUSH 4500 [1]
+ A: Hristo’s Mac mini._afpovertcp._tcp.local. SRV IN FLUSH 120 [18] 548 minifritz.local.
+ A: minifritz.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:142e:54ff:b8c4:fd09
+ A: minifritz.local. A IN FLUSH 120 [4] 192.168.254.16
+ A: Hristo’s Mac mini.local. NSEC IN FLUSH 4500 [9] Hristo’s Mac mini._afpovertcp._tcp.local. 00 05 00 00 80 00 40
+ A: minifritz.local. NSEC IN FLUSH 120 [8] minifritz...local. 00 04 40 00 00 08
+
+Input: in/test-83.bin
+Packet Length: 105
+Answers: 1 + 2
+ A: Sofiya-Ivanovas-MacBook.local. A IN FLUSH 120 [4] 192.168.254.20
+ A: Sofiya-Ivanovas-MacBook.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:021c:b3ff:feb2:72a3
+ A: Sofiya-Ivanovas-MacBook.local. NSEC IN FLUSH 120 [8] Sofiya-Ivanovas-MacBook...local. 00 04 40 00 00 08
+
+Input: in/test-88.bin
+Packet Length: 48
+Questions: 2
+ Q: _rfb._tcp.local. PTR IN
+ Q: _airport._tcp.local. PTR IN
+
+Input: in/test-89.bin
+Packet Length: 459
+Answers: 2 + 7
+ A: _airport._tcp.local. PTR IN 4500 [24] Hristo's Time Capsule._airport._tcp.local.
+ A: Hristo's Time Capsule._device-info._tcp.local. TXT IN 4500 [23] model=TimeCapsule6,116
+ A: Hristos-Time-Capsule.local. A IN FLUSH 120 [4] 192.168.254.49
+ A: Hristo's Time Capsule._airport._tcp.local. TXT IN FLUSH 4500 [168] waMA=70-73-CB-B4-C9-B3,raMA=70-73-CB-BB-04-E7,raM2=70-73-CB-BB-04-E8,raNm=nbis-test,raCh=11,rCh2=132,raSt=0,raNA=0,syFl=0x820C,syAP=116,syVs=7.6.8,srcv=76800.1,bjSd=30
+ A: Hristos-Time-Capsule.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:7273:cbff:feb4:c9b3
+ A: Hristo's Time Capsule._airport._tcp.local. SRV IN FLUSH 120 [8] 5009 Hristos-Time-Capsule.local.
+ A: Hristos-Time-Capsule.local. A IN FLUSH 120 [4] 169.254.23.40
+ A: Hristos-Time-Capsule.local. NSEC IN FLUSH 120 [8] Hristos-Time-Capsule...local. 00 04 40 00 00 08
+ A: Hristo's Time Capsule.local. NSEC IN FLUSH 4500 [9] Hristo's Time Capsule._airport._tcp.local. 00 05 00 00 80 00 40
+
+Input: in/test-91.bin
+Packet Length: 279
+Answers: 2 + 6
+ A: Sofiya Ivanova’s MacBook._device-info._tcp.local. TXT IN 4500 [17] model=Macmini2,1
+ A: _rfb._tcp.local. PTR IN 4500 [29] Sofiya Ivanova’s MacBook._rfb._tcp.local.
+ A: Sofiya Ivanova’s MacBook._rfb._tcp.local. TXT IN FLUSH 4500 [1]
+ A: Sofiya Ivanova’s MacBook._rfb._tcp.local. SRV IN FLUSH 120 [32] 5900 Sofiya-Ivanovas-MacBook.local.
+ A: Sofiya-Ivanovas-MacBook.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:021c:b3ff:feb2:72a3
+ A: Sofiya-Ivanovas-MacBook.local. A IN FLUSH 120 [4] 192.168.254.20
+ A: Sofiya Ivanova’s MacBook.local. NSEC IN FLUSH 4500 [9] Sofiya Ivanova’s MacBook._rfb._tcp.local. 00 05 00 00 80 00 40
+ A: Sofiya-Ivanovas-MacBook.local. NSEC IN FLUSH 120 [8] Sofiya-Ivanovas-MacBook...local. 00 04 40 00 00 08
+
+Input: in/test-95.bin
+Packet Length: 286
+Questions: 3
+ Q: _afpovertcp._tcp.local. PTR IN
+ Q: _smb._tcp.local. PTR IN
+ Q: _adisk._tcp.local. PTR IN
+Answers: 6 + 0
+ A: _afpovertcp._tcp.local. PTR IN 2353 [29] Sofiya Ivanova’s MacBook._afpovertcp._tcp.local.
+ A: _afpovertcp._tcp.local. PTR IN 3973 [22] Hristo’s Mac mini._afpovertcp._tcp.local.
+ A: _afpovertcp._tcp.local. PTR IN 2353 [24] Hristo's Time Capsule._afpovertcp._tcp.local.
+ A: _smb._tcp.local. PTR IN 2353 [29] Sofiya Ivanova’s MacBook._smb._tcp.local.
+ A: _smb._tcp.local. PTR IN 3792 [22] Hristo’s Mac mini._smb._tcp.local.
+ A: _smb._tcp.local. PTR IN 2353 [24] Hristo's Time Capsule._smb._tcp.local.
+
+Input: in/test-96.bin
+Packet Length: 319
+Answers: 2 + 3
+ A: Hristo's Time Capsule._device-info._tcp.local. TXT IN 4500 [23] model=TimeCapsule6,116
+ A: _adisk._tcp.local. PTR IN 4500 [24] Hristo's Time Capsule._adisk._tcp.local.
+ A: Hristo's Time Capsule._adisk._tcp.local. TXT IN FLUSH 4500 [110] sys=waMA=70:73:CB:B4:C9:B3,adVF=0x1000; dk2=adVF=0x1083,adVN=Capsule,adVU=55fabb8b-a63b-5441-9874-6edb504eb30a
+ A: Hristo's Time Capsule._adisk._tcp.local. SRV IN FLUSH 120 [29] 9 Hristos-Time-Capsule.local.
+ A: Hristo's Time Capsule.local. NSEC IN FLUSH 4500 [9] Hristo's Time Capsule._adisk._tcp.local. 00 05 00 00 80 00 40
diff --git a/components/mdns/tests/test_afl_fuzz_host/mdns_di.h b/components/mdns/tests/test_afl_fuzz_host/mdns_di.h
new file mode 100644
index 000000000..79dc00f33
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/mdns_di.h
@@ -0,0 +1,56 @@
+/*
+ * MDNS Dependecy injection -- preincluded to inject interface test functions into static variables
+ *
+ */
+
+#include "mdns.h"
+#include "mdns_private.h"
+
+void (*mdns_test_static_execute_action)(mdns_action_t *) = NULL;
+mdns_srv_item_t * (*mdns_test_static_mdns_get_service_item)(const char * service, const char * proto, const char *hostname) = NULL;
+mdns_search_once_t *(*mdns_test_static_search_init)(const char *name, const char *service, const char *proto, uint16_t type, bool unicast,
+ uint32_t timeout, uint8_t max_results,
+ mdns_query_notify_t notifier) = NULL;
+esp_err_t (*mdns_test_static_send_search_action)(mdns_action_type_t type, mdns_search_once_t * search) = NULL;
+void (*mdns_test_static_search_free)(mdns_search_once_t * search) = NULL;
+
+static void _mdns_execute_action(mdns_action_t * action);
+static mdns_srv_item_t * _mdns_get_service_item(const char * service, const char * proto, const char *hostname);
+static mdns_search_once_t *_mdns_search_init(const char *name, const char *service, const char *proto, uint16_t type, bool unicast,
+ uint32_t timeout, uint8_t max_results, mdns_query_notify_t notifier);
+static esp_err_t _mdns_send_search_action(mdns_action_type_t type, mdns_search_once_t * search);
+static void _mdns_search_free(mdns_search_once_t * search);
+
+void mdns_test_init_di(void)
+{
+ mdns_test_static_execute_action = _mdns_execute_action;
+ mdns_test_static_mdns_get_service_item = _mdns_get_service_item;
+ mdns_test_static_search_init = _mdns_search_init;
+ mdns_test_static_send_search_action = _mdns_send_search_action;
+ mdns_test_static_search_free = _mdns_search_free;
+}
+
+void mdns_test_execute_action(void * action)
+{
+ mdns_test_static_execute_action((mdns_action_t *)action);
+}
+
+void mdns_test_search_free(mdns_search_once_t * search)
+{
+ return mdns_test_static_search_free(search);
+}
+
+esp_err_t mdns_test_send_search_action(mdns_action_type_t type, mdns_search_once_t * search)
+{
+ return mdns_test_static_send_search_action(type, search);
+}
+
+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)
+{
+ return mdns_test_static_search_init(name, service, proto, type, timeout, type != MDNS_TYPE_PTR, max_results, NULL);
+}
+
+mdns_srv_item_t * mdns_test_mdns_get_service_item(const char * service, const char * proto)
+{
+ return mdns_test_static_mdns_get_service_item(service, proto, NULL);
+}
diff --git a/components/mdns/tests/test_afl_fuzz_host/mdns_mock.h b/components/mdns/tests/test_afl_fuzz_host/mdns_mock.h
new file mode 100644
index 000000000..1898de2c0
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/mdns_mock.h
@@ -0,0 +1,21 @@
+#pragma once
+#include "esp32_mock.h"
+#include "mdns.h"
+#include "mdns_private.h"
+
+
+static inline void* _mdns_get_packet_data(mdns_rx_packet_t *packet)
+{
+ return packet->pb->payload;
+}
+
+static inline size_t _mdns_get_packet_len(mdns_rx_packet_t *packet)
+{
+ return packet->pb->len;
+}
+
+static inline void _mdns_packet_free(mdns_rx_packet_t *packet)
+{
+ free(packet->pb);
+ free(packet);
+}
diff --git a/components/mdns/tests/test_afl_fuzz_host/sdkconfig.h b/components/mdns/tests/test_afl_fuzz_host/sdkconfig.h
new file mode 100644
index 000000000..bcb1b9d13
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/sdkconfig.h
@@ -0,0 +1,417 @@
+/*
+ * This config file commited in order to not run `idf.py reconfigure` each time when running fuzzer test. You can modify it manually or run `idf.py reconfigure` to generate new one if needed.
+ * Espressif IoT Development Framework (ESP-IDF) Configuration Header
+ */
+#pragma once
+#define CONFIG_IDF_TARGET "esp32"
+#define CONFIG_IDF_TARGET_ESP32 1
+#define CONFIG_IDF_FIRMWARE_CHIP_ID 0x0000
+#define CONFIG_SDK_TOOLPREFIX "xtensa-esp32-elf-"
+#define CONFIG_APP_BUILD_TYPE_APP_2NDBOOT 1
+#define CONFIG_APP_BUILD_GENERATE_BINARIES 1
+#define CONFIG_APP_BUILD_BOOTLOADER 1
+#define CONFIG_APP_BUILD_USE_FLASH_SECTIONS 1
+#define CONFIG_APP_COMPILE_TIME_DATE 1
+#define CONFIG_APP_RETRIEVE_LEN_ELF_SHA 16
+#define CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE 1
+#define CONFIG_BOOTLOADER_LOG_LEVEL_INFO 1
+#define CONFIG_BOOTLOADER_LOG_LEVEL 3
+#define CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V 1
+#define CONFIG_BOOTLOADER_WDT_ENABLE 1
+#define CONFIG_BOOTLOADER_WDT_TIME_MS 9000
+#define CONFIG_BOOTLOADER_RESERVE_RTC_SIZE 0x0
+#define CONFIG_ESPTOOLPY_WITH_STUB 1
+#define CONFIG_ESPTOOLPY_FLASHMODE_DIO 1
+#define CONFIG_ESPTOOLPY_FLASHMODE "dio"
+#define CONFIG_ESPTOOLPY_FLASHFREQ_40M 1
+#define CONFIG_ESPTOOLPY_FLASHFREQ "40m"
+#define CONFIG_ESPTOOLPY_FLASHSIZE_2MB 1
+#define CONFIG_ESPTOOLPY_FLASHSIZE "2MB"
+#define CONFIG_ESPTOOLPY_FLASHSIZE_DETECT 1
+#define CONFIG_ESPTOOLPY_BEFORE_RESET 1
+#define CONFIG_ESPTOOLPY_BEFORE "default_reset"
+#define CONFIG_ESPTOOLPY_AFTER_RESET 1
+#define CONFIG_ESPTOOLPY_AFTER "hard_reset"
+#define CONFIG_PARTITION_TABLE_SINGLE_APP 1
+#define CONFIG_PARTITION_TABLE_CUSTOM_FILENAME "partitions.csv"
+#define CONFIG_PARTITION_TABLE_FILENAME "partitions_singleapp.csv"
+#define CONFIG_PARTITION_TABLE_OFFSET 0x8000
+#define CONFIG_PARTITION_TABLE_MD5 1
+#define CONFIG_COMPILER_OPTIMIZATION_DEFAULT 1
+#define CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE 1
+#define CONFIG_COMPILER_STACK_CHECK_MODE_NONE 1
+#define CONFIG_APPTRACE_DEST_NONE 1
+#define CONFIG_APPTRACE_LOCK_ENABLE 1
+#define CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_EFF 0
+#define CONFIG_BTDM_CTRL_BLE_MAX_CONN_EFF 0
+#define CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN_EFF 0
+#define CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN_EFF 0
+#define CONFIG_BTDM_CTRL_PINNED_TO_CORE 0
+#define CONFIG_BTDM_BLE_SLEEP_CLOCK_ACCURACY_INDEX_EFF 1
+#define CONFIG_BTDM_RESERVE_DRAM 0x0
+#define CONFIG_COAP_MBEDTLS_PSK 1
+#define CONFIG_COAP_LOG_DEFAULT_LEVEL 0
+#define CONFIG_ADC_DISABLE_DAC 1
+#define CONFIG_SPI_MASTER_ISR_IN_IRAM 1
+#define CONFIG_SPI_SLAVE_ISR_IN_IRAM 1
+#define CONFIG_EFUSE_CODE_SCHEME_COMPAT_3_4 1
+#define CONFIG_EFUSE_MAX_BLK_LEN 192
+#define CONFIG_ESP_TLS_USING_MBEDTLS 1
+#define CONFIG_ESP32_REV_MIN_0 1
+#define CONFIG_ESP32_REV_MIN 0
+#define CONFIG_ESP32_DPORT_WORKAROUND 1
+#define CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160 1
+#define CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ 160
+#define CONFIG_ESP32_TRACEMEM_RESERVE_DRAM 0x0
+#define CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR 1
+#define CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES 4
+#define CONFIG_ULP_COPROC_RESERVE_MEM 0
+#define CONFIG_ESP_DEBUG_OCDAWARE 1
+#define CONFIG_ESP_BROWNOUT_DET 1
+#define CONFIG_ESP_BROWNOUT_DET_LVL_SEL_0 1
+#define CONFIG_ESP_BROWNOUT_DET_LVL 0
+#define CONFIG_ESP32_REDUCE_PHY_TX_POWER 1
+#define CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT 1
+#define CONFIG_RTC_CLK_SRC_INT_RC 1
+#define CONFIG_RTC_CLK_CAL_CYCLES 1024
+#define CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY 2000
+#define CONFIG_ESP32_XTAL_FREQ_40 1
+#define CONFIG_ESP32_XTAL_FREQ 40
+#define CONFIG_ESP32_DPORT_DIS_INTERRUPT_LVL 5
+#define CONFIG_ADC_CAL_EFUSE_TP_ENABLE 1
+#define CONFIG_ADC_CAL_EFUSE_VREF_ENABLE 1
+#define CONFIG_ADC_CAL_LUT_ENABLE 1
+#define CONFIG_ESP_ERR_TO_NAME_LOOKUP 1
+#define CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE 32
+#define CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE 2304
+#define CONFIG_ESP_MAIN_TASK_STACK_SIZE 3584
+#define CONFIG_ESP_IPC_TASK_STACK_SIZE 1024
+#define CONFIG_ESP_IPC_USES_CALLERS_PRIORITY 1
+#define CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE 2048
+#define CONFIG_ESP_CONSOLE_UART_DEFAULT 1
+#define CONFIG_ESP_CONSOLE_UART_NUM 0
+#define CONFIG_ESP_CONSOLE_UART_TX_GPIO 1
+#define CONFIG_ESP_CONSOLE_UART_RX_GPIO 3
+#define CONFIG_ESP_CONSOLE_UART_BAUDRATE 115200
+#define CONFIG_ESP_INT_WDT 1
+#define CONFIG_ESP_INT_WDT_TIMEOUT_MS 300
+#define CONFIG_ESP_INT_WDT_CHECK_CPU1 1
+#define CONFIG_ESP_TASK_WDT 1
+#define CONFIG_ESP_TASK_WDT_TIMEOUT_S 5
+#define CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 1
+#define CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 1
+#define CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA 1
+#define CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP 1
+#define CONFIG_ESP_MAC_ADDR_UNIVERSE_BT 1
+#define CONFIG_ESP_MAC_ADDR_UNIVERSE_BT_OFFSET 2
+#define CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH 1
+#define CONFIG_ETH_ENABLED 1
+#define CONFIG_ETH_USE_ESP32_EMAC 1
+#define CONFIG_ETH_PHY_INTERFACE_RMII 1
+#define CONFIG_ETH_RMII_CLK_INPUT 1
+#define CONFIG_ETH_RMII_CLK_IN_GPIO 0
+#define CONFIG_ETH_DMA_BUFFER_SIZE 512
+#define CONFIG_ETH_DMA_RX_BUFFER_NUM 10
+#define CONFIG_ETH_DMA_TX_BUFFER_NUM 10
+#define CONFIG_ETH_USE_SPI_ETHERNET 1
+#define CONFIG_ESP_EVENT_POST_FROM_ISR 1
+#define CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR 1
+#define CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS 1
+#define CONFIG_HTTPD_MAX_REQ_HDR_LEN 512
+#define CONFIG_HTTPD_MAX_URI_LEN 512
+#define CONFIG_HTTPD_ERR_RESP_NO_DELAY 1
+#define CONFIG_HTTPD_PURGE_BUF_LEN 32
+#define CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL 120
+#define CONFIG_ESP_NETIF_TCPIP_LWIP 1
+#define CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT 1
+#define CONFIG_ESP_TIMER_TASK_STACK_SIZE 3584
+#define CONFIG_ESP_TIMER_IMPL_TG0_LAC 1
+#define CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM 10
+#define CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM 32
+#define CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER 1
+#define CONFIG_ESP32_WIFI_TX_BUFFER_TYPE 1
+#define CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM 32
+#define CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED 1
+#define CONFIG_ESP32_WIFI_TX_BA_WIN 6
+#define CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED 1
+#define CONFIG_ESP32_WIFI_RX_BA_WIN 6
+#define CONFIG_ESP32_WIFI_NVS_ENABLED 1
+#define CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0 1
+#define CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN 752
+#define CONFIG_ESP32_WIFI_MGMT_SBUF_NUM 32
+#define CONFIG_ESP32_WIFI_IRAM_OPT 1
+#define CONFIG_ESP32_WIFI_RX_IRAM_OPT 1
+#define CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE 1
+#define CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE 1
+#define CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER 20
+#define CONFIG_ESP32_PHY_MAX_TX_POWER 20
+#define CONFIG_ESP_COREDUMP_ENABLE_TO_NONE 1
+#define CONFIG_FATFS_CODEPAGE_437 1
+#define CONFIG_FATFS_CODEPAGE 437
+#define CONFIG_FATFS_LFN_NONE 1
+#define CONFIG_FATFS_FS_LOCK 0
+#define CONFIG_FATFS_TIMEOUT_MS 10000
+#define CONFIG_FATFS_PER_FILE_CACHE 1
+#define CONFIG_FMB_COMM_MODE_RTU_EN 1
+#define CONFIG_FMB_COMM_MODE_ASCII_EN 1
+#define CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND 150
+#define CONFIG_FMB_MASTER_DELAY_MS_CONVERT 200
+#define CONFIG_FMB_QUEUE_LENGTH 20
+#define CONFIG_FMB_SERIAL_TASK_STACK_SIZE 2048
+#define CONFIG_FMB_SERIAL_BUF_SIZE 256
+#define CONFIG_FMB_SERIAL_ASCII_BITS_PER_SYMB 8
+#define CONFIG_FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS 1000
+#define CONFIG_FMB_SERIAL_TASK_PRIO 10
+#define CONFIG_FMB_CONTROLLER_NOTIFY_TIMEOUT 20
+#define CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE 20
+#define CONFIG_FMB_CONTROLLER_STACK_SIZE 4096
+#define CONFIG_FMB_EVENT_QUEUE_TIMEOUT 20
+#define CONFIG_FMB_TIMER_PORT_ENABLED 1
+#define CONFIG_FMB_TIMER_GROUP 0
+#define CONFIG_FMB_TIMER_INDEX 0
+#define CONFIG_FREERTOS_NO_AFFINITY 0x7FFFFFFF
+#define CONFIG_FREERTOS_CORETIMER_0 1
+#define CONFIG_FREERTOS_HZ 100
+#define CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION 1
+#define CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY 1
+#define CONFIG_FREERTOS_INTERRUPT_BACKTRACE 1
+#define CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS 1
+#define CONFIG_FREERTOS_IDLE_TASK_STACKSIZE 1536
+#define CONFIG_FREERTOS_ISR_STACKSIZE 1536
+#define CONFIG_FREERTOS_MAX_TASK_NAME_LEN 16
+#define CONFIG_FREERTOS_TIMER_TASK_PRIORITY 1
+#define CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH 2048
+#define CONFIG_FREERTOS_TIMER_QUEUE_LENGTH 10
+#define CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE 0
+#define CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER 1
+#define CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER 1
+#define CONFIG_FREERTOS_DEBUG_OCDAWARE 1
+#define CONFIG_HEAP_POISONING_DISABLED 1
+#define CONFIG_HEAP_TRACING_OFF 1
+#define CONFIG_LOG_DEFAULT_LEVEL_INFO 1
+#define CONFIG_LOG_DEFAULT_LEVEL 3
+#define CONFIG_LOG_COLORS 1
+#define CONFIG_LOG_TIMESTAMP_SOURCE_RTOS 1
+#define CONFIG_LWIP_LOCAL_HOSTNAME "espressif"
+#define CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES 1
+#define CONFIG_LWIP_TIMERS_ONDEMAND 1
+#define CONFIG_LWIP_MAX_SOCKETS 10
+#define CONFIG_LWIP_SO_REUSE 1
+#define CONFIG_LWIP_SO_REUSE_RXTOALL 1
+#define CONFIG_LWIP_IP_FRAG 1
+#define CONFIG_LWIP_ESP_GRATUITOUS_ARP 1
+#define CONFIG_LWIP_GARP_TMR_INTERVAL 60
+#define CONFIG_LWIP_TCPIP_RECVMBOX_SIZE 32
+#define CONFIG_LWIP_DHCP_DOES_ARP_CHECK 1
+#define CONFIG_LWIP_DHCPS_LEASE_UNIT 60
+#define CONFIG_LWIP_DHCPS_MAX_STATION_NUM 8
+#define CONFIG_LWIP_NETIF_LOOPBACK 1
+#define CONFIG_LWIP_LOOPBACK_MAX_PBUFS 8
+#define CONFIG_LWIP_MAX_ACTIVE_TCP 16
+#define CONFIG_LWIP_MAX_LISTENING_TCP 16
+#define CONFIG_LWIP_TCP_MAXRTX 12
+#define CONFIG_LWIP_TCP_SYNMAXRTX 6
+#define CONFIG_LWIP_TCP_MSS 1440
+#define CONFIG_LWIP_TCP_TMR_INTERVAL 250
+#define CONFIG_LWIP_TCP_MSL 60000
+#define CONFIG_LWIP_TCP_SND_BUF_DEFAULT 5744
+#define CONFIG_LWIP_TCP_WND_DEFAULT 5744
+#define CONFIG_LWIP_TCP_RECVMBOX_SIZE 6
+#define CONFIG_LWIP_TCP_QUEUE_OOSEQ 1
+#define CONFIG_LWIP_TCP_OVERSIZE_MSS 1
+#define CONFIG_LWIP_MAX_UDP_PCBS 16
+#define CONFIG_LWIP_UDP_RECVMBOX_SIZE 6
+#define CONFIG_LWIP_TCPIP_TASK_STACK_SIZE 3072
+#define CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY 1
+#define CONFIG_LWIP_TCPIP_TASK_AFFINITY 0x7FFFFFFF
+#define CONFIG_LWIP_MAX_RAW_PCBS 16
+#define CONFIG_LWIP_DHCP_MAX_NTP_SERVERS 1
+#define CONFIG_LWIP_SNTP_UPDATE_DELAY 3600000
+#define CONFIG_LWIP_ESP_LWIP_ASSERT 1
+#define CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC 1
+#define CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN 1
+#define CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN 16384
+#define CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN 4096
+#define CONFIG_MBEDTLS_CERTIFICATE_BUNDLE 1
+#define CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL 1
+#define CONFIG_MBEDTLS_HARDWARE_AES 1
+#define CONFIG_MBEDTLS_HARDWARE_MPI 1
+#define CONFIG_MBEDTLS_HARDWARE_SHA 1
+#define CONFIG_MBEDTLS_HAVE_TIME 1
+#define CONFIG_MBEDTLS_ECDSA_DETERMINISTIC 1
+#define CONFIG_MBEDTLS_SHA512_C 1
+#define CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT 1
+#define CONFIG_MBEDTLS_TLS_SERVER 1
+#define CONFIG_MBEDTLS_TLS_CLIENT 1
+#define CONFIG_MBEDTLS_TLS_ENABLED 1
+#define CONFIG_MBEDTLS_KEY_EXCHANGE_RSA 1
+#define CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA 1
+#define CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE 1
+#define CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA 1
+#define CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA 1
+#define CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA 1
+#define CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA 1
+#define CONFIG_MBEDTLS_SSL_RENEGOTIATION 1
+#define CONFIG_MBEDTLS_SSL_PROTO_TLS1 1
+#define CONFIG_MBEDTLS_SSL_PROTO_TLS1_1 1
+#define CONFIG_MBEDTLS_SSL_PROTO_TLS1_2 1
+#define CONFIG_MBEDTLS_SSL_ALPN 1
+#define CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS 1
+#define CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS 1
+#define CONFIG_MBEDTLS_AES_C 1
+#define CONFIG_MBEDTLS_RC4_DISABLED 1
+#define CONFIG_MBEDTLS_CCM_C 1
+#define CONFIG_MBEDTLS_GCM_C 1
+#define CONFIG_MBEDTLS_PEM_PARSE_C 1
+#define CONFIG_MBEDTLS_PEM_WRITE_C 1
+#define CONFIG_MBEDTLS_X509_CRL_PARSE_C 1
+#define CONFIG_MBEDTLS_X509_CSR_PARSE_C 1
+#define CONFIG_MBEDTLS_ECP_C 1
+#define CONFIG_MBEDTLS_ECDH_C 1
+#define CONFIG_MBEDTLS_ECDSA_C 1
+#define CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED 1
+#define CONFIG_MBEDTLS_ECP_NIST_OPTIM 1
+#define CONFIG_MDNS_MAX_SERVICES 25
+#define CONFIG_MDNS_MAX_INTERFACES 3
+#define CONFIG_MDNS_TASK_PRIORITY 1
+#define CONFIG_MDNS_TASK_STACK_SIZE 4096
+#define CONFIG_MDNS_TASK_AFFINITY_CPU0 1
+#define CONFIG_MDNS_TASK_AFFINITY 0x0
+#define CONFIG_MDNS_SERVICE_ADD_TIMEOUT_MS 1
+#define CONFIG_MDNS_TIMER_PERIOD_MS 100
+#define CONFIG_MQTT_PROTOCOL_311 1
+#define CONFIG_MQTT_TRANSPORT_SSL 1
+#define CONFIG_MQTT_TRANSPORT_WEBSOCKET 1
+#define CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE 1
+#define CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF 1
+#define CONFIG_NEWLIB_STDIN_LINE_ENDING_CR 1
+#define CONFIG_OPENSSL_ASSERT_EXIT 1
+#define CONFIG_PTHREAD_TASK_PRIO_DEFAULT 5
+#define CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT 3072
+#define CONFIG_PTHREAD_STACK_MIN 768
+#define CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY 1
+#define CONFIG_PTHREAD_TASK_CORE_DEFAULT -1
+#define CONFIG_PTHREAD_TASK_NAME_DEFAULT "pthread"
+#define CONFIG_SPI_FLASH_ROM_DRIVER_PATCH 1
+#define CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS 1
+#define CONFIG_SPI_FLASH_YIELD_DURING_ERASE 1
+#define CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS 20
+#define CONFIG_SPI_FLASH_ERASE_YIELD_TICKS 1
+#define CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP 1
+#define CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP 1
+#define CONFIG_SPI_FLASH_SUPPORT_GD_CHIP 1
+#define CONFIG_SPIFFS_MAX_PARTITIONS 3
+#define CONFIG_SPIFFS_CACHE 1
+#define CONFIG_SPIFFS_CACHE_WR 1
+#define CONFIG_SPIFFS_PAGE_CHECK 1
+#define CONFIG_SPIFFS_GC_MAX_RUNS 10
+#define CONFIG_SPIFFS_PAGE_SIZE 256
+#define CONFIG_SPIFFS_OBJ_NAME_LEN 32
+#define CONFIG_SPIFFS_USE_MAGIC 1
+#define CONFIG_SPIFFS_USE_MAGIC_LENGTH 1
+#define CONFIG_SPIFFS_META_LENGTH 4
+#define CONFIG_SPIFFS_USE_MTIME 1
+#define CONFIG_USB_DESC_CUSTOM_VID 0x1234
+#define CONFIG_USB_DESC_CUSTOM_PID 0x5678
+#define CONFIG_UNITY_ENABLE_FLOAT 1
+#define CONFIG_UNITY_ENABLE_DOUBLE 1
+#define CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER 1
+#define CONFIG_VFS_SUPPORT_IO 1
+#define CONFIG_VFS_SUPPORT_DIR 1
+#define CONFIG_VFS_SUPPORT_SELECT 1
+#define CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT 1
+#define CONFIG_VFS_SUPPORT_TERMIOS 1
+#define CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS 1
+#define CONFIG_VFS_SEMIHOSTFS_HOST_PATH_MAX_LEN 128
+#define CONFIG_WL_SECTOR_SIZE_4096 1
+#define CONFIG_WL_SECTOR_SIZE 4096
+#define CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES 16
+#define CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT 30
+#define CONFIG_WPA_MBEDTLS_CRYPTO 1
+
+/* List of deprecated options */
+#define CONFIG_ADC2_DISABLE_DAC CONFIG_ADC_DISABLE_DAC
+#define CONFIG_BROWNOUT_DET CONFIG_ESP_BROWNOUT_DET
+#define CONFIG_BROWNOUT_DET_LVL_SEL_0 CONFIG_ESP_BROWNOUT_DET_LVL_SEL_0
+#define CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG CONFIG_COMPILER_OPTIMIZATION_DEFAULT
+#define CONFIG_CONSOLE_UART_BAUDRATE CONFIG_ESP_CONSOLE_UART_BAUDRATE
+#define CONFIG_CONSOLE_UART_DEFAULT CONFIG_ESP_CONSOLE_UART_DEFAULT
+#define CONFIG_CONSOLE_UART_RX_GPIO CONFIG_ESP_CONSOLE_UART_RX_GPIO
+#define CONFIG_CONSOLE_UART_TX_GPIO CONFIG_ESP_CONSOLE_UART_TX_GPIO
+#define CONFIG_ESP32S2_PANIC_PRINT_REBOOT CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT
+#define CONFIG_ESP32_APPTRACE_DEST_NONE CONFIG_APPTRACE_DEST_NONE
+#define CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY
+#define CONFIG_ESP32_PANIC_PRINT_REBOOT CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT
+#define CONFIG_ESP32_PTHREAD_STACK_MIN CONFIG_PTHREAD_STACK_MIN
+#define CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT CONFIG_PTHREAD_TASK_NAME_DEFAULT
+#define CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT CONFIG_PTHREAD_TASK_PRIO_DEFAULT
+#define CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT
+#define CONFIG_ESP_GRATUITOUS_ARP CONFIG_LWIP_ESP_GRATUITOUS_ARP
+#define CONFIG_FLASHMODE_DIO CONFIG_ESPTOOLPY_FLASHMODE_DIO
+#define CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR
+#define CONFIG_GARP_TMR_INTERVAL CONFIG_LWIP_GARP_TMR_INTERVAL
+#define CONFIG_INT_WDT CONFIG_ESP_INT_WDT
+#define CONFIG_INT_WDT_CHECK_CPU1 CONFIG_ESP_INT_WDT_CHECK_CPU1
+#define CONFIG_INT_WDT_TIMEOUT_MS CONFIG_ESP_INT_WDT_TIMEOUT_MS
+#define CONFIG_IPC_TASK_STACK_SIZE CONFIG_ESP_IPC_TASK_STACK_SIZE
+#define CONFIG_LOG_BOOTLOADER_LEVEL_INFO CONFIG_BOOTLOADER_LOG_LEVEL_INFO
+#define CONFIG_MAIN_TASK_STACK_SIZE CONFIG_ESP_MAIN_TASK_STACK_SIZE
+#define CONFIG_MB_CONTROLLER_NOTIFY_QUEUE_SIZE CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE
+#define CONFIG_MB_CONTROLLER_NOTIFY_TIMEOUT CONFIG_FMB_CONTROLLER_NOTIFY_TIMEOUT
+#define CONFIG_MB_CONTROLLER_STACK_SIZE CONFIG_FMB_CONTROLLER_STACK_SIZE
+#define CONFIG_MB_EVENT_QUEUE_TIMEOUT CONFIG_FMB_EVENT_QUEUE_TIMEOUT
+#define CONFIG_MB_MASTER_DELAY_MS_CONVERT CONFIG_FMB_MASTER_DELAY_MS_CONVERT
+#define CONFIG_MB_MASTER_TIMEOUT_MS_RESPOND CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND
+#define CONFIG_MB_QUEUE_LENGTH CONFIG_FMB_QUEUE_LENGTH
+#define CONFIG_MB_SERIAL_BUF_SIZE CONFIG_FMB_SERIAL_BUF_SIZE
+#define CONFIG_MB_SERIAL_TASK_PRIO CONFIG_FMB_SERIAL_TASK_PRIO
+#define CONFIG_MB_SERIAL_TASK_STACK_SIZE CONFIG_FMB_SERIAL_TASK_STACK_SIZE
+#define CONFIG_MB_TIMER_GROUP CONFIG_FMB_TIMER_GROUP
+#define CONFIG_MB_TIMER_INDEX CONFIG_FMB_TIMER_INDEX
+#define CONFIG_MB_TIMER_PORT_ENABLED CONFIG_FMB_TIMER_PORT_ENABLED
+#define CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE
+#define CONFIG_OPTIMIZATION_LEVEL_DEBUG CONFIG_COMPILER_OPTIMIZATION_DEFAULT
+#define CONFIG_POST_EVENTS_FROM_IRAM_ISR CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR
+#define CONFIG_POST_EVENTS_FROM_ISR CONFIG_ESP_EVENT_POST_FROM_ISR
+#define CONFIG_REDUCE_PHY_TX_POWER CONFIG_ESP32_REDUCE_PHY_TX_POWER
+#define CONFIG_SEMIHOSTFS_HOST_PATH_MAX_LEN CONFIG_VFS_SEMIHOSTFS_HOST_PATH_MAX_LEN
+#define CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS
+#define CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS
+#define CONFIG_STACK_CHECK_NONE CONFIG_COMPILER_STACK_CHECK_MODE_NONE
+#define CONFIG_SUPPORT_TERMIOS CONFIG_VFS_SUPPORT_TERMIOS
+#define CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT
+#define CONFIG_SYSTEM_EVENT_QUEUE_SIZE CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE
+#define CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE
+#define CONFIG_TASK_WDT CONFIG_ESP_TASK_WDT
+#define CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
+#define CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
+#define CONFIG_TASK_WDT_TIMEOUT_S CONFIG_ESP_TASK_WDT_TIMEOUT_S
+#define CONFIG_TCPIP_RECVMBOX_SIZE CONFIG_LWIP_TCPIP_RECVMBOX_SIZE
+#define CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY
+#define CONFIG_TCPIP_TASK_STACK_SIZE CONFIG_LWIP_TCPIP_TASK_STACK_SIZE
+#define CONFIG_TCP_MAXRTX CONFIG_LWIP_TCP_MAXRTX
+#define CONFIG_TCP_MSL CONFIG_LWIP_TCP_MSL
+#define CONFIG_TCP_MSS CONFIG_LWIP_TCP_MSS
+#define CONFIG_TCP_OVERSIZE_MSS CONFIG_LWIP_TCP_OVERSIZE_MSS
+#define CONFIG_TCP_QUEUE_OOSEQ CONFIG_LWIP_TCP_QUEUE_OOSEQ
+#define CONFIG_TCP_RECVMBOX_SIZE CONFIG_LWIP_TCP_RECVMBOX_SIZE
+#define CONFIG_TCP_SND_BUF_DEFAULT CONFIG_LWIP_TCP_SND_BUF_DEFAULT
+#define CONFIG_TCP_SYNMAXRTX CONFIG_LWIP_TCP_SYNMAXRTX
+#define CONFIG_TCP_WND_DEFAULT CONFIG_LWIP_TCP_WND_DEFAULT
+#define CONFIG_TIMER_QUEUE_LENGTH CONFIG_FREERTOS_TIMER_QUEUE_LENGTH
+#define CONFIG_TIMER_TASK_PRIORITY CONFIG_FREERTOS_TIMER_TASK_PRIORITY
+#define CONFIG_TIMER_TASK_STACK_DEPTH CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH
+#define CONFIG_TIMER_TASK_STACK_SIZE CONFIG_ESP_TIMER_TASK_STACK_SIZE
+#define CONFIG_TOOLPREFIX CONFIG_SDK_TOOLPREFIX
+#define CONFIG_UDP_RECVMBOX_SIZE CONFIG_LWIP_UDP_RECVMBOX_SIZE
diff --git a/components/mdns/tests/test_afl_fuzz_host/test.c b/components/mdns/tests/test_afl_fuzz_host/test.c
new file mode 100644
index 000000000..568c7eee1
--- /dev/null
+++ b/components/mdns/tests/test_afl_fuzz_host/test.c
@@ -0,0 +1,280 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include "esp32_mock.h"
+#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(void);
+extern mdns_server_t * _mdns_server;
+
+//
+// mdns function wrappers for mdns setup in test mode
+static int mdns_test_hostname_set(const char * mdns_hostname)
+{
+ for (int i=0; iinterfaces[i].pcbs[MDNS_IP_PROTOCOL_V4].state = PCB_RUNNING; // mark the PCB running to exercise mdns in fully operational mode
+ _mdns_server->interfaces[i].pcbs[MDNS_IP_PROTOCOL_V6].state = PCB_RUNNING;
+ }
+ 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_add_delegated_host(const char * mdns_hostname)
+{
+ mdns_ip_addr_t addr = { .addr = { .u_addr = ESP_IPADDR_TYPE_V4 } };
+ addr.addr.u_addr.ip4.addr = 0x11111111;
+ int ret = mdns_delegate_hostname_add(mdns_hostname, &addr);
+ 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_sub_service_add(const char * sub_name, 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;
+ }
+ int ret = mdns_service_subtype_add_for_host(NULL, service_name, proto, NULL, sub_name);
+ 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 * name, const char * service, const char * proto, uint16_t type)
+{
+ search = mdns_test_search_init(name, service, proto, type, 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(void)
+{
+ 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_add_delegated_host(mdns_hostname) || mdns_test_add_delegated_host("megafritz")) {
+ abort();
+ }
+
+#ifndef MDNS_NO_SERVICES
+
+ if (mdns_test_sub_service_add("_server", "_fritz", "_tcp", 22)) {
+ abort();
+ }
+
+ if (mdns_test_service_add("_telnet", "_tcp", 22)) {
+ 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();
+ }
+#endif
+ 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");
+ assert(file >= 0 );
+ 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 = malloc(len);
+ memcpy(mypbuf.payload, buf, len);
+ mypbuf.len = len;
+ g_packet.pb = &mypbuf;
+ mdns_test_query("minifritz", "_fritz", "_tcp", MDNS_TYPE_ANY);
+ mdns_test_query(NULL, "_fritz", "_tcp", MDNS_TYPE_PTR);
+ mdns_test_query(NULL, "_afpovertcp", "_tcp", MDNS_TYPE_PTR);
+ mdns_parse_packet(&g_packet);
+ free(mypbuf.payload);
+ }
+#ifndef MDNS_NO_SERVICES
+ mdns_service_remove_all();
+ mdns_action_t *a = NULL;
+ GetLastItem(&a);
+ mdns_test_execute_action(a);
+#endif
+ ForceTaskDelete();
+ mdns_free();
+ return 0;
+}
diff --git a/components/mdns/tests/test_apps/CMakeLists.txt b/components/mdns/tests/test_apps/CMakeLists.txt
new file mode 100644
index 000000000..466df27ef
--- /dev/null
+++ b/components/mdns/tests/test_apps/CMakeLists.txt
@@ -0,0 +1,11 @@
+# The following four lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+# (Not part of the boilerplate)
+# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
+set(EXTRA_COMPONENT_DIRS "../../../../common_components/protocol_examples_common")
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+
+project(mdns)
diff --git a/components/mdns/tests/test_apps/README.md b/components/mdns/tests/test_apps/README.md
new file mode 100644
index 000000000..96b76dcb7
--- /dev/null
+++ b/components/mdns/tests/test_apps/README.md
@@ -0,0 +1,49 @@
+| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 |
+| ----------------- | ----- | -------- | -------- |
+
+# mDNS test project
+
+Main purpose of this application is to test the mDNS library to correctly advertise, lookup services and hosts.
+The "app_test.py" logically separated from two sets of test cases 1. "ESP32 board sends queries -> Host sends answers" and 2. "Host sends queries" -> "ESP32 board sends answers".
+Two first sets of test cases are starting by 'test_query_' and the second ones by 'start_case'.
+
+## Runtime settings
+
+1. For first sets of test cases python creates and sends dns queries using "dpkt" library
+2. For the second sets of test cases this app waits for user input to provide test case(e.g. CONFIG_TEST_QUERY_HOST, CONFIG_TEST_QUERY_HOST_ASYNC or CONFIG_TEST_QUERY_SERVICE)
+In order to run both of them just needed to set up the project and run by 'python app_test.py'
+
+## Test app workflow
+
+- mDNS is initialized with hostname and instance name defined through the project configuration and `_http._tcp` service is added to be advertised
+- A delegated host `esp32-delegated._local` is added and another `_http._tcp` service is added for this host.
+- WiFi STA is started and tries to connect to the access point defined through the project configuration
+
+### Configure the project
+
+* Open the project configuration menu (`idf.py menuconfig`)
+
+* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu.
+
+* Set `mDNS Hostname` as host name prefix for the device and its instance name in `mDNS Instance Name`
+
+### Build and Flash
+
+Build the project and flash it to the board, then run the monitor tool to view the serial output:
+
+```
+idf.py -p PORT flash monitor
+```
+
+- Wait for WiFi to connect to your access point
+- You can now ping the device at `[board-hostname].local`, where `[board-hostname]` is preconfigured hostname, `esp32-mdns` by default.
+- You can also browse for `_http._tcp` on the same network to find the advertised service
+- Note that for purpose of CI tests, configuration options of `MDNS_RESOLVE_TEST_SERVICES` and `MDNS_ADD_MAC_TO_HOSTNAME` are available, but disabled by default. If enabled, then the hostname suffix of last 3 bytes from device MAC address is added, e.g. `esp32-mdns-80FFFF`, and a query for test service is issued.
+
+
+(To exit the serial monitor, type ``Ctrl-]``.)
+
+See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
+
+## Hardware Required
+This test-app can be executed on any ESP32 board, the only required interface is WiFi and connection to a local network and tls server.
diff --git a/components/mdns/tests/test_apps/app_test.py b/components/mdns/tests/test_apps/app_test.py
new file mode 100644
index 000000000..efd98feda
--- /dev/null
+++ b/components/mdns/tests/test_apps/app_test.py
@@ -0,0 +1,290 @@
+# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Apache-2.0
+
+import re
+import select
+import socket
+import struct
+import time
+from threading import Event, Thread
+
+import dpkt
+import dpkt.dns
+import ttfw_idf
+from tiny_test_fw.Utility import console_log
+
+UDP_PORT = 5353
+MCAST_GRP = '224.0.0.251'
+
+# This service is created from esp board startup
+SERVICE_NAME = u'ESP32-WebServer._http._tcp.local'
+SUB_SERVICE_NAME = u'_server._sub._http._tcp.local'
+
+# This host name answer sent by host, when there is query from board
+HOST_NAME = u'tinytester.local'
+
+# This servce answer sent by host, when there is query from board
+MDNS_HOST_SERVICE = u'ESP32._http._tcp.local'
+
+# Number of retries to receive mdns answear
+RETRY_COUNT = 10
+
+stop_mdns_listener = Event()
+start_mdns_listener = Event()
+esp_service_answered = Event()
+esp_sub_service_answered = Event()
+esp_host_answered = Event()
+esp_delegated_host_answered = Event()
+
+
+# Get query of ESP32-WebServer._http._tcp.local service
+def get_mdns_service_query(service): # type:(str) -> dpkt.dns.Msg
+ dns = dpkt.dns.DNS()
+ dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
+ dns.rcode = dpkt.dns.DNS_RCODE_NOERR
+ arr = dpkt.dns.DNS.RR()
+ arr.cls = dpkt.dns.DNS_IN
+ arr.type = dpkt.dns.DNS_SRV
+ arr.name = service
+ arr.target = socket.inet_aton('127.0.0.1')
+ arr.srvname = service
+ dns.qd.append(arr)
+ console_log('Created mdns service query: {} '.format(dns.__repr__()))
+ return dns.pack()
+
+
+# Get query of _server_.sub._http._tcp.local sub service
+def get_mdns_sub_service_query(sub_service): # type:(str) -> dpkt.dns.Msg
+ dns = dpkt.dns.DNS()
+ dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
+ dns.rcode = dpkt.dns.DNS_RCODE_NOERR
+ arr = dpkt.dns.DNS.RR()
+ arr.cls = dpkt.dns.DNS_IN
+ arr.type = dpkt.dns.DNS_PTR
+ arr.name = sub_service
+ arr.target = socket.inet_aton('127.0.0.1')
+ arr.ptrname = sub_service
+ dns.qd.append(arr)
+ console_log('Created mdns subtype service query: {} '.format(dns.__repr__()))
+ return dns.pack()
+
+
+# Get query for host resolution
+def get_dns_query_for_esp(esp_host): # type:(str) -> dpkt.dns.Msg
+ dns = dpkt.dns.DNS()
+ arr = dpkt.dns.DNS.RR()
+ arr.cls = dpkt.dns.DNS_IN
+ arr.name = esp_host + u'.local'
+ dns.qd.append(arr)
+ console_log('Created query for esp host: {} '.format(dns.__repr__()))
+ return dns.pack()
+
+
+# Get mdns answer for host resoloution
+def get_dns_answer_to_mdns(tester_host): # type:(str) -> dpkt.dns.Msg
+ dns = dpkt.dns.DNS()
+ dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
+ dns.rcode = dpkt.dns.DNS_RCODE_NOERR
+ arr = dpkt.dns.DNS.RR()
+ arr.cls = dpkt.dns.DNS_IN
+ arr.type = dpkt.dns.DNS_A
+ arr.name = tester_host
+ arr.ip = socket.inet_aton('127.0.0.1')
+ dns. an.append(arr)
+ console_log('Created answer to mdns query: {} '.format(dns.__repr__()))
+ return dns.pack()
+
+
+# Get mdns answer for service query
+def get_dns_answer_to_service_query(mdns_service): # type:(str) -> dpkt.dns.Msg
+ dns = dpkt.dns.DNS()
+ dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
+ dns.rcode = dpkt.dns.DNS_RCODE_NOERR
+ arr = dpkt.dns.DNS.RR()
+ arr.name = mdns_service
+ arr.cls = dpkt.dns.DNS_IN
+ arr.type = dpkt.dns.DNS_SRV
+ arr.priority = 0
+ arr.weight = 0
+ arr.port = 100
+ arr.srvname = mdns_service
+ arr.ip = socket.inet_aton('127.0.0.1')
+ dns. an.append(arr)
+ console_log('Created answer to mdns query: {} '.format(dns.__repr__()))
+ return dns.pack()
+
+
+def mdns_listener(esp_host): # type:(str) -> None
+ print('mdns_listener thread started')
+
+ UDP_IP = '0.0.0.0'
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ sock.setblocking(False)
+ sock.bind((UDP_IP,UDP_PORT))
+ mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
+ sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
+ last_query_timepoint = time.time()
+ QUERY_TIMEOUT = 0.2
+ while not stop_mdns_listener.is_set():
+ try:
+ start_mdns_listener.set()
+ current_time = time.time()
+ if current_time - last_query_timepoint > QUERY_TIMEOUT:
+ last_query_timepoint = current_time
+ timeout = max(0, QUERY_TIMEOUT - (current_time - last_query_timepoint))
+ read_socks, _, _ = select.select([sock], [], [], timeout)
+ if not read_socks:
+ continue
+ data, _ = sock.recvfrom(1024)
+ dns = dpkt.dns.DNS(data)
+ # Receives queries from esp board and sends answers
+ if len(dns.qd) > 0:
+ if dns.qd[0].name == HOST_NAME:
+ console_log('Received query: {} '.format(dns.__repr__()))
+ sock.sendto(get_dns_answer_to_mdns(HOST_NAME), (MCAST_GRP,UDP_PORT))
+ if dns.qd[0].name == HOST_NAME:
+ console_log('Received query: {} '.format(dns.__repr__()))
+ sock.sendto(get_dns_answer_to_mdns(HOST_NAME), (MCAST_GRP,UDP_PORT))
+ if dns.qd[0].name == MDNS_HOST_SERVICE:
+ print(dns.qd[0].name)
+ console_log('Received query: {} '.format(dns.__repr__()))
+ sock.sendto(get_dns_answer_to_service_query(MDNS_HOST_SERVICE), (MCAST_GRP,UDP_PORT))
+ # Receives answers from esp board and sets event flags for python test cases
+ if len(dns.an) == 1:
+ if dns.an[0].name.startswith(SERVICE_NAME):
+ console_log('Received answer to service query: {}'.format(dns.__repr__()))
+ esp_service_answered.set()
+ if len(dns.an) > 1:
+ if dns.an[1].name.startswith(SUB_SERVICE_NAME):
+ console_log('Received answer for sub service query: {}'.format(dns.__repr__()))
+ esp_sub_service_answered.set()
+ if len(dns.an) > 0 and dns.an[0].type == dpkt.dns.DNS_A:
+ if dns.an[0].name == esp_host + u'.local':
+ console_log('Received answer to esp32-mdns query: {}'.format(dns.__repr__()))
+ esp_host_answered.set()
+ if dns.an[0].name == esp_host + u'-delegated.local':
+ console_log('Received answer to esp32-mdns-delegate query: {}'.format(dns.__repr__()))
+ esp_delegated_host_answered.set()
+ except socket.timeout:
+ break
+ except dpkt.UnpackError:
+ continue
+
+
+def create_socket(): # type:() -> socket.socket
+ UDP_IP = '0.0.0.0'
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ sock.setblocking(False)
+ sock.bind((UDP_IP,UDP_PORT))
+ mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
+ sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
+ return sock
+
+
+def test_query_dns_http_service(service): # type: (str) -> None
+ print('SRV: Query {}'.format(service))
+ sock = create_socket()
+ packet = get_mdns_service_query(service)
+ for _ in range(RETRY_COUNT):
+ if esp_service_answered.wait(timeout=25):
+ break
+ sock.sendto(packet, (MCAST_GRP,UDP_PORT))
+ else:
+ raise RuntimeError('Test has failed: did not receive mdns answer within timeout')
+
+
+def test_query_dns_sub_service(sub_service): # type: (str) -> None
+ print('PTR: Query {}'.format(sub_service))
+ sock = create_socket()
+ packet = get_mdns_sub_service_query(sub_service)
+ for _ in range(RETRY_COUNT):
+ if esp_sub_service_answered.wait(timeout=25):
+ break
+ sock.sendto(packet, (MCAST_GRP,UDP_PORT))
+ else:
+ raise RuntimeError('Test has failed: did not receive mdns answer within timeout')
+
+
+def test_query_dns_host(esp_host): # type: (str) -> None
+ print('A: {}'.format(esp_host))
+ sock = create_socket()
+ packet = get_dns_query_for_esp(esp_host)
+ for _ in range(RETRY_COUNT):
+ if esp_host_answered.wait(timeout=25):
+ break
+ sock.sendto(packet, (MCAST_GRP,UDP_PORT))
+ else:
+ raise RuntimeError('Test has failed: did not receive mdns answer within timeout')
+
+
+def test_query_dns_host_delegated(esp_host): # type: (str) -> None
+ print('A: {}'.format(esp_host))
+ sock = create_socket()
+ packet = get_dns_query_for_esp(esp_host + '-delegated')
+ for _ in range(RETRY_COUNT):
+ if esp_delegated_host_answered.wait(timeout=25):
+ break
+ sock.sendto(packet, (MCAST_GRP,UDP_PORT))
+ else:
+ raise RuntimeError('Test has failed: did not receive mdns answer within timeout')
+
+
+@ttfw_idf.idf_custom_test(env_tag='Example_WIFI', group='test-apps')
+def test_app_esp_mdns(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None
+ dut1 = env.get_dut('mdns', 'tools/test_apps/protocols/mdns', dut_class=ttfw_idf.ESP32DUT)
+
+ # 1. start mdns application
+ dut1.start_app()
+ # 2. get the dut host name (and IP address)
+ specific_host = dut1.expect(re.compile(r'mdns hostname set to: \[([^\]]+)\]'), timeout=30)[0]
+
+ esp_ip = dut1.expect(re.compile(r' IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)'), timeout=30)
+ print('Got IP={}'.format(esp_ip[0]))
+
+ mdns_responder = Thread(target=mdns_listener, args=(str(specific_host),))
+
+ def start_case(case, desc, result): # type: (str, str, str) -> None
+ print('Starting {}: {}'.format(case, desc))
+ dut1.write(case)
+ dut1.expect(re.compile(result), timeout=30)
+
+ try:
+ # start dns listener thread
+ mdns_responder.start()
+
+ # wait untill mdns listener thred started
+ if not start_mdns_listener.wait(timeout=5):
+ raise ValueError('Test has failed: mdns listener thread did not start')
+
+ # query dns service from host, answer should be received from esp board
+ test_query_dns_http_service(SERVICE_NAME)
+
+ # query dns sub-service from host, answer should be received from esp board
+ test_query_dns_sub_service(SUB_SERVICE_NAME)
+
+ # query dns host name, answer should be received from esp board
+ test_query_dns_host(specific_host)
+
+ # query dns host name delegated, answer should be received from esp board
+ test_query_dns_host_delegated(specific_host)
+
+ # query service from esp board, answer should be received from host
+ start_case('CONFIG_TEST_QUERY_SERVICE', 'Query SRV ESP32._http._tcp.local', 'SRV:ESP32')
+
+ # query dns-host from esp board, answer should be received from host
+ start_case('CONFIG_TEST_QUERY_HOST', 'Query tinytester.local', 'tinytester.local resolved to: 127.0.0.1')
+
+ # query dns-host aynchrounusely from esp board, answer should be received from host
+ start_case('CONFIG_TEST_QUERY_HOST_ASYNC', 'Query tinytester.local async', 'Async query resolved to A:127.0.0.1')
+
+ finally:
+ stop_mdns_listener.set()
+ mdns_responder.join()
+
+
+if __name__ == '__main__':
+ test_app_esp_mdns()
diff --git a/components/mdns/tests/test_apps/main/CMakeLists.txt b/components/mdns/tests/test_apps/main/CMakeLists.txt
new file mode 100644
index 000000000..b5902d589
--- /dev/null
+++ b/components/mdns/tests/test_apps/main/CMakeLists.txt
@@ -0,0 +1,2 @@
+idf_component_register(SRCS "main.c" "mdns_test.c"
+ INCLUDE_DIRS ".")
diff --git a/components/mdns/tests/test_apps/main/Kconfig.projbuild b/components/mdns/tests/test_apps/main/Kconfig.projbuild
new file mode 100644
index 000000000..d5d791a70
--- /dev/null
+++ b/components/mdns/tests/test_apps/main/Kconfig.projbuild
@@ -0,0 +1,28 @@
+menu "Example Configuration"
+
+ config TEST_MDNS_HOSTNAME
+ string "mDNS Hostname"
+ default "esp32-mdns"
+ help
+ mDNS Hostname for example to use
+
+ config TEST_MDNS_INSTANCE
+ string "mDNS Instance Name"
+ default "ESP32 with mDNS"
+ help
+ mDNS Instance Name for example to use
+
+ config TEST_MDNS_PUBLISH_DELEGATE_HOST
+ bool "Publish a delegated host"
+ help
+ Enable publishing a delegated host other than ESP32.
+ The example will also add a mock service for this host.
+
+ config TEST_MDNS_ADD_MAC_TO_HOSTNAME
+ bool "Add mac suffix to hostname"
+ default n
+ help
+ If enabled, a portion of MAC address is added to the hostname, this is used
+ for evaluation of tests in CI
+
+endmenu
diff --git a/components/mdns/tests/test_apps/main/main.c b/components/mdns/tests/test_apps/main/main.c
new file mode 100644
index 000000000..0a160aee2
--- /dev/null
+++ b/components/mdns/tests/test_apps/main/main.c
@@ -0,0 +1,117 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include
+#include
+#include "esp_mac.h"
+#include "nvs_flash.h"
+#include "esp_event.h"
+#include "esp_netif.h"
+#include "esp_log.h"
+#include "protocol_examples_common.h"
+#include "mdns.h"
+
+static const char *TAG = "MDNS_TEST";
+void mdns_test(char *line);
+
+static void get_string(char *line, size_t size)
+{
+ int count = 0;
+ while (count < size) {
+ int c = fgetc(stdin);
+ if (c == '\n') {
+ line[count] = '\0';
+ break;
+ } else if (c > 0 && c < 127) {
+ line[count] = c;
+ ++count;
+ }
+ vTaskDelay(20 / portTICK_PERIOD_MS);
+ }
+}
+
+/** Generate host name based on sdkconfig, optionally adding a portion of MAC address to it.
+ * @return host name string allocated from the heap
+ */
+static char* generate_hostname(void)
+{
+#ifndef CONFIG_TEST_MDNS_ADD_MAC_TO_HOSTNAME
+ return strdup(CONFIG_TEST_MDNS_HOSTNAME);
+#else
+ uint8_t mac[6];
+ char *hostname;
+ esp_read_mac(mac, ESP_MAC_WIFI_STA);
+ if (-1 == asprintf(&hostname, "%s-%02X%02X%02X", CONFIG_TEST_MDNS_HOSTNAME, mac[3], mac[4], mac[5])) {
+ abort();
+ }
+ return hostname;
+#endif
+}
+
+static void initialise_mdns(void)
+{
+ char * hostname = generate_hostname();
+
+ //initialize mDNS
+ ESP_ERROR_CHECK( mdns_init() );
+
+ //set mDNS hostname (required if you want to advertise services)
+ ESP_ERROR_CHECK( mdns_hostname_set(hostname) );
+
+ ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname);
+ //set default mDNS instance name
+ ESP_ERROR_CHECK( mdns_instance_name_set(CONFIG_TEST_MDNS_INSTANCE) );
+
+ //initialize service
+ ESP_ERROR_CHECK( mdns_service_add("ESP32-WebServer", "_http", "_tcp", 80, NULL, 0) );
+
+#if CONFIG_TEST_MDNS_PUBLISH_DELEGATE_HOST
+ char *delegated_hostname;
+ if (-1 == asprintf(&delegated_hostname, "%s-delegated", hostname)) {
+ abort();
+ }
+
+ mdns_ip_addr_t addr4, addr6;
+ esp_netif_str_to_ip4("10.0.0.1", &addr4.addr.u_addr.ip4);
+ addr4.addr.type = ESP_IPADDR_TYPE_V4;
+ esp_netif_str_to_ip6("fd11:22::1", &addr6.addr.u_addr.ip6);
+ addr6.addr.type = ESP_IPADDR_TYPE_V6;
+ addr4.next = &addr6;
+ addr6.next = NULL;
+ ESP_ERROR_CHECK( mdns_delegate_hostname_add(delegated_hostname, &addr4) );
+ ESP_ERROR_CHECK( mdns_service_add_for_host("test0", "_http", "_tcp", delegated_hostname, 1234, NULL, 0) );
+ free(delegated_hostname);
+#endif // CONFIG_TEST_MDNS_PUBLISH_DELEGATE_HOST
+
+ ESP_ERROR_CHECK( mdns_service_subtype_add_for_host("ESP32-WebServer", "_http", "_tcp", NULL, "_server") );
+
+ free(hostname);
+}
+
+void app_main(void)
+{
+ ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
+ ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
+
+ ESP_ERROR_CHECK(nvs_flash_init());
+ ESP_ERROR_CHECK(esp_netif_init());
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ initialise_mdns();
+
+ /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
+ * Read "Establishing Wi-Fi or Ethernet Connection" section in
+ * examples/protocols/README.md for more information about this function.
+ */
+ ESP_ERROR_CHECK(example_connect());
+
+ while (1) {
+ char line[256];
+
+ get_string(line, sizeof(line));
+ mdns_test(line);
+ continue;
+ }
+}
diff --git a/components/mdns/tests/test_apps/main/mdns_test.c b/components/mdns/tests/test_apps/main/mdns_test.c
new file mode 100644
index 000000000..d82229f1d
--- /dev/null
+++ b/components/mdns/tests/test_apps/main/mdns_test.c
@@ -0,0 +1,188 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "mdns.h"
+#include "esp_log.h"
+#include "esp_netif.h"
+
+static const char * TAG = "MDNS_TEST_APP";
+static const int RETRY_COUNT = 10;
+
+static void mdns_print_results(mdns_result_t *results)
+{
+ mdns_result_t *r = results;
+ mdns_ip_addr_t *a = NULL;
+ int t;
+ while (r) {
+ if (r->instance_name) {
+ printf("PTR:%s.%s.%s\n", r->instance_name, r->service_type, r->proto);
+ }
+ if (r->hostname) {
+ printf("SRV:%s.local:%u\n", r->hostname, r->port);
+ }
+ if (r->txt_count) {
+ printf("TXT:[%zu] ", r->txt_count);
+ for (t = 0; t < r->txt_count; t++) {
+ printf("%s=%s(%d); ", r->txt[t].key, r->txt[t].value ? r->txt[t].value : "NULL", r->txt_value_len[t]);
+ }
+ printf("\n");
+ }
+ a = r->addr;
+ while (a) {
+ if (a->addr.type == ESP_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 bool check_and_print_result(mdns_search_once_t *search)
+{
+ // Check if any result is available
+ mdns_result_t * result = NULL;
+ if (!mdns_query_async_get_results(search, 0, &result, NULL)) {
+ return false;
+ }
+
+ if (!result) { // search timeout, but no result
+ return false;
+ }
+
+ // If yes, print the result
+ mdns_ip_addr_t * a = result->addr;
+ while (a) {
+ if(a->addr.type == ESP_IPADDR_TYPE_V6){
+ printf("Async query resolved to AAAA:" IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6));
+ } else {
+ printf("Async query resolved to A:" IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4)));
+ }
+ a = a->next;
+ }
+ // and free the result
+ mdns_query_results_free(result);
+ return true;
+}
+
+static bool query_mdns_hosts_async(const char * host_name)
+{
+ ESP_LOGI(TAG, "Query both A and AAA: %s.local", host_name);
+ bool res = false;
+
+ mdns_search_once_t *s_a = mdns_query_async_new(host_name, NULL, NULL, MDNS_TYPE_A, 1000, 1, NULL);
+ mdns_query_async_delete(s_a);
+ mdns_search_once_t *s_aaaa = mdns_query_async_new(host_name, NULL, NULL, MDNS_TYPE_AAAA, 1000, 1, NULL);
+ while (s_a || s_aaaa) {
+ if (s_a && check_and_print_result(s_a)) {
+ ESP_LOGI(TAG, "Query A %s.local finished", host_name);
+ mdns_query_async_delete(s_a);
+ s_a = NULL;
+ res = true;
+ }
+ if (s_aaaa && check_and_print_result(s_aaaa)) {
+ ESP_LOGI(TAG, "Query AAAA %s.local finished", host_name);
+ mdns_query_async_delete(s_aaaa);
+ s_aaaa = NULL;
+ res = true;
+ }
+ }
+ return res;
+}
+
+static esp_err_t query_mdns_host(const char * host_name)
+{
+ ESP_LOGI(TAG, "Query A: %s.local", host_name);
+
+ struct esp_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, "%s: Host was not found!", esp_err_to_name(err));
+ }
+ ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err));
+ return err;
+ }
+
+ ESP_LOGI(TAG, "Query A: %s.local resolved to: " IPSTR, host_name, IP2STR(&addr));
+ return ESP_OK;
+}
+
+static esp_err_t query_mdns_service(const char * instance, const char * service_name, const char * proto)
+{
+ ESP_LOGI(TAG, "Query SRV: %s.%s.local", service_name, proto);
+
+ mdns_result_t * results = NULL;
+ esp_err_t err = mdns_query_srv(instance, service_name, proto, 3000, &results);
+ if(err){
+ ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err));
+ return err;
+ }
+ if(!results){
+ ESP_LOGW(TAG, "No results found!");
+ }
+
+ mdns_print_results(results);
+ mdns_query_results_free(results);
+ return ESP_OK;
+}
+
+void query_mdns_service_sub_type(const char * subtype, 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: %s", esp_err_to_name(err));
+ }
+ if(!results){
+ ESP_LOGW(TAG, "No results found!");
+ }
+
+ mdns_print_results(results);
+ mdns_query_results_free(results);
+}
+
+void mdns_test(const char *line)
+{
+ char test_case[32];
+ int i = 0;
+ const TickType_t xDelay = 1000 / portTICK_PERIOD_MS;
+
+ sscanf(line, "%s", test_case);
+ ESP_LOGI(TAG, "test case = %s", test_case);
+
+ if (strcmp(test_case, "CONFIG_TEST_QUERY_HOST") == 0) {
+ i = 0;
+ while (query_mdns_host("tinytester") != ESP_OK && i != RETRY_COUNT) {
+ query_mdns_host("tinytester");
+ i++;
+ vTaskDelay(xDelay);
+ }
+ } else if (strcmp(test_case, "CONFIG_TEST_QUERY_HOST_ASYNC") == 0) {
+ i = 0;
+ while (query_mdns_hosts_async("tinytester") == false && i != RETRY_COUNT) {
+ query_mdns_hosts_async("tinytester");
+ i++;
+ vTaskDelay(xDelay);
+ }
+ } else if (strcmp(test_case, "CONFIG_TEST_QUERY_SERVICE") == 0) {
+ i = 0;
+ while (query_mdns_service("ESP32", "_http", "_tcp") != ESP_OK && i != RETRY_COUNT) {
+ query_mdns_service("ESP32", "_http", "_tcp");
+ i++;
+ vTaskDelay(xDelay);
+ }
+ } else {
+ ESP_LOGE(TAG, "%s: No such test case", test_case);
+ }
+}
diff --git a/components/mdns/tests/test_apps/sdkconfig.defaults b/components/mdns/tests/test_apps/sdkconfig.defaults
new file mode 100644
index 000000000..0c4ffe915
--- /dev/null
+++ b/components/mdns/tests/test_apps/sdkconfig.defaults
@@ -0,0 +1,2 @@
+CONFIG_TEST_MDNS_ADD_MAC_TO_HOSTNAME=y
+CONFIG_TEST_MDNS_PUBLISH_DELEGATE_HOST=y
diff --git a/components/mdns/tests/unit_test/CMakeLists.txt b/components/mdns/tests/unit_test/CMakeLists.txt
new file mode 100644
index 000000000..5994c440c
--- /dev/null
+++ b/components/mdns/tests/unit_test/CMakeLists.txt
@@ -0,0 +1,2 @@
+idf_component_register(SRC_DIRS "."
+ PRIV_REQUIRES cmock test_utils mdns)
diff --git a/components/mdns/tests/unit_test/test_mdns.c b/components/mdns/tests/unit_test/test_mdns.c
new file mode 100644
index 000000000..88bc49dbc
--- /dev/null
+++ b/components/mdns/tests/unit_test/test_mdns.c
@@ -0,0 +1,115 @@
+#include "test_utils.h"
+#include "mdns.h"
+#include "esp_event.h"
+#include "unity.h"
+
+
+#define MDNS_HOSTNAME "test-hostname"
+#define MDNS_DELEGATE_HOSTNAME "delegate-hostname"
+#define MDNS_INSTANCE "test-instance"
+#define MDNS_SERVICE_NAME "_http"
+#define MDNS_SERVICE_PROTO "_tcp"
+#define MDNS_SERVICE_PORT 80
+
+
+static void yield_to_all_priorities(void)
+{
+ // Lower the test-task priority before testing to ensure other tasks got executed on forced context switch
+ size_t test_task_prio_before = uxTaskPriorityGet(NULL);
+ vTaskPrioritySet(NULL, tskIDLE_PRIORITY);
+ taskYIELD(); // Let the RTOS to switch context
+ vTaskPrioritySet(NULL, test_task_prio_before);
+}
+
+
+TEST_CASE("mdns api to fail in invalid state", "[mdns][leaks=64]")
+{
+ TEST_ASSERT_NOT_EQUAL(ESP_OK, mdns_init() );
+ TEST_ASSERT_NOT_EQUAL(ESP_OK, mdns_hostname_set(MDNS_HOSTNAME) );
+ TEST_ASSERT_NOT_EQUAL(ESP_OK, mdns_instance_name_set(MDNS_INSTANCE) );
+ TEST_ASSERT_NOT_EQUAL(ESP_OK, mdns_service_add(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT, NULL, 0) );
+}
+
+TEST_CASE("mdns init and deinit", "[mdns][leaks=64]")
+{
+ test_case_uses_tcpip();
+ TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create_default());
+ TEST_ASSERT_EQUAL(ESP_OK, mdns_init() );
+ yield_to_all_priorities(); // Make sure that mdns task has executed to complete initialization
+ mdns_free();
+ esp_event_loop_delete_default();
+}
+
+TEST_CASE("mdns api return expected err-code and do not leak memory", "[mdns][leaks=64]")
+{
+ mdns_txt_item_t serviceTxtData[CONFIG_MDNS_MAX_SERVICES] = { {NULL, NULL},
+ };
+ mdns_ip_addr_t addr;
+ addr.addr.type = ESP_IPADDR_TYPE_V4;
+ addr.addr.u_addr.ip4.addr = esp_ip4addr_aton("127.0.0.1");
+ addr.next = NULL;
+ for (int i=0; i