Chapter 84: mDNS and Service Discovery

Chapter Objectives

By the end of this chapter, you will be able to:

  • Understand the core principles of zero-configuration networking (Zeroconf), particularly mDNS and DNS-SD.
  • Master the use of Multicast DNS (mDNS) for local hostname registration and resolution on ESP32 devices.
  • Implement DNS-based Service Discovery (DNS-SD) to advertise custom network services offered by an ESP32.
  • Develop ESP32 applications capable of discovering and Browse for services available on the local network using mDNS/DNS-SD.
  • Effectively utilize the ESP-IDF mdns component, including its APIs for service advertisement, TXT record management, and service querying.
  • Handle results from mDNS queries and manage associated resources correctly.

Introduction

In the realm of modern networking, especially within IoT and smart home environments, the ability for devices to seamlessly connect and communicate without manual configuration is paramount. Imagine plugging in a new smart device and having it instantly appear in your control application, or your laptop automatically finding a nearby printer without you needing to type in an IP address. This “plug-and-play” experience is largely achieved through zero-configuration networking (Zeroconf) technologies.

This chapter focuses on two key pillars of Zeroconf: Multicast DNS (mDNS) and DNS-based Service Discovery (DNS-SD). While Chapter 83 introduced these concepts alongside DNS server functionalities, this chapter provides a more in-depth, focused exploration of how the ESP32 can leverage mDNS to make itself known on the network by a human-readable hostname (e.g., my-esp32.local) and use DNS-SD to advertise its capabilities (like an embedded web server or a custom data service). Crucially, we will also delve into how an ESP32 can discover other services on the network. Mastering these will allow you to build more intuitive, user-friendly, and interconnected ESP32 applications.

Theory

Zero-Configuration Networking (Zeroconf) Overview

Zeroconf aims to enable networking in the absence of manual configuration and centralized infrastructure services like DHCP and DNS. It typically comprises three main components:

  1. Link-Local Addressing: Devices automatically assign themselves IP addresses (e.g., IPv4 Link-Local addresses in the 169.254.0.0/16 range, or IPv6 link-local addresses) if a DHCP server is not available. This ensures basic IP connectivity on the local network segment. While important, the ESP-IDF mdns component itself doesn’t manage link-local addressing directly; this is handled by the underlying TCP/IP stack (LwIP) when DHCP fails or is not used.
  2. Multicast DNS (mDNS): This protocol allows devices to resolve hostnames to IP addresses within a local network without a conventional unicast DNS server. Devices use multicast messaging to announce their hostnames (ending in .local) and respond to queries for these names. It operates on UDP port 5353 using multicast addresses (224.0.0.251 for IPv4, FF02::FB for IPv6).
  3. DNS-based Service Discovery (DNS-SD): Built on top of DNS (and typically using mDNS on local networks), DNS-SD allows devices to advertise the services they offer (e.g., web server, printer, music streaming) and discover services offered by other devices. It uses standard DNS record types like PTR, SRV, and TXT in specific ways.
graph TD
    subgraph Zeroconf Environment [Local Network]
        direction LR
        Z_LLA[("Link-Local Addressing<br>(169.254.x.x / IPv6 Link-Local)")]
        Z_MDNS[("Multicast DNS (mDNS)<br>Hostname Resolution<br><i>my-device.local</i> <--> IP")]
        Z_DNSSD[("DNS-based Service Discovery (DNS-SD)<br>Service Advertisement & Discovery<br><i>_http._tcp.local</i>")]

        Z_LLA --> Z_MDNS;
        Z_MDNS --> Z_DNSSD;

        subgraph DeviceA [ESP32 Device A]
            direction TB
            A_IP{Assign IP}
            A_HN[Register Hostname<br><i>esp32-a.local</i>]
            A_SVC_ADV[Advertise Service<br><i>_sensor._tcp</i>]
        end

        subgraph DeviceB [Laptop/Phone/Other ESP32]
            direction TB
            B_IP{Assign IP}
            B_HN_RES[Resolve Hostname<br><i>esp32-a.local?</i>]
            B_SVC_DSC[Discover Service<br><i>_sensor._tcp?</i>]
        end

        Z_LLA --> A_IP
        Z_LLA --> B_IP
        A_IP --> A_HN
        A_HN --> Z_MDNS
        Z_MDNS --> B_HN_RES
        A_SVC_ADV --> Z_DNSSD
        Z_DNSSD --> B_SVC_DSC

        B_HN_RES --> B_SVC_DSC
    end

    style Z_LLA fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    style Z_MDNS fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    style Z_DNSSD fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF

    style DeviceA fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    style A_IP fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
    style A_HN fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    style A_SVC_ADV fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF

    style DeviceB fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46
    style B_IP fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
    style B_HN_RES fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    style B_SVC_DSC fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF

    classDef default fill:#FFF,stroke:#333,stroke-width:1px;

mDNS in ESP-IDF

The ESP-IDF provides a dedicated mdns component that handles the complexities of mDNS.

