Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion components/mdns/include/mdns.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -757,6 +757,47 @@ void mdns_query_results_free(mdns_result_t *results);
*/
esp_err_t mdns_query_ptr(const char *service_type, const char *proto, uint32_t timeout, size_t max_results, mdns_result_t **results);

/**
* @brief Query mDNS for service subtype (Selective Instance Enumeration per RFC 6763 Section 7.1)
*
* Sends a PTR query for `_subtype._sub._service_type._proto.local` to discover
* only the instances of a service that have the given subtype.
*
* @param service_type service type (_http, _arduino, _ftp etc.)
* @param proto service protocol (_tcp, _udp, etc.)
* @param subtype the subtype to query for (e.g. "_printer")
* @param timeout time in milliseconds to wait for answer.
* @param max_results maximum results to be collected
* @param results pointer to the results of the query
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_STATE mDNS is not running
* - ESP_ERR_NO_MEM memory error
* - ESP_ERR_INVALID_ARG parameter error
*/
esp_err_t mdns_query_ptr_subtype(const char *service_type, const char *proto, const char *subtype,
uint32_t timeout, size_t max_results, mdns_result_t **results);

/**
* @brief Query mDNS for service subtype asynchronously (Selective Instance Enumeration per RFC 6763 Section 7.1)
*
* Sends a PTR query for `_subtype._sub._service_type._proto.local`.
* The search object must be checked for progress and deleted manually.
*
* @param service_type service type (_http, _arduino, _ftp etc.)
* @param proto service protocol (_tcp, _udp, etc.)
* @param subtype the subtype to query for (e.g. "_printer")
* @param timeout time in milliseconds during which mDNS query is active
* @param max_results maximum results to be collected
* @param notifier Notification function to be called when the result is ready, can be NULL
*
* @return mdns_search_once_s pointer to new search object if query initiated successfully.
* NULL otherwise.
*/
mdns_search_once_t *mdns_query_async_new_subtype(const char *service_type, const char *proto, const char *subtype,
uint32_t timeout, size_t max_results, mdns_query_notify_t notifier);

/**
* @brief Query mDNS for SRV record
*
Expand Down Expand Up @@ -919,6 +960,20 @@ esp_err_t mdns_netif_action(esp_netif_t *esp_netif, mdns_event_actions_t event_a
*/
mdns_browse_t *mdns_browse_new(const char *service, const char *proto, mdns_browse_notify_t notifier);

/**
* @brief Browse mDNS for a service subtype `_subtype._sub._service._proto`
* (Selective Instance Enumeration per RFC 6763 Section 7.1).
*
* @param service Pointer to the `_service` which will be browsed.
* @param proto Pointer to the `_proto` which will be browsed.
* @param subtype Pointer to the `_subtype` to restrict browsing to. Can be NULL for standard browse.
* @param notifier The callback which will be called when the browsing service changed.
* @return mdns_browse_t pointer to new browse object if initiated successfully.
* NULL otherwise.
*/
mdns_browse_t *mdns_browse_new_subtype(const char *service, const char *proto, const char *subtype,
mdns_browse_notify_t notifier);

/**
* @brief Stop the `_service._proto` browse.
* @param service Pointer to the `_service` which will be browsed.
Expand All @@ -930,6 +985,18 @@ mdns_browse_t *mdns_browse_new(const char *service, const char *proto, mdns_brow
*/
esp_err_t mdns_browse_delete(const char *service, const char *proto);

/**
* @brief Stop the `_subtype._sub._service._proto` browse.
* @param service Pointer to the `_service` which will be browsed.
* @param proto Pointer to the `_proto` which will be browsed.
* @param subtype Pointer to the `_subtype`. Can be NULL to match a browse without subtype.
* @return
* - ESP_OK success.
* - ESP_ERR_FAIL mDNS is not running or the browsing was never started.
* - ESP_ERR_NO_MEM memory error.
*/
esp_err_t mdns_browse_delete_subtype(const char *service, const char *proto, const char *subtype);

