Просмотр исходного кода

mdns: added test app

Closes IDF-4132
Suren Gabrielyan 4 лет назад
Родитель
Сommit
e0d5fca390

+ 11 - 0
tools/test_apps/protocols/mdns/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 $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+
+project(mdns)

+ 48 - 0
tools/test_apps/protocols/mdns/README.md

@@ -0,0 +1,48 @@
+| 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. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](README.md) for more details.
+* 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.

+ 268 - 0
tools/test_apps/protocols/mdns/app_test.py

@@ -0,0 +1,268 @@
+# SPDX-FileCopyrightText: 2021 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'
+
+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)
+            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))
+            if len(dns.an) == 1:
+                if dns.an[0].name == 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 == 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()
+    sock.sendto(get_mdns_service_query(service), (MCAST_GRP,UDP_PORT))
+    if not esp_service_answered.wait(timeout=25):
+        raise ValueError('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()
+    sock.sendto(get_mdns_sub_service_query(sub_service), (MCAST_GRP,UDP_PORT))
+    if not esp_sub_service_answered.wait(timeout=25):
+        raise ValueError('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()
+    sock.sendto(get_dns_query_for_esp(esp_host), (MCAST_GRP,UDP_PORT))
+    if not esp_host_answered.wait(timeout=25):
+        raise ValueError('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()
+    sock.sendto(get_dns_query_for_esp(esp_host + '-delegated'), (MCAST_GRP,UDP_PORT))
+    if not esp_delegated_host_answered.wait(timeout=25):
+        raise ValueError('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=10)
+
+    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 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')
+
+        # 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')
+    finally:
+        stop_mdns_listener.set()
+        mdns_responder.join()
+
+
+if __name__ == '__main__':
+    test_app_esp_mdns()

+ 2 - 0
tools/test_apps/protocols/mdns/main/CMakeLists.txt

@@ -0,0 +1,2 @@
+idf_component_register(SRCS "main.c" "mdns_test.c"
+                        INCLUDE_DIRS ".")

+ 28 - 0
tools/test_apps/protocols/mdns/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

+ 117 - 0
tools/test_apps/protocols/mdns/main/main.c

@@ -0,0 +1,117 @@
+/*
+ * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <stdio.h>
+#include <string.h>
+#include "esp_system.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(50 / 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)
+{
+    char line[256];
+
+    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) {
+        get_string(line, sizeof(line));
+        mdns_test(line);
+        continue;
+    }
+}

+ 166 - 0
tools/test_apps/protocols/mdns/main/mdns_test.c

@@ -0,0 +1,166 @@
+/*
+ * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <string.h>
+#include "mdns.h"
+#include "esp_log.h"
+#include "esp_netif.h"
+
+static const char * TAG = "MDNS_TEST_APP";
+
+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)) {
+        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("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 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_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;
+        }
+        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;
+        }
+    }
+}
+
+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 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;
+    }
+    if(!results){
+        ESP_LOGW(TAG, "No results found!");
+        return;
+    }
+
+    mdns_print_results(results);
+    mdns_query_results_free(results);
+}
+
+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));
+        return;
+    }
+    if(!results){
+        ESP_LOGW(TAG, "No results found!");
+        return;
+    }
+
+    mdns_print_results(results);
+    mdns_query_results_free(results);
+}
+
+void mdns_test(const char *line)
+{
+    char test_case[32];
+
+    sscanf(line, "%s", test_case);
+    ESP_LOGI(TAG, "test case = %s", test_case);
+
+    if (strcmp(test_case, "CONFIG_TEST_QUERY_HOST") == 0) {
+        query_mdns_host("tinytester");
+    } else if (strcmp(test_case, "CONFIG_TEST_QUERY_HOST_ASYNC") == 0) {
+        query_mdns_hosts_async("tinytester");
+    } else if (strcmp(test_case, "CONFIG_TEST_QUERY_SERVICE") == 0) {
+        query_mdns_service("ESP32", "_http", "_tcp");
+    } else {
+        ESP_LOGE(TAG, "%s: No such test case", test_case);
+    }
+}

+ 2 - 0
tools/test_apps/protocols/mdns/sdkconfig.defaults

@@ -0,0 +1,2 @@
+CONFIG_TEST_MDNS_ADD_MAC_TO_HOSTNAME=y
+CONFIG_TEST_MDNS_PUBLISH_DELEGATE_HOST=y