Function Purpose Key Parameter(s) Example Usage Snippet
mdns_init() Initializes the mDNS service globally. Must be called once after network stack initialization and before other mDNS functions. N/A ESP_ERROR_CHECK(mdns_init());
mdns_hostname_set() Sets the mDNS hostname for the ESP32. The device will then be discoverable as hostname.local. const char* hostname: The desired hostname (e.g., “my-esp32”). ESP_ERROR_CHECK(mdns_hostname_set("my-esp32-device"));
mdns_instance_name_set() Sets a human-readable “instance name” or “friendly name” for the device. This can be displayed by mDNS browsers and used as a default name for services. const char* instance: The instance name (e.g., “Living Room Sensor”). ESP_ERROR_CHECK(mdns_instance_name_set("ESP32 Weather Station"));
mdns_free() Stops the mDNS service and releases all allocated resources. Call if mDNS is no longer needed or before re-initialization. N/A mdns_free();
  • Initialization: The mDNS service must be initialized globally using mdns_init(). This is typically done once after the network stack (esp_netif) is initialized and the primary network interface (Wi-Fi STA/AP, Ethernet) is up.
  • Hostname Registration: You can assign a hostname to your ESP32 using mdns_hostname_set("my-device"). The mdns service will then attempt to claim my-device.local on the network and respond to mDNS queries for this name with the ESP32’s IP address.
  • Instance Name: mdns_instance_name_set("My Custom ESP32") can set a human-readable “instance name” for the device, which might be displayed by mDNS browsers. This is often used as a default friendly name for services if not specified otherwise.
  • Network Interface Interaction: The mdns service automatically operates over active IP network interfaces. If you have multiple interfaces (e.g., Wi-Fi AP and STA active simultaneously), mDNS will function on both, using their respective IP addresses.
flowchart TD
    A[Start Application] --> B(Initialize NVS Flash);
    B --> C("Initialize TCP/IP Adapter<br><pre>esp_netif_init()</pre>");
    C --> D("Create Default Event Loop<br><pre>esp_event_loop_create_default()</pre>");
    D --> E("Create Default Wi-Fi STA/AP Interface<br><pre>esp_netif_create_default_wifi_sta()</pre>");
    E --> F("Initialize Wi-Fi<br><pre>esp_wifi_init()</pre>");
    F --> G("Configure & Start Wi-Fi STA/AP");
    G --> H{"Wi-Fi Connected and<br>IP Address Obtained?"};

    H -- Yes --> I("Initialize mDNS Service<br><pre>mdns_init()</pre>");
    I --> J("Set mDNS Hostname<br><pre>mdns_hostname_set(<i>my-esp32</i>)</pre>");
    J --> K("Set mDNS Instance Name (Optional)<br><pre>mdns_instance_name_set(<i>My Device</i>)</pre>");
    K --> L[("ESP32 Announces & Responds<br>to <i>my-esp32.local</i> queries")];
    L --> M((End of Hostname Registration Setup));

    H -- No / Timeout --> N[Handle Wi-Fi Connection Failure];
    N --> M;

    classDef startEnd fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6;
    classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF;
    classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E;
    classDef io fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B;
    classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46;

    class A,M startEnd;
    class B,C,D,E,F,G,I,J,K process;
    class H decision;
    class N io;
    class L success;

DNS-SD in ESP-IDF: Advertising Services

The mdns component allows you to advertise services offered by your ESP32.

  • Adding a Service:mdns_service_add(instance_name, service_type, proto, port, txt_records, num_txt_records) is the primary function.
    • instance_name: A human-readable name for this specific service instance (e.g., “ESP32 Web Config”).
    • service_type: The type of service, e.g., _http, _ftp, _mycustomservice.
    • proto: The transport protocol, e.g., _tcp, _udp.
    • port: The port number the service is listening on.
    • txt_records: An array of mdns_txt_item_t structures for associated TXT record data.
    • num_txt_records: Number of items in the txt_records array. The full service name advertised will be instance_name.service_type.proto.local., and a PTR record will point from service_type.proto.local. to this full service name.
  • TXT Records: These provide additional key-value metadata about the service. Each mdns_txt_item_t has a key and value (both strings). Example: {"path", "/status"}, {"version", "2.1"}. TXT records can be updated dynamically for an existing service using mdns_service_txt_set().
  • Removing a Service: mdns_service_remove(service_type, proto) can be used to stop advertising a specific service type.
Function Purpose Key Parameters
mdns_service_add() Advertises a new network service offered by the ESP32. Makes the service discoverable on the local network.
  • const char* instance_name: Human-readable name for this service instance (e.g., “ESP32 Web Portal”). If NULL, mdns_instance_name_set() value is used.
  • const char* service_type: Service type (e.g., “_http”, “_mycustomsrv”).
  • const char* proto: Transport protocol (e.g., “_tcp”, “_udp”).
  • uint16_t port: Port number the service is listening on.
  • mdns_txt_item_t txt_records[]: Array of TXT records (key-value pairs) for additional metadata. Can be NULL.
  • uint8_t num_txt_records: Number of items in txt_records.
mdns_service_txt_set() Updates or sets the TXT records for an already advertised service. Allows dynamic changes to service metadata.
  • const char* service_type: The service type of the existing service.
  • const char* proto: The protocol of the existing service.
  • mdns_txt_item_t txt_records[]: New array of TXT records.
  • uint8_t num_txt_records: Number of items in the new txt_records.
mdns_service_remove() Stops advertising a specific service type and protocol combination.
  • const char* service_type: The service type to remove.
  • const char* proto: The protocol of the service to remove.
