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:
- 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-IDFmdns
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. - 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 port5353
using multicast addresses (224.0.0.251
for IPv4,FF02::FB
for IPv6). - 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")
. Themdns
service will then attempt to claimmy-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 ofmdns_txt_item_t
structures for associated TXT record data.num_txt_records
: Number of items in thetxt_records
array. The full service name advertised will beinstance_name.service_type.proto.local.
, and a PTR record will point fromservice_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 akey
andvalue
(both strings). Example:{"path", "/status"}, {"version", "2.1"}
. TXT records can be updated dynamically for an existing service usingmdns_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. |
|
mdns_service_txt_set() |
Updates or sets the TXT records for an already advertised service. Allows dynamic changes to service metadata. |
|
mdns_service_remove() |
Stops advertising a specific service type and protocol combination. |
|
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.
- 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 ofmdns_result_t
structures if successful. Each result corresponding to a PTR record will contain the instance name of a discovered service inresults->instance_name
.
- Use
- 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 singlemdns_result_t*
) will contain the SRV record details:result_srv->hostname
(target hostname),result_srv->port
.
- For each instance name obtained from the PTR query, use
- 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 ofmdns_txt_item_t
containing the key-value pairs.
- Similarly, use
- 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 ofesp_ip4_addr_t
) will contain the IP address(es). For IPv6, usemdns_query_aaaa()
.
- The hostname obtained from the SRV record (e.g.,
- Freeing Results: All
mdns_result_t
structures and their internal lists obtained from query functions are dynamically allocated. They must be freed usingmdns_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. |
|
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. |
|
Single mdns_result_t : hostname , port . |
mdns_query_txt() |
Queries for TXT records of a specific service instance. Finds additional key-value metadata. |
|
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). |
|
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
.
#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
- Create a new ESP-IDF project (e.g.,
esp32_mdns_discovery
). - Add the common Wi-Fi STA code above to your
main/main.c
. - 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.
// 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:
- Replace Wi-Fi credentials. Build and flash.
- Use an mDNS browser on your PC (e.g., Avahi Discover on Linux, Bonjour Browser on macOS/Windows).
- 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.
// 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:
- Use
app_main_example2
as yourapp_main
. - Build and flash.
- Use an mDNS browser. You should see two services (
_http._tcp
and_ftp._tcp
) advertised byesp32-multi-node.local
. - 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.
// 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:
- 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).
- Use
app_main_example3
as yourapp_main
. Build and flash. - 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:
|
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:
|
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:
|
Network Interface Not Ready / No IP | – mDNS operations fail silently or return errors. – Device not visible on the network. |
Fix:
|
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:
|
Misunderstanding Blocking Queries | – Application becomes unresponsive or seems to hang. – Watchdog timer might trigger if a high-priority task blocks for too long. |
Fix:
|
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:
|
Exercises
- 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 usingmdns_service_txt_set()
. - Verify the TXT record updates using an mDNS browser tool.
- Create an ESP32 application that advertises a service (e.g.,
- 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 andmdns_service_remove()
(ormdns_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.
- 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.)
- Have one ESP32 (Advertiser) advertise a unique custom service, e.g.,
- 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 themdns
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.).
- Initialize the mDNS system (
- 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:
- www.zeroconf.org: General information about Zeroconf technologies.
- 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”.
- Avahi (Linux):
