Files
esp-protocols/components/mdns/mdns_service.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

425 lines
13 KiB
C
Raw Normal View History

/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "sdkconfig.h"
#include "mdns_private.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_check.h"
#include "mdns.h"
#include "mdns_mem_caps.h"
#include "mdns_utils.h"
#include "mdns_browser.h"
#include "mdns_netif.h"
#include "mdns_send.h"
#include "mdns_receive.h"
#include "mdns_querier.h"
#include "mdns_pcb.h"
#include "mdns_responder.h"
#define MDNS_SERVICE_STACK_DEPTH CONFIG_MDNS_TASK_STACK_SIZE
#define MDNS_TASK_PRIORITY CONFIG_MDNS_TASK_PRIORITY
#if (MDNS_TASK_PRIORITY > ESP_TASK_PRIO_MAX)
#error "mDNS task priority is higher than ESP_TASK_PRIO_MAX"
#elif (MDNS_TASK_PRIORITY > ESP_TASKD_EVENT_PRIO)
#warning "mDNS task priority is higher than ESP_TASKD_EVENT_PRIO, mDNS library might not work correctly"
#endif
#define MDNS_TASK_AFFINITY CONFIG_MDNS_TASK_AFFINITY
#define MDNS_SERVICE_LOCK() xSemaphoreTake(s_service_semaphore, portMAX_DELAY)
#define MDNS_SERVICE_UNLOCK() xSemaphoreGive(s_service_semaphore)
static volatile TaskHandle_t s_service_task_handle = NULL;
static SemaphoreHandle_t s_service_semaphore = NULL;
static StackType_t *s_stack_buffer;
static QueueHandle_t s_action_queue;
static esp_timer_handle_t s_timer_handle;
static const char *TAG = "mdns_service";
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
static inline char nibble_to_hex(int var)
{
return var > 9 ? var - 10 + 'a' : var + '0';
}
#endif
/**
* @brief Performs interface changes based on system events or custom commands
*/
static void perform_event_action(mdns_if_t mdns_if, mdns_event_actions_t action)
{
if (!mdns_priv_is_server_init() || mdns_if >= MDNS_MAX_INTERFACES) {
return;
}
if (action & MDNS_EVENT_ENABLE_IP4) {
mdns_priv_pcb_enable(mdns_if, MDNS_IP_PROTOCOL_V4);
}
if (action & MDNS_EVENT_ENABLE_IP6) {
mdns_priv_pcb_enable(mdns_if, MDNS_IP_PROTOCOL_V6);
}
if (action & MDNS_EVENT_DISABLE_IP4) {
mdns_priv_pcb_disable(mdns_if, MDNS_IP_PROTOCOL_V4);
}
if (action & MDNS_EVENT_DISABLE_IP6) {
mdns_priv_pcb_disable(mdns_if, MDNS_IP_PROTOCOL_V6);
}
if (action & MDNS_EVENT_ANNOUNCE_IP4) {
mdns_priv_pcb_announce(mdns_if, MDNS_IP_PROTOCOL_V4, NULL, 0, true);
}
if (action & MDNS_EVENT_ANNOUNCE_IP6) {
mdns_priv_pcb_announce(mdns_if, MDNS_IP_PROTOCOL_V6, NULL, 0, true);
}
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
#ifdef CONFIG_LWIP_IPV4
if (action & MDNS_EVENT_IP4_REVERSE_LOOKUP) {
esp_netif_ip_info_t if_ip_info;
if (esp_netif_get_ip_info(mdns_priv_get_esp_netif(mdns_if), &if_ip_info) == ESP_OK) {
esp_ip4_addr_t *ip = &if_ip_info.ip;
char *reverse_query_name = NULL;
if (asprintf(&reverse_query_name, "%d.%d.%d.%d.in-addr",
esp_ip4_addr4_16(ip), esp_ip4_addr3_16(ip),
esp_ip4_addr2_16(ip), esp_ip4_addr1_16(ip)) > 0 && reverse_query_name) {
ESP_LOGD(TAG, "Registered reverse query: %s.arpa", reverse_query_name);
mdns_priv_delegate_hostname_add(reverse_query_name, NULL);
}
}
}
#endif /* CONFIG_LWIP_IPV4 */
#ifdef CONFIG_LWIP_IPV6
if (action & MDNS_EVENT_IP6_REVERSE_LOOKUP) {
esp_ip6_addr_t addr6;
if (!esp_netif_get_ip6_linklocal(mdns_priv_get_esp_netif(mdns_if), &addr6) && !mdns_utils_ipv6_address_is_zero(addr6)) {
uint8_t *paddr = (uint8_t *)&addr6.addr;
const char sub[] = "ip6";
const size_t query_name_size = 4 * sizeof(addr6.addr) /* (2 nibbles + 2 dots)/per byte of IP address */ + sizeof(sub);
char *reverse_query_name = mdns_mem_malloc(query_name_size);
if (reverse_query_name) {
char *ptr = &reverse_query_name[query_name_size]; // point to the end
memcpy(ptr - sizeof(sub), sub, sizeof(sub)); // copy the IP sub-domain
ptr -= sizeof(sub) + 1; // move before the sub-domain
while (reverse_query_name < ptr) { // continue populating reverse query from the end
*ptr-- = '.'; // nibble by nibble, until we reach the beginning
*ptr-- = nibble_to_hex(((*paddr) >> 4) & 0x0F);
*ptr-- = '.';
*ptr-- = nibble_to_hex((*paddr) & 0x0F);
paddr++;
}
ESP_LOGD(TAG, "Registered reverse query: %s.arpa", reverse_query_name);
mdns_priv_delegate_hostname_add(reverse_query_name, NULL);
}
}
}
#endif /* CONFIG_LWIP_IPV6 */
#endif /* CONFIG_MDNS_RESPOND_REVERSE_QUERIES */
}
/**
* @brief Free action data
*/
static void free_action(mdns_action_t *action)
{
switch (action->type) {
case ACTION_SEARCH_ADD:
case ACTION_SEARCH_SEND:
case ACTION_SEARCH_END:
mdns_priv_query_action(action, ACTION_CLEANUP);
break;
case ACTION_BROWSE_ADD:
case ACTION_BROWSE_END:
case ACTION_BROWSE_SYNC:
mdns_priv_browse_action(action, ACTION_CLEANUP);
break;
case ACTION_TX_HANDLE:
mdns_priv_send_action(action, ACTION_CLEANUP);
break;
case ACTION_RX_HANDLE:
mdns_priv_receive_action(action, ACTION_CLEANUP);
break;
case ACTION_HOSTNAME_SET:
case ACTION_INSTANCE_SET:
case ACTION_DELEGATE_HOSTNAME_SET_ADDR:
case ACTION_DELEGATE_HOSTNAME_REMOVE:
case ACTION_DELEGATE_HOSTNAME_ADD:
mdns_priv_responder_action(action, ACTION_CLEANUP);
break;
default:
break;
}
mdns_mem_free(action);
}
/**
* @brief Called from service thread to execute given action
*/
static void execute_action(mdns_action_t *action)
{
switch (action->type) {
case ACTION_SYSTEM_EVENT:
perform_event_action(action->data.sys_event.interface, action->data.sys_event.event_action);
break;
case ACTION_SEARCH_ADD:
case ACTION_SEARCH_SEND:
case ACTION_SEARCH_END:
mdns_priv_query_action(action, ACTION_RUN);
break;
case ACTION_BROWSE_ADD:
case ACTION_BROWSE_SYNC:
case ACTION_BROWSE_END:
mdns_priv_browse_action(action, ACTION_RUN);
break;
case ACTION_TX_HANDLE:
mdns_priv_send_action(action, ACTION_RUN);
break;
case ACTION_RX_HANDLE:
mdns_priv_receive_action(action, ACTION_RUN);
break;
case ACTION_HOSTNAME_SET:
case ACTION_INSTANCE_SET:
case ACTION_DELEGATE_HOSTNAME_ADD:
case ACTION_DELEGATE_HOSTNAME_SET_ADDR:
case ACTION_DELEGATE_HOSTNAME_REMOVE:
mdns_priv_responder_action(action, ACTION_RUN);
break;
default:
break;
}
mdns_mem_free(action);
}
/**
* @brief the main MDNS service task. Packets are received and parsed here
*/
static void service_task(void *pvParameters)
{
mdns_action_t *a = NULL;
for (;;) {
if (mdns_priv_is_server_init() && s_action_queue) {
if (xQueueReceive(s_action_queue, &a, portMAX_DELAY) == pdTRUE) {
assert(a);
if (a->type == ACTION_TASK_STOP) {
break;
}
MDNS_SERVICE_LOCK();
execute_action(a);
MDNS_SERVICE_UNLOCK();
}
} else {
vTaskDelay(500 * portTICK_PERIOD_MS);
}
}
s_service_task_handle = NULL;
vTaskDelay(portMAX_DELAY);
}
static void timer_cb(void *arg)
{
mdns_priv_send_packets();
mdns_priv_query_start_stop();
}
static esp_err_t start_timer(void)
{
esp_timer_create_args_t timer_conf = {
.callback = timer_cb,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "mdns_timer"
};
esp_err_t err = esp_timer_create(&timer_conf, &(s_timer_handle));
if (err) {
return err;
}
return esp_timer_start_periodic(s_timer_handle, MDNS_TIMER_PERIOD_US);
}
static esp_err_t stop_timer(void)
{
esp_err_t err = ESP_OK;
if (s_timer_handle) {
err = esp_timer_stop(s_timer_handle);
if (err) {
return err;
}
err = esp_timer_delete(s_timer_handle);
}
return err;
}
static esp_err_t create_task_with_caps(void)
{
esp_err_t ret = ESP_OK;
static StaticTask_t mdns_task_buffer;
s_stack_buffer = mdns_mem_task_malloc(MDNS_SERVICE_STACK_DEPTH);
ESP_GOTO_ON_FALSE(s_stack_buffer != NULL, ESP_FAIL, alloc_failed, TAG, "failed to allocate memory for the mDNS task's stack");
s_service_task_handle = xTaskCreateStaticPinnedToCore(service_task, "mdns", MDNS_SERVICE_STACK_DEPTH, NULL, MDNS_TASK_PRIORITY, s_stack_buffer, &mdns_task_buffer, MDNS_TASK_AFFINITY);
ESP_GOTO_ON_FALSE(s_service_task_handle != NULL, ESP_FAIL, err, TAG, "failed to create task for the mDNS");
return ret;
alloc_failed:
HOOK_MALLOC_FAILED;
err:
mdns_mem_task_free(s_stack_buffer);
return ret;
}
/**
* @brief Start the service thread if not running
*
* @return
* - ESP_OK on success
* - ESP_FAIL on error
*/
static esp_err_t service_task_start(void)
{
esp_err_t ret = ESP_OK;
if (!s_service_semaphore) {
s_service_semaphore = xSemaphoreCreateMutex();
ESP_RETURN_ON_FALSE(s_service_semaphore != NULL, ESP_FAIL, TAG, "Failed to create the mDNS service lock");
}
MDNS_SERVICE_LOCK();
ESP_GOTO_ON_ERROR(start_timer(), err, TAG, "Failed to start the mDNS service timer");
if (!s_service_task_handle) {
ESP_GOTO_ON_ERROR(create_task_with_caps(), err_stop_timer, TAG, "Failed to start the mDNS service task");
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) && !CONFIG_IDF_TARGET_LINUX
StackType_t *mdns_debug_stack_buffer;
StaticTask_t *mdns_debug_task_buffer;
xTaskGetStaticBuffers(s_service_task_handle, &mdns_debug_stack_buffer, &mdns_debug_task_buffer);
ESP_LOGD(TAG, "mdns_debug_stack_buffer:%p mdns_debug_task_buffer:%p\n", mdns_debug_stack_buffer, mdns_debug_task_buffer);
#endif // CONFIG_IDF_TARGET_LINUX
}
MDNS_SERVICE_UNLOCK();
return ret;
err_stop_timer:
stop_timer();
err:
MDNS_SERVICE_UNLOCK();
vSemaphoreDelete(s_service_semaphore);
s_service_semaphore = NULL;
return ret;
}
/**
* @brief Stop the service thread
*
* @return
* - ESP_OK
*/
static esp_err_t service_task_stop(void)
{
stop_timer();
if (s_service_task_handle) {
TaskHandle_t task_handle = s_service_task_handle;
mdns_action_t action;
mdns_action_t *a = &action;
action.type = ACTION_TASK_STOP;
if (xQueueSend(s_action_queue, &a, (TickType_t)0) != pdPASS) {
s_service_task_handle = NULL;
}
while (s_service_task_handle) {
vTaskDelay(10 / portTICK_PERIOD_MS);
}
vTaskDelete(task_handle);
}
vSemaphoreDelete(s_service_semaphore);
s_service_semaphore = NULL;
return ESP_OK;
}
void mdns_priv_service_lock(void)
{
MDNS_SERVICE_LOCK();
}
void mdns_priv_service_unlock(void)
{
MDNS_SERVICE_UNLOCK();
}
esp_err_t mdns_init(void)
{
esp_err_t err = ESP_OK;
if (mdns_priv_is_server_init()) {
return err;
}
if (mdns_priv_responder_init() != ESP_OK) {
return ESP_ERR_NO_MEM;
}
s_action_queue = xQueueCreate(MDNS_ACTION_QUEUE_LEN, sizeof(mdns_action_t *));
if (!s_action_queue) {
err = ESP_ERR_NO_MEM;
goto free_responder;
}
if (mdns_priv_netif_init() != ESP_OK) {
err = ESP_FAIL;
goto free_queue;
}
if (service_task_start()) {
//service start failed!
err = ESP_FAIL;
goto free_all_and_disable_pcbs;
}
return ESP_OK;
free_all_and_disable_pcbs:
mdns_priv_netif_deinit();
free_queue:
vQueueDelete(s_action_queue);
free_responder:
mdns_priv_responder_free();
return err;
}
void mdns_free(void)
{
if (!mdns_priv_is_server_init()) {
return;
}
// Unregister handlers before destroying the mdns internals to avoid receiving async events while deinit
mdns_priv_netif_unregister_predefined_handlers();
mdns_service_remove_all();
service_task_stop();
// at this point, the service task is deleted, so we can destroy the stack size
mdns_mem_task_free(s_stack_buffer);
mdns_priv_pcb_deinit();
if (s_action_queue) {
mdns_action_t *c;
while (xQueueReceive(s_action_queue, &c, 0) == pdTRUE) {
free_action(c);
}
vQueueDelete(s_action_queue);
}
mdns_priv_clear_tx_queue();
mdns_priv_query_free();
mdns_priv_browse_free();
mdns_priv_responder_free();
}
bool mdns_priv_queue_action(mdns_action_t *action)
{
if (xQueueSend(s_action_queue, &action, (TickType_t)0) != pdPASS) {
return false;
}
return true;
}