mdns_service_remove_all() Stops advertising all services previously added by mdns_service_add(). N/A

DNS-SD in ESP-IDF: Discovering (Browse) Services

Discovering services involves querying the network for specific service types and then retrieving details about each instance found.

  1. Query for Service Instances (PTR query):
    • Use mdns_query_ptr(service_type, proto, timeout, max_results, &results).
    • This queries for PTR records matching service_type.proto.local..
    • results will be a pointer to a linked list of mdns_result_t structures if successful. Each result corresponding to a PTR record will contain the instance name of a discovered service in results->instance_name.
  2. Query for Service Details (SRV query):
    • For each instance name obtained from the PTR query, use mdns_query_srv(instance_name, service_type, proto, timeout, &result_srv).
    • result_srv (a single mdns_result_t*) will contain the SRV record details: result_srv->hostname (target hostname), result_srv->port.
  3. Query for TXT Records (TXT query):
    • Similarly, use mdns_query_txt(instance_name, service_type, proto, timeout, &result_txt).
    • result_txt->txt will be a linked list of mdns_txt_item_t containing the key-value pairs.
  4. Resolve Hostname to IP Address (A/AAAA query):
    • The hostname obtained from the SRV record (e.g., esp32-target.local) needs to be resolved to an IP address.
    • Use mdns_query_a(hostname_from_srv, timeout, &result_a) for IPv4.
    • result_a->addr (a linked list of esp_ip4_addr_t) will contain the IP address(es). For IPv6, use mdns_query_aaaa().
  5. Freeing Results: All mdns_result_t structures and their internal lists obtained from query functions are dynamically allocated. They must be freed using mdns_query_results_free(results) to prevent memory leaks.
Function Purpose Key Parameters Typical Result Data (in mdns_result_t)
mdns_query_ptr() Queries for service instances (PTR records) of a specific type and protocol. Finds all devices offering a particular service.
  • service_type
  • proto
  • timeout (ms)
  • max_results
  • &results (ptr to result list)
Linked list of mdns_result_t. Each result: instance_name.
mdns_query_srv() Queries for service details (SRV record) of a specific service instance. Finds hostname and port.
  • instance_name (from PTR)
  • service_type
  • proto
  • timeout (ms)
  • &result_srv (ptr to single result)
Single mdns_result_t: hostname, port.
mdns_query_txt() Queries for TXT records of a specific service instance. Finds additional key-value metadata.
  • instance_name (from PTR)
  • service_type
  • proto
  • timeout (ms)
  • &result_txt (ptr to single result)
Single mdns_result_t: txt (linked list of mdns_txt_item_t), txt_count.
mdns_query_a() Resolves a hostname (from SRV record) to an IPv4 address (A record).
  • hostname (from SRV)
  • timeout (ms)
  • &result_a (ptr to result list)
Linked list of mdns_result_t. Each result: addr (linked list of esp_ip4_addr_t).
mdns_query_aaaa() Resolves a hostname to an IPv6 address (AAAA record). Similar to mdns_query_a(). Linked list of mdns_result_t. Each result: addr_v6 (linked list of esp_ip6_addr_t).
mdns_query_results_free() Frees the memory allocated for a list of mdns_result_t structures obtained from any query function. Crucial to prevent memory leaks. mdns_result_t* results N/A

sequenceDiagram
    participant ClientApp as Client Application (ESP32)
    participant MDNSComp as ESP-IDF mDNS Component
    participant Network as Local Network (Multicast)
    participant ResponderA as Responder Device A<br>(e.g., ESP32_A)
    participant ResponderB as Responder Device B<br>(e.g., ESP32_B)

    ClientApp->>MDNSComp: Call mdns_query_ptr("_http", "_tcp", timeout, max_results, &results_ptr)
    activate MDNSComp
    MDNSComp->>Network: Multicast Query: PTR _http._tcp.local?
    activate Network
    Network-->>ResponderA: Forward Query
    activate ResponderA
    ResponderA-->>Network: Multicast Response: PTR WebServerA._http._tcp.local
    deactivate ResponderA
    Network-->>ResponderB: Forward Query
    activate ResponderB
    ResponderB-->>Network: Multicast Response: PTR WebServerB._http._tcp.local
    deactivate ResponderB
    Network-->>MDNSComp: Collect Responses
    deactivate Network
    MDNSComp-->>ClientApp: Return results_ptr (list: ["WebServerA", "WebServerB"])
    deactivate MDNSComp

    ClientApp->>ClientApp: For each instance (e.g., "WebServerA"):
    ClientApp->>MDNSComp: Call mdns_query_srv("WebServerA", "_http", "_tcp", timeout, &result_srv)
    activate MDNSComp
    MDNSComp->>Network: Multicast Query: SRV WebServerA._http._tcp.local?
    activate Network
    Network-->>ResponderA: Forward Query
    activate ResponderA
    ResponderA-->>Network: Multicast Response: SRV (target=esp32a.local, port=80)
    deactivate ResponderA
    Network-->>MDNSComp: Collect Response
    deactivate Network
    MDNSComp-->>ClientApp: Return result_srv (hostname="esp32a.local", port=80)
    deactivate MDNSComp

    ClientApp->>MDNSComp: Call mdns_query_txt("WebServerA", "_http", "_tcp", timeout, &result_txt)
    activate MDNSComp
    MDNSComp->>Network: Multicast Query: TXT WebServerA._http._tcp.local?
    activate Network
    Network-->>ResponderA: Forward Query
    activate ResponderA
    ResponderA-->>Network: Multicast Response: TXT (e.g., "path=/data", "auth=false")
    deactivate ResponderA
    Network-->>MDNSComp: Collect Response
    deactivate Network
    MDNSComp-->>ClientApp: Return result_txt (txt_items list)
    deactivate MDNSComp

    ClientApp->>MDNSComp: Call mdns_query_a("esp32a.local", timeout, &result_a)
    activate MDNSComp
    MDNSComp->>Network: Multicast Query: A esp32a.local?
    activate Network
    Network-->>ResponderA: Forward Query (or any device knowing esp32a.local)
    activate ResponderA
    ResponderA-->>Network: Multicast Response: A (IP=192.168.1.10)
    deactivate ResponderA
    Network-->>MDNSComp: Collect Response
    deactivate Network
    MDNSComp-->>ClientApp: Return result_a (ip_addr="192.168.1.10")
    deactivate MDNSComp

    ClientApp->>ClientApp: Use IP (192.168.1.10) and Port (80) to connect to WebServerA
    ClientApp->>MDNSComp: Call mdns_query_results_free(results_ptr)
    ClientApp->>MDNSComp: Call mdns_query_results_free(result_srv)
    ClientApp->>MDNSComp: Call mdns_query_results_free(result_txt)
    ClientApp->>MDNSComp: Call mdns_query_results_free(result_a)
    note right of ClientApp: Repeat SRV, TXT, A queries for "WebServerB" and free results.