#ifdef __cplusplus
}
#endif
Expand Down
55 changes: 45 additions & 10 deletions components/mdns/mdns_browser.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -50,6 +50,7 @@ static void browse_item_free(mdns_browse_t *browse)
{
mdns_mem_free(browse->service);
mdns_mem_free(browse->proto);
mdns_mem_free(browse->subtype);
if (browse->result) {
mdns_priv_query_results_free(browse->result);
}
Expand Down Expand Up @@ -79,12 +80,12 @@ static void browse_sync(mdns_browse_sync_t *browse_sync)
*/
static void browse_send(mdns_browse_t *browse, mdns_if_t interface)
{
// Using search once for sending the PTR query
mdns_search_once_t search = {0};

search.instance = NULL;
search.service = browse->service;
search.proto = browse->proto;
search.subtype = browse->subtype;
search.type = MDNS_TYPE_PTR;
search.unicast = false;
search.result = NULL;
Expand Down Expand Up @@ -113,6 +114,23 @@ void mdns_priv_browse_free(void)
}
}

static bool browse_match(const mdns_browse_t *a, const mdns_browse_t *b)
{
if (strlen(a->service) != strlen(b->service) || memcmp(a->service, b->service, strlen(a->service)) != 0) {
return false;
}
if (strlen(a->proto) != strlen(b->proto) || memcmp(a->proto, b->proto, strlen(a->proto)) != 0) {
return false;
}
if (a->subtype == NULL && b->subtype == NULL) {
return true;
}
if (a->subtype == NULL || b->subtype == NULL) {
return false;
}
return strlen(a->subtype) == strlen(b->subtype) && memcmp(a->subtype, b->subtype, strlen(a->subtype)) == 0;
}