All mdns_query_* functions are blocking. They will wait for the specified timeout or until max_results are found. It’s crucial to call these from tasks that can afford to block, or manage them appropriately in an event-driven architecture.

Practical Examples

This section provides practical examples for mDNS hostname and service advertisement, and service discovery. A basic Wi-Fi station setup is assumed.

Common Wi-Fi Station Setup

For brevity, the following Wi-Fi station initialization function will be assumed for the examples. Include it in your main.c.

C
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"

// Wi-Fi Configuration - Replace with your network credentials
#define EXAMPLE_ESP_WIFI_SSID      "YOUR_WIFI_SSID"
#define EXAMPLE_ESP_WIFI_PASS      "YOUR_WIFI_PASSWORD"
#define EXAMPLE_ESP_MAXIMUM_RETRY  5

static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1

static const char *TAG_WIFI = "wifi_station"; // Generic tag for Wi-Fi
static int s_retry_num = 0;

static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                               int32_t event_id, void* event_data) {
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG_WIFI, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGW(TAG_WIFI,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG_WIFI, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void connect_wifi_sta(void) {
    s_wifi_event_group = xEventGroupCreate();

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = { .ssid = EXAMPLE_ESP_WIFI_SSID, .password = EXAMPLE_ESP_WIFI_PASS },
    };
    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_LOGI(TAG_WIFI, "wifi_init_sta finished.");

    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG_WIFI, "connected to ap SSID:%s", EXAMPLE_ESP_WIFI_SSID);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGE(TAG_WIFI, "Failed to connect to SSID:%s", EXAMPLE_ESP_WIFI_SSID);
        // Handle error, perhaps by re-initializing or notifying user
    } else {
        ESP_LOGE(TAG_WIFI, "UNEXPECTED WIFI EVENT");
    }
}

Project Setup

  1. Create a new ESP-IDF project (e.g., esp32_mdns_discovery).
  2. Add the common Wi-Fi STA code above to your main/main.c.
  3. Ensure your sdkconfig has mDNS enabled (CONFIG_MDNS_ENABLED=y). This is usually default.

Example 1: Hostname and Basic Service Advertisement

This example advertises the ESP32 with a hostname and a simple custom service.

C
// In main.c, add mDNS include
#include "mdns.h"

static const char *TAG_MDNS_ADV = "mdns_advertiser";

#define MDNS_HOSTNAME "my-esp32-device"
#define MDNS_INSTANCE "ESP32 Advanced Gizmo"

void start_mdns_advertising_service(void) {
    ESP_LOGI(TAG_MDNS_ADV, "Initializing mDNS...");
    ESP_ERROR_CHECK(mdns_init());
    ESP_LOGI(TAG_MDNS_ADV, "Setting mDNS hostname to '%s'", MDNS_HOSTNAME);
    ESP_ERROR_CHECK(mdns_hostname_set(MDNS_HOSTNAME));
    ESP_LOGI(TAG_MDNS_ADV, "Setting mDNS instance name to '%s'", MDNS_INSTANCE);
    ESP_ERROR_CHECK(mdns_instance_name_set(MDNS_INSTANCE));

    ESP_LOGI(TAG_MDNS_ADV, "Advertising custom service...");
    mdns_txt_item_t custom_service_txt[2] = {
        {"status", "online"},
        {"chip", "esp32"} // Update based on your chip
    };

    ESP_ERROR_CHECK(mdns_service_add("MyESP32Service",    // Instance name for this service
                                     "_mycustomsrv",      // Service Type
                                     "_tcp",              // Protocol
                                     1234,                // Port
                                     custom_service_txt,  // TXT records
                                     2                    // Number of TXT records
                                     ));
    ESP_LOGI(TAG_MDNS_ADV, "Service '_mycustomsrv._tcp' on port 1234 advertised.");
    ESP_LOGI(TAG_MDNS_ADV, "Device discoverable as %s.local", MDNS_HOSTNAME);
}

void app_main(void) {
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    connect_wifi_sta(); // Uses the common Wi-Fi function

    // Check if Wi-Fi connected before starting mDNS
    EventBits_t bits = xEventGroupGetBits(s_wifi_event_group);
    if (bits & WIFI_CONNECTED_BIT) {
        start_mdns_advertising_service();
    } else {
        ESP_LOGE(TAG_MDNS_ADV, "Wi-Fi not connected. mDNS service not started.");
    }
}

Build, Flash & Observe:

  1. Replace Wi-Fi credentials. Build and flash.
  2. Use an mDNS browser on your PC (e.g., Avahi Discover on Linux, Bonjour Browser on macOS/Windows).
  3. You should see my-esp32-device.local and a service instance “MyESP32Service” under _mycustomsrv._tcp with the specified TXT records.

Example 2: Advanced Service Advertisement & TXT Record Management

This example shows advertising multiple services and updating TXT records for one of them.

C
// In main.c (assuming Wi-Fi setup and mDNS includes are present)
static const char *TAG_MDNS_ADV_MULTI = "mdns_multi_service";

#define MDNS_HOSTNAME_MULTI "esp32-multi-node"

// Keep track of TXT data for dynamic updates
static mdns_txt_item_t http_txt_records[] = {
    {"path", "/index.html"},
    {"data_version", "1"}
};
const size_t http_txt_records_count = sizeof(http_txt_records) / sizeof(http_txt_records[0]);

void update_http_service_txt_task(void *pvParameters) {
    char new_version_str[5];
    int version = 1;

    for (;;) {
        vTaskDelay(pdMS_TO_TICKS(30000)); // Update every 30 seconds
        version++;
        sprintf(new_version_str, "%d", version);
        http_txt_records[1].value = new_version_str; // Update the "data_version" value

        esp_err_t err = mdns_service_txt_set("_http", "_tcp", http_txt_records, http_txt_records_count);
        if (err == ESP_OK) {
            ESP_LOGI(TAG_MDNS_ADV_MULTI, "HTTP service TXT 'data_version' updated to %s", new_version_str);
        } else {
            ESP_LOGE(TAG_MDNS_ADV_MULTI, "Failed to update HTTP service TXT: %s", esp_err_to_name(err));
        }
    }
}


void start_multiple_mdns_services(void) {
    ESP_LOGI(TAG_MDNS_ADV_MULTI, "Initializing mDNS...");
    ESP_ERROR_CHECK(mdns_init());
    ESP_LOGI(TAG_MDNS_ADV_MULTI, "Setting mDNS hostname to '%s'", MDNS_HOSTNAME_MULTI);
    ESP_ERROR_CHECK(mdns_hostname_set(MDNS_HOSTNAME_MULTI));
    // Instance name not strictly needed if services define their own

    ESP_LOGI(TAG_MDNS_ADV_MULTI, "Advertising HTTP service...");
    ESP_ERROR_CHECK(mdns_service_add("ESP32 Web Portal", "_http", "_tcp", 80, http_txt_records, http_txt_records_count));
    ESP_LOGI(TAG_MDNS_ADV_MULTI, "HTTP service advertised on port 80.");

    ESP_LOGI(TAG_MDNS_ADV_MULTI, "Advertising FTP service (placeholder)...");
    mdns_txt_item_t ftp_txt[] = { {"readonly", "true"} };
    ESP_ERROR_CHECK(mdns_service_add("ESP32 FTP Access", "_ftp", "_tcp", 21, ftp_txt, 1));
    ESP_LOGI(TAG_MDNS_ADV_MULTI, "FTP service advertised on port 21.");

    // Start task to update HTTP TXT records
    xTaskCreate(update_http_service_txt_task, "update_txt_task", 2048, NULL, 5, NULL);
}

void app_main_example2(void) { // Rename app_main for this example
    // NVS and Wi-Fi init (as before)
    connect_wifi_sta();
    EventBits_t bits = xEventGroupGetBits(s_wifi_event_group);
    if (bits & WIFI_CONNECTED_BIT) {
        start_multiple_mdns_services();
    }
}

Build, Flash & Observe:

  1. Use app_main_example2 as your app_main.
  2. Build and flash.
  3. Use an mDNS browser. You should see two services (_http._tcp and _ftp._tcp) advertised by esp32-multi-node.local.
  4. Observe the TXT records for the HTTP service; the data_version should update periodically.

Example 3: Discovering Services on the Network (Browse)

This example demonstrates how the ESP32 can discover _http._tcp services on the local network.

C
// In main.c (assuming Wi-Fi setup and mDNS includes)
static const char *TAG_MDNS_BROWSER = "mdns_browser";