/**
* @brief Mark browse as finished, remove and free it from browse chain
*/
Expand All @@ -122,8 +140,7 @@ static void browse_finish(mdns_browse_t *browse)
mdns_browse_t *b = s_browse;
mdns_browse_t *target_free = NULL;
while (b) {
if (strlen(b->service) == strlen(browse->service) && memcmp(b->service, browse->service, strlen(b->service)) == 0 &&
strlen(b->proto) == strlen(browse->proto) && memcmp(b->proto, browse->proto, strlen(b->proto)) == 0) {
if (browse_match(b, browse)) {
target_free = b;
b = b->next;
queueDetach(mdns_browse_t, s_browse, target_free);
Expand All @@ -138,7 +155,8 @@ static void browse_finish(mdns_browse_t *browse)
/**
* @brief Allocate new browse structure
*/
static mdns_browse_t *browse_init(const char *service, const char *proto, mdns_browse_notify_t notifier)
static mdns_browse_t *browse_init(const char *service, const char *proto, const char *subtype,
mdns_browse_notify_t notifier)
{
mdns_browse_t *browse = (mdns_browse_t *)mdns_mem_malloc(sizeof(mdns_browse_t));

Expand Down Expand Up @@ -166,6 +184,14 @@ static mdns_browse_t *browse_init(const char *service, const char *proto, mdns_b
}
}

if (!mdns_utils_str_null_or_empty(subtype)) {
browse->subtype = mdns_mem_strndup(subtype, MDNS_NAME_BUF_LEN - 1);
if (!browse->subtype) {
browse_item_free(browse);
return NULL;
}
}

browse->notifier = notifier;
return browse;
}
Expand All @@ -178,10 +204,8 @@ static void browse_add(mdns_browse_t *browse)
browse->state = BROWSE_RUNNING;
mdns_browse_t *queue = s_browse;
bool found = false;
// looking for this browse in active browses
while (queue) {
if (strlen(queue->service) == strlen(browse->service) && memcmp(queue->service, browse->service, strlen(queue->service)) == 0 &&
strlen(queue->proto) == strlen(browse->proto) && memcmp(queue->proto, browse->proto, strlen(queue->proto)) == 0) {
if (browse_match(queue, browse)) {
found = true;
break;
}
Expand Down Expand Up @@ -615,14 +639,20 @@ esp_err_t mdns_priv_browse_sync(mdns_browse_sync_t *browse_sync)
* @defgroup MDNS_PUBCLIC_API
*/
mdns_browse_t *mdns_browse_new(const char *service, const char *proto, mdns_browse_notify_t notifier)
{
return mdns_browse_new_subtype(service, proto, NULL, notifier);
}

mdns_browse_t *mdns_browse_new_subtype(const char *service, const char *proto, const char *subtype,
mdns_browse_notify_t notifier)
{
mdns_browse_t *browse = NULL;

if (!mdns_priv_is_server_init() || mdns_utils_str_null_or_empty(service) || mdns_utils_str_null_or_empty(proto)) {
return NULL;
}

browse = browse_init(service, proto, notifier);
browse = browse_init(service, proto, subtype, notifier);
if (!browse) {
return NULL;
}
Expand All @@ -636,14 +666,19 @@ mdns_browse_t *mdns_browse_new(const char *service, const char *proto, mdns_brow
}

esp_err_t mdns_browse_delete(const char *service, const char *proto)
{
return mdns_browse_delete_subtype(service, proto, NULL);
}

esp_err_t mdns_browse_delete_subtype(const char *service, const char *proto, const char *subtype)
{
mdns_browse_t *browse = NULL;

if (!mdns_priv_is_server_init() || mdns_utils_str_null_or_empty(service) || mdns_utils_str_null_or_empty(proto)) {
return ESP_FAIL;
}

browse = browse_init(service, proto, NULL);
browse = browse_init(service, proto, subtype, NULL);
if (!browse) {
return ESP_ERR_NO_MEM;
}
Expand Down
99 changes: 97 additions & 2 deletions components/mdns/mdns_querier.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand All @@ -21,6 +21,9 @@ static mdns_search_once_t *s_search_once;

static esp_err_t send_search_action(mdns_action_type_t type, mdns_search_once_t *search);
static void search_free(mdns_search_once_t *search);
static mdns_search_once_t *search_init_with_subtype(const char *name, const char *service, const char *proto,
const char *subtype, uint16_t type, bool unicast,
uint32_t timeout, uint8_t max_results, mdns_query_notify_t notifier);

void mdns_priv_query_results_free(mdns_result_t *results)
{
Expand Down Expand Up @@ -167,6 +170,7 @@ void mdns_priv_query_free(void)
mdns_mem_free(h->instance);
mdns_mem_free(h->service);
mdns_mem_free(h->proto);
mdns_mem_free(h->subtype);
vSemaphoreDelete(h->done_semaphore);
if (h->result) {
mdns_priv_query_results_free(h->result);
Expand Down Expand Up @@ -249,7 +253,15 @@ mdns_search_once_t *mdns_priv_query_find_from(mdns_search_once_t *s, mdns_name_t
}

if (type == MDNS_TYPE_PTR && type == s->type && !strcasecmp(name->service, s->service) && !strcasecmp(name->proto, s->proto)) {
return s;
if (name->sub) {
if (s->subtype && !strcasecmp(name->host, s->subtype)) {
return s;
}
} else if (!s->subtype) {
return s;
}
s = s->next;
continue;
}

s = s->next;
Expand Down Expand Up @@ -287,6 +299,7 @@ static mdns_tx_packet_t *create_search_packet(mdns_search_once_t *search, mdns_i
q->service = search->service;
q->proto = search->proto;
q->domain = MDNS_UTILS_DEFAULT_DOMAIN;
q->subtype = search->subtype;
q->own_dynamic_memory = false;
queueToEnd(mdns_out_question_t, packet->questions, q);

Expand Down Expand Up @@ -345,6 +358,7 @@ static void search_free(mdns_search_once_t *search)
mdns_mem_free(search->instance);
mdns_mem_free(search->service);
mdns_mem_free(search->proto);
mdns_mem_free(search->subtype);
vSemaphoreDelete(search->done_semaphore);
mdns_mem_free(search);
}
Expand All @@ -354,6 +368,13 @@ static void search_free(mdns_search_once_t *search)
*/
static mdns_search_once_t *search_init(const char *name, const char *service, const char *proto, uint16_t type, bool unicast,
uint32_t timeout, uint8_t max_results, mdns_query_notify_t notifier)
{
return search_init_with_subtype(name, service, proto, NULL, type, unicast, timeout, max_results, notifier);
}

static mdns_search_once_t *search_init_with_subtype(const char *name, const char *service, const char *proto,
const char *subtype, uint16_t type, bool unicast,
uint32_t timeout, uint8_t max_results, mdns_query_notify_t notifier)
{
mdns_search_once_t *search = (mdns_search_once_t *)mdns_mem_malloc(sizeof(mdns_search_once_t));
if (!search) {
Expand Down Expand Up @@ -392,6 +413,14 @@ static mdns_search_once_t *search_init(const char *name, const char *service, co
}
}

if (!mdns_utils_str_null_or_empty(subtype)) {
search->subtype = mdns_mem_strndup(subtype, MDNS_NAME_BUF_LEN - 1);
if (!search->subtype) {
search_free(search);
return NULL;
}
}

search->type = type;
search->unicast = unicast;
search->timeout = timeout;
Expand Down Expand Up @@ -775,6 +804,72 @@ esp_err_t mdns_query_ptr(const char *service, const char *proto, uint32_t timeou
return mdns_query(NULL, service, proto, MDNS_TYPE_PTR, timeout, max_results, results);
}

esp_err_t mdns_query_ptr_subtype(const char *service_type, const char *proto, const char *subtype,
uint32_t timeout, size_t max_results, mdns_result_t **results)
{
mdns_search_once_t *search = NULL;

if (mdns_utils_str_null_or_empty(service_type) || mdns_utils_str_null_or_empty(proto) ||
mdns_utils_str_null_or_empty(subtype)) {
return ESP_ERR_INVALID_ARG;
}

if (!results) {
return ESP_ERR_INVALID_ARG;
}

*results = NULL;

if (!mdns_priv_is_server_init()) {
return ESP_ERR_INVALID_STATE;
}

if (!timeout) {
return ESP_ERR_INVALID_ARG;
}

search = search_init_with_subtype(NULL, service_type, proto, subtype, MDNS_TYPE_PTR, false, timeout,
max_results, NULL);
if (!search) {
return ESP_ERR_NO_MEM;
}

if (send_search_action(ACTION_SEARCH_ADD, search)) {
search_free(search);
return ESP_ERR_NO_MEM;
}
xSemaphoreTake(search->done_semaphore, portMAX_DELAY);

*results = search->result;
search_free(search);

return ESP_OK;
}

mdns_search_once_t *mdns_query_async_new_subtype(const char *service_type, const char *proto, const char *subtype,
uint32_t timeout, size_t max_results, mdns_query_notify_t notifier)
{
mdns_search_once_t *search = NULL;

if (!mdns_priv_is_server_init() || !timeout || mdns_utils_str_null_or_empty(service_type) ||
mdns_utils_str_null_or_empty(proto) || mdns_utils_str_null_or_empty(subtype)) {
return NULL;
}

search = search_init_with_subtype(NULL, service_type, proto, subtype, MDNS_TYPE_PTR, false, timeout,
max_results, notifier);
if (!search) {
return NULL;
}

if (send_search_action(ACTION_SEARCH_ADD, search)) {
search_free(search);
return NULL;
}

return search;
}

esp_err_t mdns_query_srv(const char *instance, const char *service, const char *proto, uint32_t timeout, mdns_result_t **result)
{
if (mdns_utils_str_null_or_empty(instance) || mdns_utils_str_null_or_empty(service) || mdns_utils_str_null_or_empty(proto)) {
Expand Down
Loading
Loading