// Task to perform mDNS discovery
void mdns_discover_services_task(void *pvParameters) {
    ESP_LOGI(TAG_MDNS_BROWSER, "Starting mDNS service discovery for _http._tcp services...");
    
    // It's good to initialize mDNS for the local device too, even if just Browse
    // However, for pure Browse, mdns_init() might not be strictly necessary if the
    // query functions handle it. Best practice is to initialize it if mDNS is used.
    // esp_err_t err_init = mdns_init();
    // if (err_init != ESP_OK && err_init != ESP_ERR_INVALID_STATE) { // ESP_ERR_INVALID_STATE if already inited
    //    ESP_LOGE(TAG_MDNS_BROWSER, "mDNS Init failed: %s", esp_err_to_name(err_init));
    //    vTaskDelete(NULL);
    //    return;
    // }


    for (int i = 0; i < 5; i++) { // Query a few times
        ESP_LOGI(TAG_MDNS_BROWSER, "Querying for PTR records of _http._tcp.local. (Attempt %d)", i + 1);
        mdns_result_t *results = NULL;
        esp_err_t err = mdns_query_ptr("_http", "_tcp", 3000, 20, &results); // 3s timeout, max 20 results

        if (err != ESP_OK) {
            ESP_LOGE(TAG_MDNS_BROWSER, "PTR Query failed: %s", esp_err_to_name(err));
            vTaskDelay(pdMS_TO_TICKS(5000)); // Wait before retrying
            continue;
        }
        if (!results) {
            ESP_LOGW(TAG_MDNS_BROWSER, "No _http._tcp services found in this query iteration.");
            vTaskDelay(pdMS_TO_TICKS(5000));
            continue;
        }

        ESP_LOGI(TAG_MDNS_BROWSER, "Found services:");
        mdns_result_t *r = results;
        while (r) {
            ESP_LOGI(TAG_MDNS_BROWSER, "  PTR: Instance: %s", r->instance_name ? r->instance_name : "(null)");

            // Now get SRV record for this instance
            mdns_result_t *srv_result = NULL;
            if (r->instance_name) { // r->instance_name could be NULL if PTR result is malformed by responder
                err = mdns_query_srv(r->instance_name, "_http", "_tcp", 500, &srv_result);
                if (err == ESP_OK && srv_result) {
                    ESP_LOGI(TAG_MDNS_BROWSER, "    SRV: Hostname: %s.local, Port: %u",
                             srv_result->hostname ? srv_result->hostname : "(null)", srv_result->port);
                    
                    // Now get A record for the hostname from SRV
                    if (srv_result->hostname) {
                        mdns_result_t *a_result = NULL;
                        err = mdns_query_a(srv_result->hostname, 500, &a_result);
                        if (err == ESP_OK && a_result) {
                            if(a_result->addr) { // addr is a linked list of esp_ip4_addr_t
                                ESP_LOGI(TAG_MDNS_BROWSER, "      A  : IP: " IPSTR, IP2STR(&(a_result->addr->addr)));
                            } else {
                                ESP_LOGW(TAG_MDNS_BROWSER, "      A  : No IP address in A record result for %s.local", srv_result->hostname);
                            }
                            mdns_query_results_free(a_result);
                        } else {
                             ESP_LOGW(TAG_MDNS_BROWSER, "    A Query for %s.local failed or no result: %s", srv_result->hostname, esp_err_to_name(err));
                        }
                    }
                    // Get TXT records
                    mdns_result_t *txt_result = NULL;
                    err = mdns_query_txt(r->instance_name, "_http", "_tcp", 500, &txt_result);
                     if (err == ESP_OK && txt_result) {
                        ESP_LOGI(TAG_MDNS_BROWSER, "    TXT Records:");
                        for(size_t j=0; j<txt_result->txt_count; j++){
                            ESP_LOGI(TAG_MDNS_BROWSER, "      %s = %s", txt_result->txt[j].key, txt_result->txt[j].value ? txt_result->txt[j].value : "(null)");
                        }
                        mdns_query_results_free(txt_result);
                    } else {
                        ESP_LOGW(TAG_MDNS_BROWSER, "    TXT Query for %s failed or no result: %s", r->instance_name, esp_err_to_name(err));
                    }
                    mdns_query_results_free(srv_result);
                } else {
                    ESP_LOGW(TAG_MDNS_BROWSER, "  SRV Query for %s failed or no result: %s", r->instance_name, esp_err_to_name(err));
                }
            }
            r = r->next; // Move to the next PTR result
        }
        mdns_query_results_free(results); // Free the linked list of PTR results
        ESP_LOGI(TAG_MDNS_BROWSER, "--------------------------------------------------");
        vTaskDelay(pdMS_TO_TICKS(10000)); // Query every 10 seconds
    }
    ESP_LOGI(TAG_MDNS_BROWSER, "mDNS discovery task finished iterations.");
    vTaskDelete(NULL);
}


void app_main_example3(void) { // Rename app_main for this example
    // NVS and Wi-Fi init (as before)
    connect_wifi_sta();
    EventBits_t bits = xEventGroupGetBits(s_wifi_event_group);
    if (bits & WIFI_CONNECTED_BIT) {
        // It's good to initialize mDNS for the local device,
        // especially if it might also respond to queries or if query functions rely on it being active.
        ESP_ERROR_CHECK(mdns_init());
        // No need to set hostname if only Browse, but doesn't hurt.
        // ESP_ERROR_CHECK(mdns_hostname_set("esp32-browser-node"));

        xTaskCreate(mdns_discover_services_task, "mdns_discover_task", 4096 * 2, NULL, 5, NULL);
    } else {
         ESP_LOGE(TAG_MDNS_BROWSER, "Wi-Fi not connected. mDNS discovery not started.");
    }
}

Build, Flash & Observe:

  1. Ensure you have another device on your network advertising an HTTP service via mDNS (e.g., another ESP32 running Example 1 or 2, or a PC with a web server advertised via Avahi/Bonjour).
  2. Use app_main_example3 as your app_main. Build and flash.
  3. Observe the serial monitor. The ESP32 should periodically scan for _http._tcp services and print details of what it finds. Pay attention to how results are iterated and freed.

Warning: The discovery example queries for PTR, then SRV, then A, then TXT for each service. This can generate significant network traffic and take time if many services are present. Timeouts should be chosen carefully. Properly freeing all mdns_result_t lists is critical to avoid memory leaks.

Variant Notes

  • Consistency: The mdns component and its functionalities (hostname advertisement, service advertisement, service discovery) are primarily software-based and work consistently across all ESP32 variants that support IP networking via ESP-IDF (ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2).
  • Resource Usage:
    • The mDNS service maintains tables of known local services and hostnames, consuming RAM. The amount depends on the number of services advertised by the ESP32 and the number of other mDNS devices/services on the network that it might cache.
    • Each mDNS query can also consume memory for storing results until they are freed. Tasks performing queries, especially Browse, need adequate stack space (e.g., 4KB-8KB might be necessary for complex scenarios).
    • Highly active mDNS networks can lead to increased CPU usage for processing multicast packets.
  • ESP32-H2 & ESP32-C6 (802.15.4 Radio): These variants also support Thread. While this chapter focuses on mDNS over IP (Wi-Fi/Ethernet), Thread networks use similar concepts for service discovery (often involving an SRP server and mDNS over Thread). The ESP-IDF mdns component is primarily for IP interfaces. For Thread service discovery, you’d typically use APIs related to the OpenThread stack if that’s your primary network. If the ESP32-H2/C6 is acting as a border router, it might bridge mDNS services between Wi-Fi/Ethernet and Thread.

Resource Considerations:

Resource Impact Factor / Consideration Notes & Mitigation
RAM (Memory) – Number of services advertised by the ESP32.
– Number of TXT records and their size.
– Caching of discovered services/hostnames from the network.
– Storing query results (mdns_result_t lists).
– Advertise only necessary services and TXT records.
– Keep TXT record values concise.
Crucially, always free query results using mdns_query_results_free() to prevent memory leaks.
– The mDNS component has internal limits, but be mindful in memory-constrained applications.
CPU Usage – Processing incoming multicast mDNS packets (queries and announcements).
– Responding to queries for its own hostname/services.
– Performing service discovery queries.
– Generally low, but can increase on very “chatty” mDNS networks with many devices.
– Blocking query functions (mdns_query_*) will occupy the calling task’s CPU time until timeout or completion. Run queries in tasks that can afford to block.
Task Stack Space – Tasks performing mDNS queries, especially those handling potentially large result lists or complex parsing. – Allocate sufficient stack space for tasks involved in mDNS operations (e.g., 4KB-8KB or more, depending on query complexity and number of results handled simultaneously). Monitor stack high water mark during development.
Flash (Program Space) – Size of the mDNS component library code. – Relatively fixed. Ensure CONFIG_MDNS_ENABLED=y is set in sdkconfig (usually default).
Network Bandwidth – mDNS uses multicast, so packets are sent to all devices on the local network segment.
– Frequent queries or numerous devices announcing many services can increase network traffic.
– Use reasonable timeouts for queries.
– Avoid excessively frequent polling for services if not necessary.
– Be mindful of the number of services and TXT record updates.

Common Mistakes & Troubleshooting Tips

Mistake / Issue Symptom(s) Troubleshooting / Solution
mDNS Initialization Problems – Device not discoverable (.local hostname doesn’t resolve).
– Errors like ESP_ERR_INVALID_STATE or crashes during mdns_init() or other mDNS calls.
Fixes:
  • Ensure mdns_init() is called once globally.
  • Call mdns_init() after network stack (esp_netif_init(), Wi-Fi/Ethernet connection established, IP address obtained).
  • If re-initializing, call mdns_free() first.
  • Check return codes of all mDNS functions for errors.
Invalid Hostnames or Service Names – Device/service not appearing in mDNS browsers, or appearing with unexpected names.
mdns_hostname_set() or mdns_service_add() might return errors.
Fixes:
  • Hostnames: Use letters, numbers, hyphens. Max length 63 chars. Avoid spaces or other special symbols. (e.g., my-device)
  • Service Types: Must follow _service._proto convention (e.g., _http._tcp, _myservice._udp). Leading underscore is critical.
  • Instance Names: Can be more descriptive but avoid problematic characters.
Forgetting to Free Query Results – Memory leaks over time, eventually leading to crashes or ESP_ERR_NO_MEM errors, especially with frequent service discovery. Fix:
  • Always call mdns_query_results_free(results) for every linked list of mdns_result_t obtained from mdns_query_ptr(), mdns_query_srv(), mdns_query_txt(), mdns_query_a(), and mdns_query_aaaa() once you are done processing them.
Network Interface Not Ready / No IP – mDNS operations fail silently or return errors.
– Device not visible on the network.
Fix:
  • Ensure Wi-Fi or Ethernet is connected and an IP address is acquired before calling mdns_init(), setting hostname, or adding services.
  • Use event groups (e.g., WIFI_CONNECTED_BIT, IP_EVENT_STA_GOT_IP) or other synchronization mechanisms.
Firewall Blocking mDNS Traffic – ESP32 can’t discover services on a PC, or PC can’t discover ESP32 services.
– Works between ESP32s but not with a PC/Mac.
Fix:
  • Check firewall settings on your PC, router, or any network security software.
  • Ensure UDP traffic on port 5353 (both inbound and outbound) is allowed for the local network segment.
  • Router settings might also have “multicast filtering” or “IGMP snooping” settings that could interfere.
Misunderstanding Blocking Queries – Application becomes unresponsive or seems to hang.
– Watchdog timer might trigger if a high-priority task blocks for too long.
Fix:
  • All mdns_query_* functions are blocking. They wait for the specified timeout or until max_results are found.
  • Run mDNS query operations in dedicated FreeRTOS tasks with appropriate stack sizes and priorities, not from critical loops or event handlers that need to return quickly.
  • Choose timeouts carefully. Short timeouts might miss responses on slow networks; long timeouts increase blocking time.
Incorrect TXT Record Formatting/Handling – TXT records not appearing or appearing garbled in mDNS browsers.
mdns_service_txt_set() fails or has no effect.
Fixes:
  • TXT records are arrays of mdns_txt_item_t, each with a key and value (both C strings).
  • Ensure strings are null-terminated.
  • Total length of a single TXT record string (e.g., “key=value”) should not exceed 255 bytes. Multiple strings can be used.
  • When updating with mdns_service_txt_set(), provide the complete new set of TXT records.

Exercises

  1. Dynamic Service TXT Update:
    • Create an ESP32 application that advertises a service (e.g., _sensor._tcp).
    • Simulate a sensor reading (e.g., a random number or ADC reading).
    • Update a TXT record for this service (e.g., value=XXX) with the new sensor reading every 5 seconds using mdns_service_txt_set().
    • Verify the TXT record updates using an mDNS browser tool.
  2. Conditional Service Advertisement:
    • Implement a service that is only advertised if a certain condition is met (e.g., a button is pressed, or a specific value is read from a sensor).
    • Use mdns_service_add() to advertise when the condition is true and mdns_service_remove() (or mdns_service_stop() if simply stopping all services associated with the mdns component instance) when it becomes false.
    • Observe the service appearing and disappearing in an mDNS browser.
  3. Targeted Service Discovery and Interaction:
    • Have one ESP32 (Advertiser) advertise a unique custom service, e.g., _mygame._tcp with its MAC address in the instance name or a TXT record.
    • Have a second ESP32 (Discoverer) specifically query for services of type _mygame._tcp.
    • When the Discoverer finds the Advertiser, it should log the Advertiser’s full details (hostname, IP, port, MAC from TXT).
    • (Bonus: Establish a simple TCP or UDP communication between them after discovery.)
  4. Robust Service Browser with Multiple Instances:
    • Modify Example 3 (Service Discovery) to handle multiple instances of the same service type more robustly.
    • Instead of just printing, store the discovered service details (instance name, hostname, IP, port, key TXT items) in an array or linked list.
    • Implement a way to refresh this list periodically and note any changes (new services, services no longer found after a few failed re-queries – simulating disappearance). This explores the challenges of maintaining an active list of dynamic services.

Summary

  • Zeroconf (Zero-Configuration Networking) simplifies local network device interaction, with mDNS and DNS-SD as key components.
  • mDNS allows devices to register and resolve hostnames ending in .local via UDP multicast on port 5353 without a central DNS server. ESP-IDF provides the mdns component for this.
  • DNS-SD uses standard DNS record types (PTR, SRV, TXT) over mDNS to advertise and discover network services.
  • ESP-IDF’s mdns component offers functions to:
    • Initialize the mDNS system (mdns_init).
    • Set hostnames and instance names (mdns_hostname_set, mdns_instance_name_set).
    • Advertise services with TXT records (mdns_service_add, mdns_service_txt_set).
    • Discover services by querying for PTR, SRV, TXT, and A/AAAA records (mdns_query_ptr, mdns_query_srv, etc.).
  • Proper resource management, especially freeing results from query functions (mdns_query_results_free), is crucial.
  • mDNS and DNS-SD are software features generally consistent across all network-enabled ESP32 variants.

Further Reading

  • ESP-IDF mDNS Documentation:
  • Official RFCs:
  • Zeroconf.org:
  • DNS-SD Resource Page:
    • www.dns-sd.org: Extensive information on DNS-SD, including registered service types.
  • mDNS Browser Tools:
    • Avahi (Linux): avahi-discover, avahi-browse command-line tools.
    • Bonjour (Apple): Integrated into macOS. “Bonjour Browser” or “Discovery” utilities.
    • Third-party tools for Windows: Search for “Bonjour Browser for Windows” or “mDNS Browser Windows”.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top