esp-idf/components/mdns/mdns.c
Jiacheng Guo 0b556de95c mdns: fix memory free issue when repeating the query in reply
The repeated query will be copied in the next event loop while the
memory is freed instantly. Delay the free to fix this issue.
2021-10-14 17:31:01 +02:00

5160 lines
167 KiB
C

// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mdns.h"
#include "mdns_private.h"
#include "mdns_networking.h"
#include "esp_log.h"
#include <string.h>
#include <sys/param.h>
#ifdef MDNS_ENABLE_DEBUG
void mdns_debug_packet(const uint8_t * data, size_t len);
#endif
// Internal size of IPv6 address is defined here as size of AAAA record in mdns packet
// since the ip6_addr_t is defined in lwip and depends on using IPv6 zones
#define _MDNS_SIZEOF_IP6_ADDR (MDNS_ANSWER_AAAA_SIZE)
static const char * MDNS_DEFAULT_DOMAIN = "local";
static const char * MDNS_SUB_STR = "_sub";
mdns_server_t * _mdns_server = NULL;
static const char *TAG = "MDNS";
static volatile TaskHandle_t _mdns_service_task_handle = NULL;
static SemaphoreHandle_t _mdns_service_semaphore = NULL;
static void _mdns_search_finish_done(void);
static mdns_search_once_t * _mdns_search_find_from(mdns_search_once_t * search, mdns_name_t * name, uint16_t type, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol);
static void _mdns_search_result_add_ip(mdns_search_once_t * search, const char * hostname, esp_ip_addr_t * ip, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol);
static void _mdns_search_result_add_srv(mdns_search_once_t * search, const char * hostname, uint16_t port, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol);
static void _mdns_search_result_add_txt(mdns_search_once_t * search, mdns_txt_item_t * txt, size_t txt_count, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol);
static mdns_result_t * _mdns_search_result_add_ptr(mdns_search_once_t * search, const char * instance, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol);
/*
* @brief Internal collection of mdns supported interfaces
*
*/
static esp_netif_t * s_esp_netifs[MDNS_IF_MAX] = {};
/*
* @brief Convert mdns if to esp-netif handle
*/
esp_netif_t *_mdns_get_esp_netif(mdns_if_t tcpip_if)
{
if (tcpip_if < MDNS_IF_MAX) {
if (s_esp_netifs[tcpip_if] == NULL) {
// if local netif copy is NULL, try to search for the default interface key
if (tcpip_if == MDNS_IF_STA) {
s_esp_netifs[MDNS_IF_STA] = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
} else if (tcpip_if == MDNS_IF_AP) {
s_esp_netifs[MDNS_IF_AP] = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
#if CONFIG_ETH_ENABLED
} else if (tcpip_if == MDNS_IF_ETH) {
s_esp_netifs[MDNS_IF_ETH] = esp_netif_get_handle_from_ifkey("ETH_DEF");
#endif
}
}
return s_esp_netifs[tcpip_if];
}
return NULL;
}
/*
* @brief Clean internal mdns interface's pointer
*/
static inline void _mdns_clean_netif_ptr(mdns_if_t tcpip_if) {
if (tcpip_if < MDNS_IF_MAX) {
s_esp_netifs[tcpip_if] = NULL;
}
}
/*
* @brief Convert esp-netif handle to mdns if
*/
static mdns_if_t _mdns_get_if_from_esp_netif(esp_netif_t *interface)
{
for (int i=0; i<MDNS_IF_MAX; ++i) {
if (interface == s_esp_netifs[i])
return i;
}
return MDNS_IF_MAX;
}
static inline bool _str_null_or_empty(const char * str){
return (str == NULL || *str == 0);
}
/*
* @brief Appends/increments a number to name/instance in case of collision
* */
static char * _mdns_mangle_name(char* in) {
char *p = strrchr(in, '-');
int suffix = 0;
if (p == NULL) {
//No - in ``in``
suffix = 2;
} else {
char *endp = NULL;
suffix = strtol(p + 1, &endp, 10);
if (*endp != 0) {
//suffix is not numerical
suffix = 2;
p = NULL; //so we append -suffix to the entire string
}
}
char *ret;
if (p == NULL) {
//need to add -2 to string
ret = malloc(strlen(in) + 3);
if (ret == NULL) {
HOOK_MALLOC_FAILED;
return NULL;
}
sprintf(ret, "%s-2", in);
} else {
ret = malloc(strlen(in) + 2); //one extra byte in case 9-10 or 99-100 etc
if (ret == NULL) {
HOOK_MALLOC_FAILED;
return NULL;
}
strcpy(ret, in);
int baseLen = p - in; //length of 'bla' in 'bla-123'
//overwrite suffix with new suffix
sprintf(ret + baseLen, "-%d", suffix + 1);
}
return ret;
}
/**
* @brief finds service from given service type
* @param server the server
* @param service service type to match
* @param proto proto to match
*
* @return the service item if found or NULL on error
*/
static mdns_srv_item_t * _mdns_get_service_item(const char * service, const char * proto)
{
mdns_srv_item_t * s = _mdns_server->services;
while (s) {
if (!strcasecmp(s->service->service, service) && !strcasecmp(s->service->proto, proto)) {
return s;
}
s = s->next;
}
return NULL;
}
static bool _mdns_can_add_more_services(void)
{
mdns_srv_item_t * s = _mdns_server->services;
uint16_t service_num = 0;
while (s) {
service_num ++;
s = s->next;
if (service_num >= MDNS_MAX_SERVICES) {
return false;
}
}
return true;
}
esp_err_t _mdns_send_rx_action(mdns_rx_packet_t * packet)
{
mdns_action_t * action = NULL;
action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
action->type = ACTION_RX_HANDLE;
action->data.rx_handle.packet = packet;
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
/**
* @brief Get the service name of a service
*/
static const char * _mdns_get_service_instance_name(mdns_service_t * service)
{
if (service && !_str_null_or_empty(service->instance)) {
return service->instance;
}
if (_mdns_server && !_str_null_or_empty(_mdns_server->instance)) {
return _mdns_server->instance;
}
if (_mdns_server && !_str_null_or_empty(_mdns_server->hostname)) {
return _mdns_server->hostname;
}
return NULL;
}
/**
* @brief reads MDNS FQDN into mdns_name_t structure
* FQDN is in format: [hostname.|[instance.]_service._proto.]local.
*
* @param packet MDNS packet
* @param start Starting point of FQDN
* @param name mdns_name_t structure to populate
* @param buf temporary char buffer
*
* @return the address after the parsed FQDN in the packet or NULL on error
*/
static const uint8_t * _mdns_read_fqdn(const uint8_t * packet, const uint8_t * start, mdns_name_t * name, char * buf)
{
size_t index = 0;
while (start[index]) {
if (name->parts == 4) {
name->invalid = true;
}
uint8_t len = start[index++];
if (len < 0xC0) {
if (len > 63) {
//length can not be more than 63
return NULL;
}
uint8_t i;
for (i=0; i<len; i++) {
buf[i] = start[index++];
}
buf[len] = '\0';
if (name->parts == 1 && buf[0] != '_'
&& (strcasecmp(buf, MDNS_DEFAULT_DOMAIN) != 0)
&& (strcasecmp(buf, "ip6") != 0)
&& (strcasecmp(buf, "in-addr") != 0)) {
strlcat(name->host, ".", sizeof(name->host));
strlcat(name->host, buf, sizeof(name->host));
} else if (strcasecmp(buf, MDNS_SUB_STR) == 0) {
name->sub = 1;
} else if (!name->invalid) {
char* mdns_name_ptrs[]={name->host, name->service, name->proto, name->domain};
memcpy(mdns_name_ptrs[name->parts++], buf, len+1);
}
} else {
size_t address = (((uint16_t)len & 0x3F) << 8) | start[index++];
if ((packet + address) >= start) {
//reference address can not be after where we are
return NULL;
}
if (_mdns_read_fqdn(packet, packet + address, name, buf)) {
return start + index;
}
return NULL;
}
}
return start + index + 1;
}
/**
* @brief sets uint16_t value in a packet
*
* @param packet MDNS packet
* @param index offset of uint16_t value
* @param value the value to set
*/
static inline void _mdns_set_u16(uint8_t * packet, uint16_t index, uint16_t value)
{
if ((index + 1) >= MDNS_MAX_PACKET_SIZE) {
return;
}
packet[index] = (value >> 8) & 0xFF;
packet[index+1] = value & 0xFF;
}
/**
* @brief appends byte in a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param value the value to set
*
* @return length of added data: 0 on error or 1 on success
*/
static inline uint8_t _mdns_append_u8(uint8_t * packet, uint16_t * index, uint8_t value)
{
if (*index >= MDNS_MAX_PACKET_SIZE) {
return 0;
}
packet[*index] = value;
*index += 1;
return 1;
}
/**
* @brief appends uint16_t in a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param value the value to set
*
* @return length of added data: 0 on error or 2 on success
*/
static inline uint8_t _mdns_append_u16(uint8_t * packet, uint16_t * index, uint16_t value)
{
if ((*index + 1) >= MDNS_MAX_PACKET_SIZE) {
return 0;
}
_mdns_append_u8(packet, index, (value >> 8) & 0xFF);
_mdns_append_u8(packet, index, value & 0xFF);
return 2;
}
/**
* @brief appends uint32_t in a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param value the value to set
*
* @return length of added data: 0 on error or 4 on success
*/
static inline uint8_t _mdns_append_u32(uint8_t * packet, uint16_t * index, uint32_t value)
{
if ((*index + 3) >= MDNS_MAX_PACKET_SIZE) {
return 0;
}
_mdns_append_u8(packet, index, (value >> 24) & 0xFF);
_mdns_append_u8(packet, index, (value >> 16) & 0xFF);
_mdns_append_u8(packet, index, (value >> 8) & 0xFF);
_mdns_append_u8(packet, index, value & 0xFF);
return 4;
}
/**
* @brief appends answer type, class, ttl and data length to a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param type answer type
* @param ttl answer ttl
*
* @return length of added data: 0 on error or 10 on success
*/
static inline uint8_t _mdns_append_type(uint8_t * packet, uint16_t * index, uint8_t type, bool flush, uint32_t ttl)
{
if ((*index + 10) >= MDNS_MAX_PACKET_SIZE) {
return 0;
}
uint16_t mdns_class = MDNS_CLASS_IN;
if (flush) {
mdns_class = MDNS_CLASS_IN_FLUSH_CACHE;
}
if (type == MDNS_ANSWER_PTR) {
_mdns_append_u16(packet, index, MDNS_TYPE_PTR);
_mdns_append_u16(packet, index, mdns_class);
} else if (type == MDNS_ANSWER_TXT) {
_mdns_append_u16(packet, index, MDNS_TYPE_TXT);
_mdns_append_u16(packet, index, mdns_class);
} else if (type == MDNS_ANSWER_SRV) {
_mdns_append_u16(packet, index, MDNS_TYPE_SRV);
_mdns_append_u16(packet, index, mdns_class);
} else if (type == MDNS_ANSWER_A) {
_mdns_append_u16(packet, index, MDNS_TYPE_A);
_mdns_append_u16(packet, index, mdns_class);
} else if (type == MDNS_ANSWER_AAAA) {
_mdns_append_u16(packet, index, MDNS_TYPE_AAAA);
_mdns_append_u16(packet, index, mdns_class);
} else {
return 0;
}
_mdns_append_u32(packet, index, ttl);
_mdns_append_u16(packet, index, 0);
return 10;
}
/**
* @brief appends single string to a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param string the string to append
*
* @return length of added data: 0 on error or length of the string + 1 on success
*/
static inline uint8_t _mdns_append_string(uint8_t * packet, uint16_t * index, const char * string)
{
uint8_t len = strlen(string);
if ((*index + len + 1) >= MDNS_MAX_PACKET_SIZE) {
return 0;
}
_mdns_append_u8(packet, index, len);
memcpy(packet + *index, string, len);
*index += len;
return len + 1;
}
/**
* @brief appends FQDN to a packet, incrementing the index and
* compressing the output if previous occurrence of the string (or part of it) has been found
*
* @param packet MDNS packet
* @param index offset in the packet
* @param strings string array containing the parts of the FQDN
* @param count number of strings in the array
*
* @return length of added data: 0 on error or length on success
*/
static uint16_t _mdns_append_fqdn(uint8_t * packet, uint16_t * index, const char * strings[], uint8_t count)
{
if (!count) {
//empty string so terminate
return _mdns_append_u8(packet, index, 0);
}
mdns_name_t name;
static char buf[MDNS_NAME_BUF_LEN];
uint8_t len = strlen(strings[0]);
//try to find first the string length in the packet (if it exists)
uint8_t * len_location = (uint8_t *)memchr(packet, (char)len, *index);
while (len_location) {
//check if the string after len_location is the string that we are looking for
if (memcmp(len_location+1, strings[0], len)) { //not continuing with our string
search_next:
//try and find the length byte further in the packet
len_location = (uint8_t *)memchr(len_location+1, (char)len, *index - (len_location+1 - packet));
continue;
}
//seems that we might have found the string that we are looking for
//read the destination into name and compare
name.parts = 0;
name.sub = 0;
name.host[0] = 0;
name.service[0] = 0;
name.proto[0] = 0;
name.domain[0] = 0;
const uint8_t * content = _mdns_read_fqdn(packet, len_location, &name, buf);
if (!content) {
//not a readable fqdn?
return 0;
}
if (name.parts == count) {
uint8_t i;
for (i=0; i<count; i++) {
if (strcasecmp(strings[i], (const char *)&name + (i * (MDNS_NAME_BUF_LEN)))) {
//not our string! let's search more
goto search_next;
}
}
//we actually have found the string
break;
} else {
goto search_next;
}
}
//string is not yet in the packet, so let's add it
if (!len_location) {
uint8_t written = _mdns_append_string(packet, index, strings[0]);
if (!written) {
return 0;
}
//run the same for the other strings in the name
return written + _mdns_append_fqdn(packet, index, &strings[1], count - 1);
}
//we have found the string so let's insert a pointer to it instead
uint16_t offset = len_location - packet;
offset |= MDNS_NAME_REF;
return _mdns_append_u16(packet, index, offset);
}
/**
* @brief appends PTR record for service to a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param server the server that is hosting the service
* @param service the service to add record for
*
* @return length of added data: 0 on error or length on success
*/
static uint16_t _mdns_append_ptr_record(uint8_t * packet, uint16_t * index, const char * instance, const char * service, const char * proto, bool flush, bool bye)
{
const char * str[4];
uint16_t record_length = 0;
uint8_t part_length;
if (service == NULL) {
return 0;
}
str[0] = instance;
str[1] = service;
str[2] = proto;
str[3] = MDNS_DEFAULT_DOMAIN;
part_length = _mdns_append_fqdn(packet, index, str + 1, 3);
if (!part_length) {
return 0;
}
record_length += part_length;
part_length = _mdns_append_type(packet, index, MDNS_ANSWER_PTR, false, bye?0:MDNS_ANSWER_PTR_TTL);
if (!part_length) {
return 0;
}
record_length += part_length;
uint16_t data_len_location = *index - 2;
part_length = _mdns_append_fqdn(packet, index, str, 4);
if (!part_length) {
return 0;
}
_mdns_set_u16(packet, data_len_location, part_length);
record_length += part_length;
return record_length;
}
/**
* @brief appends DNS-SD PTR record for service to a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param server the server that is hosting the service
* @param service the service to add record for
*
* @return length of added data: 0 on error or length on success
*/
static uint16_t _mdns_append_sdptr_record(uint8_t * packet, uint16_t * index, mdns_service_t * service, bool flush, bool bye)
{
const char * str[3];
const char * sd_str[4];
uint16_t record_length = 0;
uint8_t part_length;
if (service == NULL) {
return 0;
}
sd_str[0] = (char*)"_services";
sd_str[1] = (char*)"_dns-sd";
sd_str[2] = (char*)"_udp";
sd_str[3] = MDNS_DEFAULT_DOMAIN;
str[0] = service->service;
str[1] = service->proto;
str[2] = MDNS_DEFAULT_DOMAIN;
part_length = _mdns_append_fqdn(packet, index, sd_str, 4);
record_length += part_length;
part_length = _mdns_append_type(packet, index, MDNS_ANSWER_PTR, flush, MDNS_ANSWER_PTR_TTL);
if (!part_length) {
return 0;
}
record_length += part_length;
uint16_t data_len_location = *index - 2;
part_length = _mdns_append_fqdn(packet, index, str, 3);
if (!part_length) {
return 0;
}
_mdns_set_u16(packet, data_len_location, part_length);
record_length += part_length;
return record_length;
}
/**
* @brief appends TXT record for service to a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param server the server that is hosting the service
* @param service the service to add record for
*
* @return length of added data: 0 on error or length on success
*/
static uint16_t _mdns_append_txt_record(uint8_t * packet, uint16_t * index, mdns_service_t * service, bool flush, bool bye)
{
const char * str[4];
uint16_t record_length = 0;
uint8_t part_length;
if (service == NULL) {
return 0;
}
str[0] = _mdns_get_service_instance_name(service);
str[1] = service->service;
str[2] = service->proto;
str[3] = MDNS_DEFAULT_DOMAIN;
if (!str[0]) {
return 0;
}
part_length = _mdns_append_fqdn(packet, index, str, 4);
if (!part_length) {
return 0;
}
record_length += part_length;
part_length = _mdns_append_type(packet, index, MDNS_ANSWER_TXT, flush, bye?0:MDNS_ANSWER_TXT_TTL);
if (!part_length) {
return 0;
}
record_length += part_length;
uint16_t data_len_location = *index - 2;
uint16_t data_len = 0;
char * tmp;
mdns_txt_linked_item_t * txt = service->txt;
while (txt) {
tmp = (char *)malloc(2 + strlen(txt->key) + strlen(txt->value));
if (tmp) {
sprintf(tmp, "%s=%s", txt->key, txt->value);
uint8_t l = _mdns_append_string(packet, index, tmp);
free(tmp);
if (!l) {
return 0;
}
data_len += l;
} else {
HOOK_MALLOC_FAILED;
// continue
}
txt = txt->next;
}
if (!data_len) {
data_len = 1;
packet[*index] = 0;
*index = *index + 1;
}
_mdns_set_u16(packet, data_len_location, data_len);
record_length += data_len;
return record_length;
}
/**
* @brief appends SRV record for service to a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param server the server that is hosting the service
* @param service the service to add record for
*
* @return length of added data: 0 on error or length on success
*/
static uint16_t _mdns_append_srv_record(uint8_t * packet, uint16_t * index, mdns_service_t * service, bool flush, bool bye)
{
const char * str[4];
uint16_t record_length = 0;
uint8_t part_length;
if (service == NULL) {
return 0;
}
str[0] = _mdns_get_service_instance_name(service);
str[1] = service->service;
str[2] = service->proto;
str[3] = MDNS_DEFAULT_DOMAIN;
if (!str[0]) {
return 0;
}
part_length = _mdns_append_fqdn(packet, index, str, 4);
if (!part_length) {
return 0;
}
record_length += part_length;
part_length = _mdns_append_type(packet, index, MDNS_ANSWER_SRV, flush, bye?0:MDNS_ANSWER_SRV_TTL);
if (!part_length) {
return 0;
}
record_length += part_length;
uint16_t data_len_location = *index - 2;
part_length = 0;
part_length += _mdns_append_u16(packet, index, service->priority);
part_length += _mdns_append_u16(packet, index, service->weight);
part_length += _mdns_append_u16(packet, index, service->port);
if (part_length != 6) {
return 0;
}
str[0] = _mdns_server->hostname;
str[1] = MDNS_DEFAULT_DOMAIN;
if (_str_null_or_empty(str[0])) {
return 0;
}
part_length = _mdns_append_fqdn(packet, index, str, 2);
if (!part_length) {
return 0;
}
_mdns_set_u16(packet, data_len_location, part_length + 6);
record_length += part_length + 6;
return record_length;
}
/**
* @brief appends A record to a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param server the server
* @param ip the IP address to add
*
* @return length of added data: 0 on error or length on success
*/
static uint16_t _mdns_append_a_record(uint8_t * packet, uint16_t * index, uint32_t ip, bool flush, bool bye)
{
const char * str[2];
uint16_t record_length = 0;
uint8_t part_length;
str[0] = _mdns_server->hostname;
str[1] = MDNS_DEFAULT_DOMAIN;
if (_str_null_or_empty(str[0])) {
return 0;
}
part_length = _mdns_append_fqdn(packet, index, str, 2);
if (!part_length) {
return 0;
}
record_length += part_length;
part_length = _mdns_append_type(packet, index, MDNS_ANSWER_A, flush, bye?0:MDNS_ANSWER_A_TTL);
if (!part_length) {
return 0;
}
record_length += part_length;
uint16_t data_len_location = *index - 2;
if ((*index + 3) >= MDNS_MAX_PACKET_SIZE) {
return 0;
}
_mdns_append_u8(packet, index, ip & 0xFF);
_mdns_append_u8(packet, index, (ip >> 8) & 0xFF);
_mdns_append_u8(packet, index, (ip >> 16) & 0xFF);
_mdns_append_u8(packet, index, (ip >> 24) & 0xFF);
_mdns_set_u16(packet, data_len_location, 4);
record_length += 4;
return record_length;
}
#if CONFIG_LWIP_IPV6
/**
* @brief appends AAAA record to a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param ipv6 the IPv6 address to add
*
* @return length of added data: 0 on error or length on success
*/
static uint16_t _mdns_append_aaaa_record(uint8_t * packet, uint16_t * index, uint8_t * ipv6, bool flush, bool bye)
{
const char * str[2];
uint16_t record_length = 0;
uint8_t part_length;
str[0] = _mdns_server->hostname;
str[1] = MDNS_DEFAULT_DOMAIN;
if (_str_null_or_empty(str[0])) {
return 0;
}
part_length = _mdns_append_fqdn(packet, index, str, 2);
if (!part_length) {
return 0;
}
record_length += part_length;
part_length = _mdns_append_type(packet, index, MDNS_ANSWER_AAAA, flush, bye?0:MDNS_ANSWER_AAAA_TTL);
if (!part_length) {
return 0;
}
record_length += part_length;
uint16_t data_len_location = *index - 2;
if ((*index + MDNS_ANSWER_AAAA_SIZE) > MDNS_MAX_PACKET_SIZE) {
return 0;
}
part_length = MDNS_ANSWER_AAAA_SIZE;
memcpy(packet + *index, ipv6, part_length);
*index += part_length;
_mdns_set_u16(packet, data_len_location, part_length);
record_length += part_length;
return record_length;
}
#endif
/**
* @brief Append question to packet
*/
static uint16_t _mdns_append_question(uint8_t * packet, uint16_t * index, mdns_out_question_t * q)
{
const char * str[4];
uint8_t str_index = 0;
uint8_t part_length;
if (q->host) {
str[str_index++] = q->host;
}
if (q->service) {
str[str_index++] = q->service;
}
if (q->proto) {
str[str_index++] = q->proto;
}
if (q->domain) {
str[str_index++] = q->domain;
}
part_length = _mdns_append_fqdn(packet, index, str, str_index);
if (!part_length) {
return 0;
}
part_length += _mdns_append_u16(packet, index, q->type);
part_length += _mdns_append_u16(packet, index, q->unicast?0x8001:0x0001);
return part_length;
}
/**
* @brief Helper to get either ETH or STA if the other is provided
* Used when two interfaces are on the same subnet
*/
static mdns_if_t _mdns_get_other_if (mdns_if_t tcpip_if)
{
if (tcpip_if == MDNS_IF_STA) {
return MDNS_IF_ETH;
} else if (tcpip_if == MDNS_IF_ETH) {
return MDNS_IF_STA;
}
return MDNS_IF_MAX;
}
/**
* @brief Check if interface is duplicate (two interfaces on the same subnet)
*/
static bool _mdns_if_is_dup(mdns_if_t tcpip_if)
{
mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
if (other_if == MDNS_IF_MAX) {
return false;
}
if (_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].state == PCB_DUP
|| _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].state == PCB_DUP
|| _mdns_server->interfaces[other_if].pcbs[MDNS_IP_PROTOCOL_V4].state == PCB_DUP
|| _mdns_server->interfaces[other_if].pcbs[MDNS_IP_PROTOCOL_V6].state == PCB_DUP
) {
return true;
}
return false;
}
#if CONFIG_LWIP_IPV6
/**
* @brief Check if IPv6 address is NULL
*/
static bool _ipv6_address_is_zero(esp_ip6_addr_t ip6)
{
uint8_t i;
uint8_t * data = (uint8_t *)ip6.addr;
for (i=0; i<_MDNS_SIZEOF_IP6_ADDR; i++) {
if (data[i]) {
return false;
}
}
return true;
}
#endif
/**
* @brief Append answer to packet
*
* @return number of answers added to the packet
*/
static uint8_t _mdns_append_answer(uint8_t * packet, uint16_t * index, mdns_out_answer_t * answer, mdns_if_t tcpip_if)
{
if (answer->type == MDNS_TYPE_PTR) {
if (answer->service) {
return _mdns_append_ptr_record(packet, index,
_mdns_get_service_instance_name(answer->service),
answer->service->service, answer->service->proto,
answer->flush, answer->bye) > 0;
} else {
return _mdns_append_ptr_record(packet, index,
answer->custom_instance, answer->custom_service, answer->custom_proto,
answer->flush, answer->bye) > 0;
}
} else if (answer->type == MDNS_TYPE_SRV) {
return _mdns_append_srv_record(packet, index, answer->service, answer->flush, answer->bye) > 0;
} else if (answer->type == MDNS_TYPE_TXT) {
return _mdns_append_txt_record(packet, index, answer->service, answer->flush, answer->bye) > 0;
} else if (answer->type == MDNS_TYPE_SDPTR) {
return _mdns_append_sdptr_record(packet, index, answer->service, answer->flush, answer->bye) > 0;
} else if (answer->type == MDNS_TYPE_A) {
esp_netif_ip_info_t if_ip_info;
if (!_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb && _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].state != PCB_DUP) {
return 0;
}
if (esp_netif_get_ip_info(_mdns_get_esp_netif(tcpip_if), &if_ip_info)) {
return 0;
}
if (_mdns_append_a_record(packet, index, if_ip_info.ip.addr, answer->flush, answer->bye) <= 0) {
return 0;
}
if (!_mdns_if_is_dup(tcpip_if)) {
return 1;
}
mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
if (esp_netif_get_ip_info(_mdns_get_esp_netif(other_if), &if_ip_info)) {
return 1;
}
if (_mdns_append_a_record(packet, index, if_ip_info.ip.addr, answer->flush, answer->bye) > 0) {
return 2;
}
return 1;
}
#if CONFIG_LWIP_IPV6
else if (answer->type == MDNS_TYPE_AAAA) {
struct esp_ip6_addr if_ip6;
if (!_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb && _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].state != PCB_DUP) {
return 0;
}
if (esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(tcpip_if), &if_ip6)) {
return 0;
}
if (_ipv6_address_is_zero(if_ip6)) {
return 0;
}
if (_mdns_append_aaaa_record(packet, index, (uint8_t*)if_ip6.addr, answer->flush, answer->bye) <= 0) {
return 0;
}
if (!_mdns_if_is_dup(tcpip_if)) {
return 1;
}
mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
if (esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(other_if), &if_ip6)) {
return 1;
}
if (_mdns_append_aaaa_record(packet, index, (uint8_t*)if_ip6.addr, answer->flush, answer->bye) > 0) {
return 2;
}
return 1;
}
#endif
return 0;
}
/**
* @brief sends a packet
*
* @param p the packet
*/
static void _mdns_dispatch_tx_packet(mdns_tx_packet_t * p)
{
static uint8_t packet[MDNS_MAX_PACKET_SIZE];
uint16_t index = MDNS_HEAD_LEN;
memset(packet, 0, MDNS_HEAD_LEN);
mdns_out_question_t * q;
mdns_out_answer_t * a;
uint8_t count;
_mdns_set_u16(packet, MDNS_HEAD_FLAGS_OFFSET, p->flags);
_mdns_set_u16(packet, MDNS_HEAD_ID_OFFSET, p->id);
count = 0;
q = p->questions;
while (q) {
if (_mdns_append_question(packet, &index, q)) {
count++;
}
q = q->next;
}
_mdns_set_u16(packet, MDNS_HEAD_QUESTIONS_OFFSET, count);
count = 0;
a = p->answers;
while (a) {
count += _mdns_append_answer(packet, &index, a, p->tcpip_if);
a = a->next;
}
_mdns_set_u16(packet, MDNS_HEAD_ANSWERS_OFFSET, count);
count = 0;
a = p->servers;
while (a) {
count += _mdns_append_answer(packet, &index, a, p->tcpip_if);
a = a->next;
}
_mdns_set_u16(packet, MDNS_HEAD_SERVERS_OFFSET, count);
count = 0;
a = p->additional;
while (a) {
count += _mdns_append_answer(packet, &index, a, p->tcpip_if);
a = a->next;
}
_mdns_set_u16(packet, MDNS_HEAD_ADDITIONAL_OFFSET, count);
#ifdef MDNS_ENABLE_DEBUG
_mdns_dbg_printf("\nTX[%u][%u]: ", p->tcpip_if, p->ip_protocol);
if (p->dst.type == IPADDR_TYPE_V4) {
_mdns_dbg_printf("To: " IPSTR ":%u, ", IP2STR(&p->dst.u_addr.ip4), p->port);
} else {
_mdns_dbg_printf("To: " IPV6STR ":%u, ", IPV62STR(p->dst.u_addr.ip6), p->port);
}
mdns_debug_packet(packet, index);
#endif
_mdns_udp_pcb_write(p->tcpip_if, p->ip_protocol, &p->dst, p->port, packet, index);
}
/**
* @brief frees a packet
*
* @param packet the packet
*/
static void _mdns_free_tx_packet(mdns_tx_packet_t * packet)
{
if (!packet) {
return;
}
mdns_out_question_t *q = packet->questions;
while (q) {
mdns_out_question_t *next = q->next;
if (q->own_dynamic_memory) {
if (q->host) {
free((char *)q->host);
}
if (q->service) {
free((char *)q->service);
}
if (q->proto) {
free((char *)q->proto);
}
if (q->domain) {
free((char *)q->domain);
}
}
free(q);
q = next;
}
queueFree(mdns_out_answer_t, packet->answers);
queueFree(mdns_out_answer_t, packet->servers);
queueFree(mdns_out_answer_t, packet->additional);
free(packet);
}
/**
* @brief schedules a packet to be sent after given milliseconds
*
* @param packet the packet
* @param ms_after number of milliseconds after which the packet should be dispatched
*/
static void _mdns_schedule_tx_packet(mdns_tx_packet_t * packet, uint32_t ms_after)
{
if (!packet) {
return;
}
packet->send_at = (xTaskGetTickCount() * portTICK_PERIOD_MS) + ms_after;
packet->next = NULL;
if (!_mdns_server->tx_queue_head || _mdns_server->tx_queue_head->send_at > packet->send_at) {
packet->next = _mdns_server->tx_queue_head;
_mdns_server->tx_queue_head = packet;
return;
}
mdns_tx_packet_t * q = _mdns_server->tx_queue_head;
while (q->next && q->next->send_at <= packet->send_at) {
q = q->next;
}
packet->next = q->next;
q->next = packet;
}
/**
* @brief free all packets scheduled for sending
*/
static void _mdns_clear_tx_queue_head(void)
{
mdns_tx_packet_t * q;
while (_mdns_server->tx_queue_head) {
q = _mdns_server->tx_queue_head;
_mdns_server->tx_queue_head = _mdns_server->tx_queue_head->next;
_mdns_free_tx_packet(q);
}
}
/**
* @brief clear packets scheduled for sending on a specific interface
*
* @param tcpip_if the interface
* @param ip_protocol pcb type V4/V6
*/
static void _mdns_clear_pcb_tx_queue_head(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
mdns_tx_packet_t * q, * p;
while (_mdns_server->tx_queue_head && _mdns_server->tx_queue_head->tcpip_if == tcpip_if && _mdns_server->tx_queue_head->ip_protocol == ip_protocol) {
q = _mdns_server->tx_queue_head;
_mdns_server->tx_queue_head = _mdns_server->tx_queue_head->next;
_mdns_free_tx_packet(q);
}
if (_mdns_server->tx_queue_head) {
q = _mdns_server->tx_queue_head;
while (q->next) {
if (q->next->tcpip_if == tcpip_if && q->next->ip_protocol == ip_protocol) {
p = q->next;
q->next = p->next;
_mdns_free_tx_packet(p);
} else {
q = q->next;
}
}
}
}
/**
* @brief get the next packet scheduled for sending on a specific interface
*
* @param tcpip_if the interface
* @param ip_protocol pcb type V4/V6
*/
static mdns_tx_packet_t * _mdns_get_next_pcb_packet(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
mdns_tx_packet_t * q = _mdns_server->tx_queue_head;
while (q) {
if (q->tcpip_if == tcpip_if && q->ip_protocol == ip_protocol) {
return q;
}
q = q->next;
}
return NULL;
}
/**
* @brief Find, remove and free answer from the scheduled packets
*/
static void _mdns_remove_scheduled_answer(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint16_t type, mdns_srv_item_t * service)
{
mdns_srv_item_t s = {NULL, NULL};
if (!service) {
service = &s;
}
mdns_tx_packet_t * q = _mdns_server->tx_queue_head;
while (q) {
if (q->tcpip_if == tcpip_if && q->ip_protocol == ip_protocol && q->distributed) {
mdns_out_answer_t * a = q->answers;
if (a->type == type && a->service == service->service) {
q->answers = q->answers->next;
free(a);
} else {
while (a->next) {
if (a->next->type == type && a->next->service == service->service) {
mdns_out_answer_t * b = a->next;
a->next = b->next;
free(b);
break;
}
a = a->next;
}
}
}
q = q->next;
}
}
/**
* @brief Remove and free answer from answer list (destination)
*/
static void _mdns_dealloc_answer(mdns_out_answer_t ** destination, uint16_t type, mdns_srv_item_t * service)
{
mdns_out_answer_t * d = *destination;
if (!d) {
return;
}
mdns_srv_item_t s = {NULL, NULL};
if (!service) {
service = &s;
}
if (d->type == type && d->service == service->service) {
*destination = d->next;
free(d);
return;
}
while (d->next) {
mdns_out_answer_t * a = d->next;
if (a->type == type && a->service == service->service) {
d->next = a->next;
free(a);
return;
}
d = d->next;
}
}
/**
* @brief Allocate new answer and add it to answer list (destination)
*/
static bool _mdns_alloc_answer(mdns_out_answer_t ** destination, uint16_t type, mdns_service_t * service, bool flush, bool bye)
{
mdns_out_answer_t * d = *destination;
while (d) {
if (d->type == type && d->service == service) {
return true;
}
d = d->next;
}
mdns_out_answer_t * a = (mdns_out_answer_t *)malloc(sizeof(mdns_out_answer_t));
if (!a) {
HOOK_MALLOC_FAILED;
return false;
}
a->type = type;
a->service = service;
a->custom_service = NULL;
a->bye = bye;
a->flush = flush;
a->next = NULL;
queueToEnd(mdns_out_answer_t, *destination, a);
return true;
}
/**
* @brief Allocate new packet for sending
*/
static mdns_tx_packet_t * _mdns_alloc_packet_default(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
mdns_tx_packet_t * packet = (mdns_tx_packet_t*)malloc(sizeof(mdns_tx_packet_t));
if (!packet) {
HOOK_MALLOC_FAILED;
return NULL;
}
memset((uint8_t*)packet, 0, sizeof(mdns_tx_packet_t));
packet->tcpip_if = tcpip_if;
packet->ip_protocol = ip_protocol;
packet->port = MDNS_SERVICE_PORT;
if (ip_protocol == MDNS_IP_PROTOCOL_V4) {
IP4_ADDR(&packet->dst.u_addr.ip4, 224, 0, 0, 251);
}
#if CONFIG_LWIP_IPV6
else {
esp_ip_addr_t addr = IPADDR6_INIT(0x000002ff, 0, 0, 0xfb000000);
memcpy(&packet->dst, &addr, sizeof(esp_ip_addr_t));
}
#endif
return packet;
}
/**
* @brief Create answer packet to questions from parsed packet
*/
static void _mdns_create_answer_from_parsed_packet(mdns_parsed_packet_t * parsed_packet)
{
if (!parsed_packet->questions) {
return;
}
bool send_flush = parsed_packet->src_port == MDNS_SERVICE_PORT;
bool unicast = false;
bool shared = false;
mdns_tx_packet_t * packet = _mdns_alloc_packet_default(parsed_packet->tcpip_if, parsed_packet->ip_protocol);
if (!packet) {
return;
}
packet->flags = MDNS_FLAGS_AUTHORITATIVE;
packet->distributed = parsed_packet->distributed;
packet->id = parsed_packet->id;
mdns_parsed_question_t * q = parsed_packet->questions;
while (q) {
mdns_srv_item_t * service = NULL;
if (q->service && q->proto) {
service = _mdns_get_service_item(q->service, q->proto);
if (!service) {
continue;
}
}
if (q->unicast) {
unicast = true;
}
if (service) {
if (q->type == MDNS_TYPE_PTR || q->type == MDNS_TYPE_ANY) {
if (q->type == MDNS_TYPE_PTR || !parsed_packet->probe) {
shared = true;
}
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, service->service, false, false)
|| !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, service->service, send_flush, false)
|| !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, service->service, send_flush, false)
|| !_mdns_alloc_answer(shared?&packet->additional:&packet->answers, MDNS_TYPE_A, NULL, send_flush, false)
|| !_mdns_alloc_answer(shared?&packet->additional:&packet->answers, MDNS_TYPE_AAAA, NULL, send_flush, false)) {
_mdns_free_tx_packet(packet);
return;
}
} else if (q->type == MDNS_TYPE_SRV) {
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, service->service, send_flush, false)
|| !_mdns_alloc_answer(&packet->additional, MDNS_TYPE_A, NULL, send_flush, false)
|| !_mdns_alloc_answer(&packet->additional, MDNS_TYPE_AAAA, NULL, send_flush, false)) {
_mdns_free_tx_packet(packet);
return;
}
} else if (q->type == MDNS_TYPE_TXT) {
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, service->service, send_flush, false)) {
_mdns_free_tx_packet(packet);
return;
}
} else if (q->type == MDNS_TYPE_SDPTR) {
shared = true;
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SDPTR, service->service, false, false)) {
_mdns_free_tx_packet(packet);
return;
}
}
} else {
if (q->type == MDNS_TYPE_ANY || q->type == MDNS_TYPE_A || q->type == MDNS_TYPE_AAAA) {
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_A, NULL, send_flush, false)
|| !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_AAAA, NULL, send_flush, false)) {
_mdns_free_tx_packet(packet);
return;
}
#ifdef MDNS_REPEAT_QUERY_IN_RESPONSE
if (parsed_packet->src_port != MDNS_SERVICE_PORT) {
mdns_out_question_t * out_question = malloc(sizeof(mdns_out_question_t));
if (out_question == NULL) {
HOOK_MALLOC_FAILED;
_mdns_free_tx_packet(packet);
return;
}
out_question->type = q->type;
out_question->unicast = q->unicast;
out_question->host = q->host;
q->host = NULL;
out_question->service = q->service;
q->service = NULL;
out_question->proto = q->proto;
q->proto = NULL;
out_question->domain = q->domain;
q->domain = NULL;
out_question->next = NULL;
out_question->own_dynamic_memory = true;
queueToEnd(mdns_out_question_t, packet->questions, out_question);
}
#endif // MDNS_REPEAT_QUERY_IN_RESPONSE
} else if (!_mdns_alloc_answer(&packet->answers, q->type, NULL, send_flush, false)) {
_mdns_free_tx_packet(packet);
return;
}
}
q = q->next;
}
if (unicast || !send_flush) {
memcpy(&packet->dst, &parsed_packet->src, sizeof(esp_ip_addr_t));
packet->port = parsed_packet->src_port;
}
static uint8_t share_step = 0;
if (shared) {
_mdns_schedule_tx_packet(packet, 25 + (share_step * 25));
share_step = (share_step + 1) & 0x03;
} else {
_mdns_dispatch_tx_packet(packet);
_mdns_free_tx_packet(packet);
}
}
/**
* @brief Check if question is already in the list
*/
static bool _mdns_question_exists(mdns_out_question_t * needle, mdns_out_question_t * haystack)
{
while (haystack) {
if (haystack->type == needle->type
&& haystack->host == needle->host
&& haystack->service == needle->service
&& haystack->proto == needle->proto) {
return true;
}
haystack = haystack->next;
}
return false;
}
/**
* @brief Create probe packet for particular services on particular PCB
*/
static mdns_tx_packet_t * _mdns_create_probe_packet(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t * services[], size_t len, bool first, bool include_ip)
{
mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol);
if (!packet) {
return NULL;
}
size_t i;
for (i=0; i<len; i++) {
mdns_out_question_t * q = (mdns_out_question_t *)malloc(sizeof(mdns_out_question_t));
if (!q) {
HOOK_MALLOC_FAILED;
_mdns_free_tx_packet(packet);
return NULL;
}
q->next = NULL;
q->unicast = first;
q->type = MDNS_TYPE_ANY;
q->host = _mdns_get_service_instance_name(services[i]->service);
q->service = services[i]->service->service;
q->proto = services[i]->service->proto;
q->domain = MDNS_DEFAULT_DOMAIN;
q->own_dynamic_memory = false;
if (!q->host || _mdns_question_exists(q, packet->questions)) {
free(q);
continue;
} else {
queueToEnd(mdns_out_question_t, packet->questions, q);
}
if (!q->host || !_mdns_alloc_answer(&packet->servers, MDNS_TYPE_SRV, services[i]->service, false, false)) {
_mdns_free_tx_packet(packet);
return NULL;
}
}
if (include_ip && !_str_null_or_empty(_mdns_server->hostname)) {
mdns_out_question_t * q = (mdns_out_question_t *)malloc(sizeof(mdns_out_question_t));
if (!q) {
HOOK_MALLOC_FAILED;
_mdns_free_tx_packet(packet);
return NULL;
}
q->next = NULL;
q->unicast = first;
q->type = MDNS_TYPE_ANY;
q->host = _mdns_server->hostname;
q->service = NULL;
q->proto = NULL;
q->domain = MDNS_DEFAULT_DOMAIN;
q->own_dynamic_memory = false;
if (_mdns_question_exists(q, packet->questions)) {
free(q);
} else {
queueToEnd(mdns_out_question_t, packet->questions, q);
}
if (_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb) {
if (!_mdns_alloc_answer(&packet->servers, MDNS_TYPE_A, NULL, false, false)) {
_mdns_free_tx_packet(packet);
return NULL;
}
}
if (_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb) {
if (!_mdns_alloc_answer(&packet->servers, MDNS_TYPE_AAAA, NULL, false, false)) {
_mdns_free_tx_packet(packet);
return NULL;
}
}
}
return packet;
}
/**
* @brief Create announce packet for particular services on particular PCB
*/
static mdns_tx_packet_t * _mdns_create_announce_packet(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t * services[], size_t len, bool include_ip)
{
mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol);
if (!packet) {
return NULL;
}
packet->flags = MDNS_FLAGS_AUTHORITATIVE;
uint8_t i;
for (i=0; i<len; i++) {
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SDPTR, services[i]->service, false, false)
|| !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, services[i]->service, false, false)
|| !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, services[i]->service, true, false)
|| !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, services[i]->service, true, false)) {
_mdns_free_tx_packet(packet);
return NULL;
}
}
if (include_ip) {
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_A, NULL, true, false)
|| !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_AAAA, NULL, true, false)) {
_mdns_free_tx_packet(packet);
return NULL;
}
}
return packet;
}
/**
* @brief Convert probe packet to announce
*/
static mdns_tx_packet_t * _mdns_create_announce_from_probe(mdns_tx_packet_t * probe)
{
mdns_tx_packet_t * packet = _mdns_alloc_packet_default(probe->tcpip_if, probe->ip_protocol);
if (!packet) {
return NULL;
}
packet->flags = MDNS_FLAGS_AUTHORITATIVE;
mdns_out_answer_t * s = probe->servers;
while (s) {
if (s->type == MDNS_TYPE_SRV) {
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SDPTR, s->service, false, false)
|| !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, s->service, false, false)
|| !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, s->service, true, false)
|| !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, s->service, true, false)) {
_mdns_free_tx_packet(packet);
return NULL;
}
} else if (s->type == MDNS_TYPE_A || s->type == MDNS_TYPE_AAAA) {
if (!_mdns_alloc_answer(&packet->answers, s->type, NULL, true, false)) {
_mdns_free_tx_packet(packet);
return NULL;
}
}
s = s->next;
}
return packet;
}
/**
* @brief Send by for particular services on particular PCB
*/
static void _mdns_pcb_send_bye(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t ** services, size_t len, bool include_ip)
{
mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol);
if (!packet) {
return;
}
packet->flags = MDNS_FLAGS_AUTHORITATIVE;
size_t i;
for (i=0; i<len; i++) {
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, services[i]->service, true, true)) {
_mdns_free_tx_packet(packet);
return;
}
}
if (include_ip && (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_A, NULL, true, true) || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_AAAA, NULL, true, true))) {
_mdns_free_tx_packet(packet);
return;
}
_mdns_dispatch_tx_packet(packet);
_mdns_free_tx_packet(packet);
}
/**
* @brief Send probe for additional services on particular PCB
*/
static void _mdns_init_pcb_probe_new_service(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t ** services, size_t len, bool probe_ip)
{
mdns_pcb_t * pcb = &_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol];
size_t services_final_len = len;
if (PCB_STATE_IS_PROBING(pcb)) {
services_final_len += pcb->probe_services_len;
}
mdns_srv_item_t ** _services = NULL;
if (services_final_len) {
_services = (mdns_srv_item_t **)malloc(sizeof(mdns_srv_item_t *) * services_final_len);
if (!_services) {
HOOK_MALLOC_FAILED;
return;
}
size_t i;
for (i=0; i<len; i++) {
_services[i] = services[i];
}
if (pcb->probe_services) {
for (i=0; i<pcb->probe_services_len; i++) {
_services[len+i] = pcb->probe_services[i];
}
free(pcb->probe_services);
}
}
probe_ip = pcb->probe_ip || probe_ip;
pcb->probe_ip = false;
pcb->probe_services = NULL;
pcb->probe_services_len = 0;
pcb->probe_running = false;
mdns_tx_packet_t * packet = _mdns_create_probe_packet(tcpip_if, ip_protocol, _services, services_final_len, true, probe_ip);
if (!packet) {
free(_services);
return;
}
pcb->probe_ip = probe_ip;
pcb->probe_services = _services;
pcb->probe_services_len = services_final_len;
pcb->probe_running = true;
_mdns_schedule_tx_packet(packet, ((pcb->failed_probes > 5)?1000:120) + (esp_random() & 0x7F));
pcb->state = PCB_PROBE_1;
}
/**
* @brief Send probe for particular services on particular PCB
*
* Tests possible duplication on probing service structure and probes only for new entries.
* - If pcb probing then add only non-probing services and restarts probing
* - If pcb not probing, run probing for all specified services
*/
static void _mdns_init_pcb_probe(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t ** services, size_t len, bool probe_ip)
{
mdns_pcb_t * pcb = &_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol];
_mdns_clear_pcb_tx_queue_head(tcpip_if, ip_protocol);
if (_str_null_or_empty(_mdns_server->hostname)) {
pcb->state = PCB_RUNNING;
return;
}
if (PCB_STATE_IS_PROBING(pcb)) {
// Looking for already probing services to resolve duplications
mdns_srv_item_t * new_probe_services[len];
int new_probe_service_len = 0;
bool found;
for (size_t j=0; j < len; ++j) {
found = false;
for (int i=0; i < pcb->probe_services_len; ++i) {
if (pcb->probe_services[i] == services[j]) {
found = true;
break;
}
}
if (!found) {
new_probe_services[new_probe_service_len++] = services[j];
}
}
// init probing for newly added services
_mdns_init_pcb_probe_new_service(tcpip_if, ip_protocol,
new_probe_service_len?new_probe_services:NULL, new_probe_service_len, probe_ip);
} else {
// not probing, so init for all services
_mdns_init_pcb_probe_new_service(tcpip_if, ip_protocol, services, len, probe_ip);
}
}
/**
* @brief Restart the responder on particular PCB
*/
static void _mdns_restart_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
size_t srv_count = 0;
mdns_srv_item_t * a = _mdns_server->services;
while (a) {
srv_count++;
a = a->next;
}
mdns_srv_item_t * services[srv_count];
size_t i = 0;
a = _mdns_server->services;
while (a) {
services[i++] = a;
a = a->next;
}
_mdns_init_pcb_probe(tcpip_if, ip_protocol, services, srv_count, true);
}
/**
* @brief Send by for particular services
*/
static void _mdns_send_bye(mdns_srv_item_t ** services, size_t len, bool include_ip)
{
uint8_t i, j;
if (_str_null_or_empty(_mdns_server->hostname)) {
return;
}
for (i=0; i<MDNS_IF_MAX; i++) {
for (j=0; j<MDNS_IP_PROTOCOL_MAX; j++) {
if (_mdns_server->interfaces[i].pcbs[j].pcb && _mdns_server->interfaces[i].pcbs[j].state == PCB_RUNNING) {
_mdns_pcb_send_bye((mdns_if_t)i, (mdns_ip_protocol_t)j, services, len, include_ip);
}
}
}
}
/**
* @brief Send announcement on particular PCB
*/
static void _mdns_announce_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t ** services, size_t len, bool include_ip)
{
mdns_pcb_t * _pcb = &_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol];
size_t i;
if (_pcb->pcb) {
if (PCB_STATE_IS_PROBING(_pcb)) {
_mdns_init_pcb_probe(tcpip_if, ip_protocol, services, len, include_ip);
} else if (PCB_STATE_IS_ANNOUNCING(_pcb)) {
mdns_tx_packet_t * p = _mdns_get_next_pcb_packet(tcpip_if, ip_protocol);
if (p) {
for (i=0; i<len; i++) {
if (!_mdns_alloc_answer(&p->answers, MDNS_TYPE_SDPTR, services[i]->service, false, false)
|| !_mdns_alloc_answer(&p->answers, MDNS_TYPE_PTR, services[i]->service, false, false)
|| !_mdns_alloc_answer(&p->answers, MDNS_TYPE_SRV, services[i]->service, true, false)
|| !_mdns_alloc_answer(&p->answers, MDNS_TYPE_TXT, services[i]->service, true, false)) {
break;
}
}
if (include_ip) {
_mdns_dealloc_answer(&p->additional, MDNS_TYPE_A, NULL);
_mdns_dealloc_answer(&p->additional, MDNS_TYPE_AAAA, NULL);
_mdns_alloc_answer(&p->answers, MDNS_TYPE_A, NULL, true, false);
_mdns_alloc_answer(&p->answers, MDNS_TYPE_AAAA, NULL, true, false);
}
_pcb->state = PCB_ANNOUNCE_1;
}
} else if (_pcb->state == PCB_RUNNING) {
if (_str_null_or_empty(_mdns_server->hostname)) {
return;
}
_pcb->state = PCB_ANNOUNCE_1;
mdns_tx_packet_t * p = _mdns_create_announce_packet(tcpip_if, ip_protocol, services, len, include_ip);
if (p) {
_mdns_schedule_tx_packet(p, 0);
}
}
}
}
/**
* @brief Send probe on all active PCBs
*/
static void _mdns_probe_all_pcbs(mdns_srv_item_t ** services, size_t len, bool probe_ip, bool clear_old_probe)
{
uint8_t i, j;
for (i=0; i<MDNS_IF_MAX; i++) {
for (j=0; j<MDNS_IP_PROTOCOL_MAX; j++) {
if (_mdns_server->interfaces[i].pcbs[j].pcb) {
mdns_pcb_t * _pcb = &_mdns_server->interfaces[i].pcbs[j];
if (clear_old_probe) {
free(_pcb->probe_services);
_pcb->probe_services = NULL;
_pcb->probe_services_len = 0;
_pcb->probe_running = false;
}
_mdns_init_pcb_probe((mdns_if_t)i, (mdns_ip_protocol_t)j, services, len, probe_ip);
}
}
}
}
/**
* @brief Send announcement on all active PCBs
*/
static void _mdns_announce_all_pcbs(mdns_srv_item_t ** services, size_t len, bool include_ip)
{
uint8_t i, j;
for (i=0; i<MDNS_IF_MAX; i++) {
for (j=0; j<MDNS_IP_PROTOCOL_MAX; j++) {
_mdns_announce_pcb((mdns_if_t)i, (mdns_ip_protocol_t)j, services, len, include_ip);
}
}
}
/**
* @brief Restart the responder on all active PCBs
*/
static void _mdns_send_final_bye(bool include_ip)
{
//collect all services and start probe
size_t srv_count = 0;
mdns_srv_item_t * a = _mdns_server->services;
while (a) {
srv_count++;
a = a->next;
}
if (!srv_count) {
return;
}
mdns_srv_item_t * services[srv_count];
size_t i = 0;
a = _mdns_server->services;
while (a) {
services[i++] = a;
a = a->next;
}
_mdns_send_bye(services, srv_count, include_ip);
}
/**
* @brief Stop the responder on all services without instance
*/
static void _mdns_send_bye_all_pcbs_no_instance(bool include_ip)
{
size_t srv_count = 0;
mdns_srv_item_t * a = _mdns_server->services;
while (a) {
if (!a->service->instance) {
srv_count++;
}
a = a->next;
}
if (!srv_count) {
return;
}
mdns_srv_item_t * services[srv_count];
size_t i = 0;
a = _mdns_server->services;
while (a) {
if (!a->service->instance) {
services[i++] = a;
}
a = a->next;
}
_mdns_send_bye(services, srv_count, include_ip);
}
/**
* @brief Restart the responder on all services without instance
*/
static void _mdns_restart_all_pcbs_no_instance(void)
{
size_t srv_count = 0;
mdns_srv_item_t * a = _mdns_server->services;
while (a) {
if (!a->service->instance) {
srv_count++;
}
a = a->next;
}
if (!srv_count) {
return;
}
mdns_srv_item_t * services[srv_count];
size_t i = 0;
a = _mdns_server->services;
while (a) {
if (!a->service->instance) {
services[i++] = a;
}
a = a->next;
}
_mdns_probe_all_pcbs(services, srv_count, false, true);
}
/**
* @brief Restart the responder on all active PCBs
*/
static void _mdns_restart_all_pcbs(void)
{
_mdns_clear_tx_queue_head();
size_t srv_count = 0;
mdns_srv_item_t * a = _mdns_server->services;
while (a) {
srv_count++;
a = a->next;
}
mdns_srv_item_t * services[srv_count];
size_t l = 0;
a = _mdns_server->services;
while (a) {
services[l++] = a;
a = a->next;
}
_mdns_probe_all_pcbs(services, srv_count, true, true);
}
/**
* @brief creates/allocates new text item list
* @param num_items service number of txt items or 0
* @param txt service txt items array or NULL
*
* @return pointer to the linked txt item list or NULL
*/
static mdns_txt_linked_item_t * _mdns_allocate_txt(size_t num_items, mdns_txt_item_t txt[])
{
mdns_txt_linked_item_t * new_txt = NULL;
size_t i = 0;
if (num_items) {
for (i=0; i<num_items; i++) {
mdns_txt_linked_item_t * new_item = (mdns_txt_linked_item_t *)malloc(sizeof(mdns_txt_linked_item_t));
if (!new_item) {
HOOK_MALLOC_FAILED;
break;
}
new_item->key = strdup(txt[i].key);
if (!new_item->key) {
free(new_item);
break;
}
new_item->value = strdup(txt[i].value);
if (!new_item->value) {
free((char *)new_item->key);
free(new_item);
break;
}
new_item->next = new_txt;
new_txt = new_item;
}
}
return new_txt;
}
static void _mdns_free_linked_txt(mdns_txt_linked_item_t *txt)
{
mdns_txt_linked_item_t *t;
while (txt) {
t = txt;
txt = txt->next;
free((char *)t->value);
free((char *)t->key);
free(t);
}
}
/**
* @brief creates/allocates new service
* @param service service type
* @param proto service proto
* @param port service port
* @param instance service instance
* @param num_items service number of txt items or 0
* @param txt service txt items array or NULL
*
* @return pointer to the service or NULL on error
*/
static mdns_service_t * _mdns_create_service(const char * service, const char * proto, uint16_t port, const char * instance, size_t num_items, mdns_txt_item_t txt[])
{
mdns_service_t * s = (mdns_service_t *)malloc(sizeof(mdns_service_t));
if (!s) {
HOOK_MALLOC_FAILED;
return NULL;
}
mdns_txt_linked_item_t * new_txt = _mdns_allocate_txt(num_items, txt);
if (num_items && new_txt == NULL) {
free(s);
return NULL;
}
s->priority = 0;
s->weight = 0;
s->instance = instance?strndup(instance, MDNS_NAME_BUF_LEN - 1):NULL;
s->txt = new_txt;
s->port = port;
s->service = strndup(service, MDNS_NAME_BUF_LEN - 1);
if (!s->service) {
free(s);
return NULL;
}
s->proto = strndup(proto, MDNS_NAME_BUF_LEN - 1);
if (!s->proto) {
free((char *)s->service);
free(s);
return NULL;
}
return s;
}
/**
* @brief Remove and free service answer from answer list (destination)
*/
static void _mdns_dealloc_scheduled_service_answers(mdns_out_answer_t ** destination, mdns_service_t * service)
{
mdns_out_answer_t * d = *destination;
if (!d) {
return;
}
while (d && d->service == service) {
*destination = d->next;
free(d);
d = *destination;
}
while (d && d->next) {
mdns_out_answer_t * a = d->next;
if (a->service == service) {
d->next = a->next;
free(a);
} else {
d = d->next;
}
}
}
/**
* @brief Find, remove and free answers and scheduled packets for service
*/
static void _mdns_remove_scheduled_service_packets(mdns_service_t * service)
{
if (!service) {
return;
}
mdns_tx_packet_t * p = NULL;
mdns_tx_packet_t * q = _mdns_server->tx_queue_head;
while (q) {
bool had_answers = (q->answers != NULL);
_mdns_dealloc_scheduled_service_answers(&(q->answers), service);
_mdns_dealloc_scheduled_service_answers(&(q->additional), service);
_mdns_dealloc_scheduled_service_answers(&(q->servers), service);
mdns_pcb_t * _pcb = &_mdns_server->interfaces[q->tcpip_if].pcbs[q->ip_protocol];
if(_pcb->pcb) {
if (PCB_STATE_IS_PROBING(_pcb)) {
uint8_t i;
//check if we are probing this service
for (i=0; i<_pcb->probe_services_len; i++) {
mdns_srv_item_t * s = _pcb->probe_services[i];
if (s->service == service){
break;
}
}
if (i < _pcb->probe_services_len) {
if (_pcb->probe_services_len > 1) {
uint8_t n;
for (n=(i+1); n<_pcb->probe_services_len; n++) {
_pcb->probe_services[n-1] = _pcb->probe_services[n];
}
_pcb->probe_services_len--;
} else {
_pcb->probe_services_len = 0;
free(_pcb->probe_services);
_pcb->probe_services = NULL;
if (!_pcb->probe_ip) {
_pcb->probe_running = false;
_pcb->state = PCB_RUNNING;
}
}
if (q->questions) {
mdns_out_question_t * qsn = NULL;
mdns_out_question_t * qs = q->questions;
if (qs->type == MDNS_TYPE_ANY
&& qs->service && strcmp(qs->service, service->service) == 0
&& qs->proto && strcmp(qs->proto, service->proto) == 0)
{
q->questions = q->questions->next;
free(qs);
} else while (qs->next) {
qsn = qs->next;
if (qsn->type == MDNS_TYPE_ANY
&& qsn->service && strcmp(qsn->service, service->service) == 0
&& qsn->proto && strcmp(qsn->proto, service->proto) == 0)
{
qs->next = qsn->next;
free(qsn);
break;
}
qs = qs->next;
}
}
}
} else if (PCB_STATE_IS_ANNOUNCING(_pcb)) {
//if answers were cleared, set to running
if (had_answers && q->answers == NULL) {
_pcb->state = PCB_RUNNING;
}
}
}
p = q;
q = q->next;
if(!p->questions && !p->answers && !p->additional && !p->servers){
queueDetach(mdns_tx_packet_t, _mdns_server->tx_queue_head, p);
_mdns_free_tx_packet(p);
}
}
}
/**
* @brief free service memory
*
* @param service the service
*/
static void _mdns_free_service(mdns_service_t * service)
{
if (!service) {
return;
}
free((char *)service->instance);
free((char *)service->service);
free((char *)service->proto);
while (service->txt) {
mdns_txt_linked_item_t * s = service->txt;
service->txt = service->txt->next;
free((char *)s->key);
free((char *)s->value);
free(s);
}
free(service->txt);
free(service);
}
/*
* Received Packet Handling
* */
/**
* @brief Detect SRV collision
*/
static int _mdns_check_srv_collision(mdns_service_t * service, uint16_t priority, uint16_t weight, uint16_t port, const char * host, const char * domain)
{
if (_str_null_or_empty(_mdns_server->hostname)) {
return 0;
}
size_t our_host_len = strlen(_mdns_server->hostname);
size_t our_len = 14 + our_host_len;
size_t their_host_len = strlen(host);
size_t their_domain_len = strlen(domain);
size_t their_len = 9 + their_host_len + their_domain_len;
if (their_len > our_len) {
return 1;//they win
} else if (their_len < our_len) {
return -1;//we win
}
uint16_t our_index = 0;
uint8_t our_data[our_len];
_mdns_append_u16(our_data, &our_index, service->priority);
_mdns_append_u16(our_data, &our_index, service->weight);
_mdns_append_u16(our_data, &our_index, service->port);
our_data[our_index++] = our_host_len;
memcpy(our_data + our_index, _mdns_server->hostname, our_host_len);
our_index += our_host_len;
our_data[our_index++] = 5;
memcpy(our_data + our_index, MDNS_DEFAULT_DOMAIN, 5);
our_index += 5;
our_data[our_index++] = 0;
uint16_t their_index = 0;
uint8_t their_data[their_len];
_mdns_append_u16(their_data, &their_index, priority);
_mdns_append_u16(their_data, &their_index, weight);
_mdns_append_u16(their_data, &their_index, port);
their_data[their_index++] = their_host_len;
memcpy(their_data + their_index, host, their_host_len);
their_index += their_host_len;
their_data[their_index++] = their_domain_len;
memcpy(their_data + their_index, domain, their_domain_len);
their_index += their_domain_len;
their_data[their_index++] = 0;
int ret = memcmp(our_data, their_data, our_len);
if (ret > 0) {
return -1;//we win
} else if (ret < 0) {
return 1;//they win
}
return 0;//same
}
/**
* @brief Detect TXT collision
*/
static int _mdns_check_txt_collision(mdns_service_t * service, const uint8_t * data, size_t len)
{
size_t data_len = 0;
if (len == 1 && service->txt) {
return -1;//we win
} else if (len > 1 && !service->txt) {
return 1;//they win
} else if (len == 1 && !service->txt) {
return 0;//same
}
mdns_txt_linked_item_t * txt = service->txt;
while (txt) {
data_len += 2 + strlen(txt->key) + strlen(txt->value);
txt = txt->next;
}
if (len > data_len) {
return 1;//they win
} else if (len < data_len) {
return -1;//we win
}
uint8_t ours[len];
uint16_t index = 0;
char * tmp;
txt = service->txt;
while (txt) {
tmp = (char *)malloc(2 + strlen(txt->key) + strlen(txt->value));
if (tmp) {
sprintf(tmp, "%s=%s", txt->key, txt->value);
_mdns_append_string(ours, &index, tmp);
free(tmp);
} else {
HOOK_MALLOC_FAILED;
// continue
}
txt = txt->next;
}
int ret = memcmp(ours, data, len);
if (ret > 0) {
return -1;//we win
} else if (ret < 0) {
return 1;//they win
}
return 0;//same
}
/**
* @brief Set interface as duplicate if another is found on the same subnet
*/
static void _mdns_dup_interface(mdns_if_t tcpip_if)
{
uint8_t i;
mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
for (i=0; i<MDNS_IP_PROTOCOL_MAX; i++) {
if (_mdns_server->interfaces[other_if].pcbs[i].pcb) {
//stop this interface and mark as dup
if (_mdns_server->interfaces[tcpip_if].pcbs[i].pcb) {
_mdns_clear_pcb_tx_queue_head(tcpip_if, i);
_mdns_pcb_deinit(tcpip_if, i);
}
_mdns_server->interfaces[tcpip_if].pcbs[i].state = PCB_DUP;
_mdns_announce_pcb(other_if, i, NULL, 0, true);
}
}
}
/**
* @brief Detect IPv4 address collision
*/
static int _mdns_check_a_collision(esp_ip4_addr_t * ip, mdns_if_t tcpip_if)
{
esp_netif_ip_info_t if_ip_info;
esp_netif_ip_info_t other_ip_info;
if (!ip->addr) {
return 1;//denial! they win
}
if (esp_netif_get_ip_info(_mdns_get_esp_netif(tcpip_if), &if_ip_info)) {
return 1;//they win
}
int ret = memcmp((uint8_t*)&if_ip_info.ip.addr, (uint8_t*)&ip->addr, sizeof(esp_ip4_addr_t));
if (ret > 0) {
return -1;//we win
} else if (ret < 0) {
//is it the other interface?
mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
if (other_if == MDNS_IF_MAX) {
return 1;//AP interface! They win
}
if (esp_netif_get_ip_info(_mdns_get_esp_netif(other_if), &other_ip_info)) {
return 1;//IPv4 not active! They win
}
if (ip->addr != other_ip_info.ip.addr) {
return 1;//IPv4 not ours! They win
}
_mdns_dup_interface(tcpip_if);
return 2;//they win
}
return 0;//same
}
#if CONFIG_LWIP_IPV6
/**
* @brief Detect IPv6 address collision
*/
static int _mdns_check_aaaa_collision(esp_ip6_addr_t * ip, mdns_if_t tcpip_if)
{
struct esp_ip6_addr if_ip6;
struct esp_ip6_addr other_ip6;
if (_ipv6_address_is_zero(*ip)) {
return 1;//denial! they win
}
if (esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(tcpip_if), &if_ip6)) {
return 1;//they win
}
int ret = memcmp((uint8_t*)&if_ip6.addr, (uint8_t*)ip->addr, _MDNS_SIZEOF_IP6_ADDR);
if (ret > 0) {
return -1;//we win
} else if (ret < 0) {
//is it the other interface?
mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
if (other_if == MDNS_IF_MAX) {
return 1;//AP interface! They win
}
if (esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(other_if), &other_ip6)) {
return 1;//IPv6 not active! They win
}
if (memcmp((uint8_t*)&other_ip6.addr, (uint8_t*)ip->addr, _MDNS_SIZEOF_IP6_ADDR)) {
return 1;//IPv6 not ours! They win
}
_mdns_dup_interface(tcpip_if);
return 2;//they win
}
return 0;//same
}
#endif
/**
* @brief Check if parsed name is discovery
*/
static bool _mdns_name_is_discovery(mdns_name_t * name, uint16_t type)
{
return (
(name->host && name->host[0] && !strcasecmp(name->host, "_services"))
&& (name->service && name->service[0] && !strcasecmp(name->service, "_dns-sd"))
&& (name->proto && name->proto[0] && !strcasecmp(name->proto, "_udp"))
&& (name->domain && name->domain[0] && !strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN))
&& type == MDNS_TYPE_PTR
);
}
/**
* @brief Check if the parsed name is ours (matches service or host name)
*/
static bool _mdns_name_is_ours(mdns_name_t * name)
{
//domain have to be "local"
if (_str_null_or_empty(name->domain) || strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN)) {
return false;
}
//if service and proto are empty, host must match out hostname
if (_str_null_or_empty(name->service) && _str_null_or_empty(name->proto)) {
if (!_str_null_or_empty(name->host)
&& !_str_null_or_empty(_mdns_server->hostname)
&& strcasecmp(name->host, _mdns_server->hostname) == 0)
{
return true;
}
return false;
}
//if service or proto is empty, name is invalid
if (_str_null_or_empty(name->service) || _str_null_or_empty(name->proto)) {
return false;
}
//find the service
mdns_srv_item_t * service = _mdns_get_service_item(name->service, name->proto);
if (!service) {
return false;
}
//if host is empty and we have service, we have success
if (_str_null_or_empty(name->host)) {
return true;
}
//OK we have host in the name. find what is the instance of the service
const char * instance = _mdns_get_service_instance_name(service->service);
if (instance == NULL) {
return false;
}
//compare the instance against the name
if (strcasecmp(name->host, instance) == 0) {
return true;
}
return false;
}
/**
* @brief read uint16_t from a packet
* @param packet the packet
* @param index index in the packet where the value starts
*
* @return the value
*/
static inline uint16_t _mdns_read_u16(const uint8_t * packet, uint16_t index)
{
return (uint16_t)(packet[index]) << 8 | packet[index+1];
}
/**
* @brief read uint32_t from a packet
* @param packet the packet
* @param index index in the packet where the value starts
*
* @return the value
*/
static inline uint32_t _mdns_read_u32(const uint8_t * packet, uint16_t index)
{
return (uint32_t)(packet[index]) << 24 | (uint32_t)(packet[index+1]) << 16 | (uint32_t)(packet[index+2]) << 8 | packet[index+3];
}
/**
* @brief reads and formats MDNS FQDN into mdns_name_t structure
*
* @param packet MDNS packet
* @param start Starting point of FQDN
* @param name mdns_name_t structure to populate
*
* @return the address after the parsed FQDN in the packet or NULL on error
*/
static const uint8_t * _mdns_parse_fqdn(const uint8_t * packet, const uint8_t * start, mdns_name_t * name)
{
name->parts = 0;
name->sub = 0;
name->host[0] = 0;
name->service[0] = 0;
name->proto[0] = 0;
name->domain[0] = 0;
name->invalid = false;
static char buf[MDNS_NAME_BUF_LEN];
const uint8_t * next_data = (uint8_t*)_mdns_read_fqdn(packet, start, name, buf);
if (!next_data) {
return 0;
}
if (!name->parts || name->invalid) {
return next_data;
}
if (name->parts == 3) {
memmove((uint8_t*)name + (MDNS_NAME_BUF_LEN), (uint8_t*)name, 3*(MDNS_NAME_BUF_LEN));
name->host[0] = 0;
} else if (name->parts == 2) {
memmove((uint8_t*)(name->domain), (uint8_t*)(name->service), (MDNS_NAME_BUF_LEN));
name->service[0] = 0;
name->proto[0] = 0;
}
if (strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN) == 0 || strcasecmp(name->domain, "arpa") == 0) {
return next_data;
}
return 0;
}
/**
* @brief Called from parser to check if question matches particular service
*/
static bool _mdns_question_matches(mdns_parsed_question_t * question, uint16_t type, mdns_srv_item_t * service)
{
if (question->type != type) {
return false;
}
if (type == MDNS_TYPE_A || type == MDNS_TYPE_AAAA) {
return true;
} else if (type == MDNS_TYPE_PTR || type == MDNS_TYPE_SDPTR) {
if (!strcasecmp(service->service->service, question->service)
&& !strcasecmp(service->service->proto, question->proto)
&& !strcasecmp(MDNS_DEFAULT_DOMAIN, question->domain)) {
return true;
}
} else if (service && (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT)) {
const char * name = _mdns_get_service_instance_name(service->service);
if (name && question->host && !strcasecmp(name, question->host)
&& !strcasecmp(service->service->service, question->service)
&& !strcasecmp(service->service->proto, question->proto)
&& !strcasecmp(MDNS_DEFAULT_DOMAIN, question->domain)) {
return true;
}
}
return false;
}
/**
* @brief Removes saved question from parsed data
*/
static void _mdns_remove_parsed_question(mdns_parsed_packet_t * parsed_packet, uint16_t type, mdns_srv_item_t * service)
{
mdns_parsed_question_t * q = parsed_packet->questions;
if (_mdns_question_matches(q, type, service)) {
parsed_packet->questions = q->next;
free(q->host);
free(q->service);
free(q->proto);
free(q->domain);
free(q);
return;
}
while (q->next) {
mdns_parsed_question_t * p = q->next;
if (_mdns_question_matches(p, type, service)) {
q->next = p->next;
free(p->host);
free(p->service);
free(p->proto);
free(p->domain);
free(p);
return;
}
q = q->next;
}
}
/**
* @brief Get number of items in TXT parsed data
*/
static int _mdns_txt_items_count_get(const uint8_t * data, size_t len)
{
if (len == 1) {
return 0;
}
int num_items = 0;
uint16_t i=0;
size_t partLen = 0;
while (i < len) {
partLen = data[i++];
if (!partLen) {
break;
}
if ((i+partLen) > len) {
return -1;//error
}
i+=partLen;
num_items++;
}
return num_items;
}
/**
* @brief Get the length of TXT item's key name
*/
static int _mdns_txt_item_name_get_len(const uint8_t * data, size_t len)
{
if (*data == '=') {
return -1;
}
for (size_t i = 0; i < len; i++) {
if (data[i] == '=') {
return i;
}
}
return len;
}
/**
* @brief Create TXT result array from parsed TXT data
*/
static void _mdns_result_txt_create(const uint8_t * data, size_t len, mdns_txt_item_t ** out_txt, size_t * out_count)
{
*out_txt = NULL;
*out_count = 0;
uint16_t i=0, y;
size_t partLen = 0;
int num_items = _mdns_txt_items_count_get(data, len);
if (num_items < 0) {
return;//error
}
if (!num_items) {
return;
}
mdns_txt_item_t * txt = (mdns_txt_item_t *)malloc(sizeof(mdns_txt_item_t) * num_items);
if (!txt) {
HOOK_MALLOC_FAILED;
return;
}
memset(txt, 0, sizeof(mdns_txt_item_t) * num_items);
size_t txt_num = 0;
while (i < len) {
partLen = data[i++];
if (!partLen) {
break;
}
if ((i+partLen) > len) {
goto handle_error;//error
}
int name_len = _mdns_txt_item_name_get_len(data+i, partLen);
if (name_len < 0) {//invalid item (no name)
i += partLen;
continue;
}
char * key = (char *)malloc(name_len + 1);
if (!key) {
HOOK_MALLOC_FAILED;
goto handle_error;//error
}
mdns_txt_item_t * t = &txt[txt_num++];
memcpy(key, data + i, name_len);
key[name_len] = 0;
i += name_len + 1;
t->key = key;
int value_len = partLen - name_len - 1;
if (value_len > 0) {
char * value = (char *)malloc(value_len + 1);
if (!value) {
HOOK_MALLOC_FAILED;
goto handle_error;//error
}
memcpy(value, data + i, value_len);
value[value_len] = 0;
i += value_len;
t->value = value;
}
}
*out_txt = txt;
*out_count = txt_num;
return;
handle_error :
for (y=0; y<txt_num; y++) {
mdns_txt_item_t * t = &txt[y];
free((char *)t->key);
free((char *)t->value);
}
free(txt);
}
/**
* @brief Duplicate string or return error
*/
static esp_err_t _mdns_strdup_check(char ** out, char * in)
{
if (in && in[0]) {
*out = strdup(in);
if (!*out) {
return ESP_FAIL;
}
return ESP_OK;
}
*out = NULL;
return ESP_OK;
}
/**
* @brief main packet parser
*
* @param packet the packet
*/
void mdns_parse_packet(mdns_rx_packet_t * packet)
{
static mdns_name_t n;
mdns_header_t header;
const uint8_t * data = (const uint8_t*)packet->pb->payload;
size_t len = packet->pb->len;
const uint8_t * content = data + MDNS_HEAD_LEN;
bool do_not_reply = false;
mdns_search_once_t * search_result = NULL;
#ifdef MDNS_ENABLE_DEBUG
_mdns_dbg_printf("\nRX[%u][%u]: ", packet->tcpip_if, (uint32_t)packet->ip_protocol);
if (packet->src.type == IPADDR_TYPE_V4) {
_mdns_dbg_printf("From: " IPSTR ":%u, To: " IPSTR ", ", IP2STR(&packet->src.u_addr.ip4), packet->src_port, IP2STR(&packet->dest.u_addr.ip4));
} else {
_mdns_dbg_printf("From: " IPV6STR ":%u, To: " IPV6STR ", ", IPV62STR(packet->src.u_addr.ip6), packet->src_port, IPV62STR(packet->dest.u_addr.ip6));
}
mdns_debug_packet(data, len);
#endif
mdns_parsed_packet_t * parsed_packet = (mdns_parsed_packet_t *)malloc(sizeof(mdns_parsed_packet_t));
if (!parsed_packet) {
HOOK_MALLOC_FAILED;
return;
}
memset(parsed_packet, 0, sizeof(mdns_parsed_packet_t));
mdns_name_t * name = &n;
memset(name, 0, sizeof(mdns_name_t));
header.id = _mdns_read_u16(data, MDNS_HEAD_ID_OFFSET);
header.flags.value = _mdns_read_u16(data, MDNS_HEAD_FLAGS_OFFSET);
header.questions = _mdns_read_u16(data, MDNS_HEAD_QUESTIONS_OFFSET);
header.answers = _mdns_read_u16(data, MDNS_HEAD_ANSWERS_OFFSET);
header.servers = _mdns_read_u16(data, MDNS_HEAD_SERVERS_OFFSET);
header.additional = _mdns_read_u16(data, MDNS_HEAD_ADDITIONAL_OFFSET);
if (header.flags.value == MDNS_FLAGS_AUTHORITATIVE && packet->src_port != MDNS_SERVICE_PORT) {
free(parsed_packet);
return;
}
//if we have not set the hostname, we can not answer questions
if (header.questions && !header.answers && _str_null_or_empty(_mdns_server->hostname)) {
free(parsed_packet);
return;
}
parsed_packet->tcpip_if = packet->tcpip_if;
parsed_packet->ip_protocol = packet->ip_protocol;
parsed_packet->multicast = packet->multicast;
parsed_packet->authoritative = header.flags.value == MDNS_FLAGS_AUTHORITATIVE;
parsed_packet->distributed = header.flags.value == MDNS_FLAGS_DISTRIBUTED;
parsed_packet->id = header.id;
#if CONFIG_LWIP_IPV6
ip_addr_copy(parsed_packet->src, packet->src);
#else
ip4_addr_copy(parsed_packet->src.u_addr.ip4, packet->src.u_addr.ip4);
#endif
parsed_packet->src_port = packet->src_port;
if (header.questions) {
uint8_t qs = header.questions;
while (qs--) {
content = _mdns_parse_fqdn(data, content, name);
if (!content) {
header.answers = 0;
header.additional = 0;
header.servers = 0;
goto clear_rx_packet;//error
}
uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET);
uint16_t mdns_class = _mdns_read_u16(content, MDNS_CLASS_OFFSET);
bool unicast = !!(mdns_class & 0x8000);
mdns_class &= 0x7FFF;
content = content + 4;
if (mdns_class != 0x0001 || name->invalid) {//bad class or invalid name for this question entry
continue;
}
if (_mdns_name_is_discovery(name, type)) {
//service discovery
parsed_packet->discovery = true;
mdns_srv_item_t * a = _mdns_server->services;
while (a) {
mdns_parsed_question_t * question = (mdns_parsed_question_t *)calloc(1, sizeof(mdns_parsed_question_t));
if (!question) {
HOOK_MALLOC_FAILED;
goto clear_rx_packet;
}
question->next = parsed_packet->questions;
parsed_packet->questions = question;
question->unicast = unicast;
question->type = MDNS_TYPE_SDPTR;
question->host = NULL;
question->service = strdup(a->service->service);
question->proto = strdup(a->service->proto);
question->domain = strdup(MDNS_DEFAULT_DOMAIN);
if (!question->service || !question->proto || !question->domain) {
goto clear_rx_packet;
}
a = a->next;
}
continue;
} else if (name->sub || !_mdns_name_is_ours(name)) {
continue;
}
if (type == MDNS_TYPE_ANY && !_str_null_or_empty(name->host)) {
parsed_packet->probe = true;
}
mdns_parsed_question_t * question = (mdns_parsed_question_t *)calloc(1, sizeof(mdns_parsed_question_t));
if (!question) {
HOOK_MALLOC_FAILED;
goto clear_rx_packet;
}
question->next = parsed_packet->questions;
parsed_packet->questions = question;
question->unicast = unicast;
question->type = type;
if (_mdns_strdup_check(&(question->host), name->host)
|| _mdns_strdup_check(&(question->service), name->service)
|| _mdns_strdup_check(&(question->proto), name->proto)
|| _mdns_strdup_check(&(question->domain), name->domain)) {
goto clear_rx_packet;
}
}
}
if (header.questions && !parsed_packet->questions && !parsed_packet->discovery && !header.answers) {
goto clear_rx_packet;
} else if (header.answers || header.servers || header.additional) {
uint16_t recordIndex = 0;
while (content < (data + len)) {
content = _mdns_parse_fqdn(data, content, name);
if (!content) {
goto clear_rx_packet;//error
}
uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET);
uint16_t mdns_class = _mdns_read_u16(content, MDNS_CLASS_OFFSET);
uint32_t ttl = _mdns_read_u32(content, MDNS_TTL_OFFSET);
uint16_t data_len = _mdns_read_u16(content, MDNS_LEN_OFFSET);
const uint8_t * data_ptr = content + MDNS_DATA_OFFSET;
mdns_class &= 0x7FFF;
content = data_ptr + data_len;
if (content > (data + len)) {
goto clear_rx_packet;
}
bool discovery = false;
bool ours = false;
mdns_srv_item_t * service = NULL;
mdns_parsed_record_type_t record_type = MDNS_ANSWER;
if (recordIndex >= (header.answers + header.servers)) {
record_type = MDNS_EXTRA;
} else if (recordIndex >= (header.answers)) {
record_type = MDNS_NS;
}
recordIndex++;
if (type == MDNS_TYPE_NSEC || type == MDNS_TYPE_OPT) {
//skip NSEC and OPT
continue;
}
if (parsed_packet->discovery && _mdns_name_is_discovery(name, type)) {
discovery = true;
} else if (!name->sub && _mdns_name_is_ours(name)) {
ours = true;
if (name->service && name->service[0] && name->proto && name->proto[0]) {
service = _mdns_get_service_item(name->service, name->proto);
}
} else {
if (!parsed_packet->authoritative || record_type == MDNS_NS) {
//skip this record
continue;
}
search_result = _mdns_search_find_from(_mdns_server->search_once, name, type, packet->tcpip_if, packet->ip_protocol);
}
if (type == MDNS_TYPE_PTR) {
if (!_mdns_parse_fqdn(data, data_ptr, name)) {
continue;//error
}
if (search_result) {
_mdns_search_result_add_ptr(search_result, name->host, packet->tcpip_if, packet->ip_protocol);
} else if ((discovery || ours) && !name->sub && _mdns_name_is_ours(name)) {
if (discovery) {
service = _mdns_get_service_item(name->service, name->proto);
_mdns_remove_parsed_question(parsed_packet, MDNS_TYPE_SDPTR, service);
} else if (parsed_packet->questions && !parsed_packet->probe) {
_mdns_remove_parsed_question(parsed_packet, type, service);
} else {
//check if TTL is more than half of the full TTL value (4500)
if (ttl > 2250) {
_mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service);
}
}
}
} else if (type == MDNS_TYPE_SRV) {
mdns_result_t * result = NULL;
if (search_result && search_result->type == MDNS_TYPE_PTR) {
result = search_result->result;
while (result) {
if (packet->tcpip_if == result->tcpip_if
&& packet->ip_protocol == result->ip_protocol
&& result->instance_name && !strcmp(name->host, result->instance_name)) {
break;
}
result = result->next;
}
if (!result) {
result = _mdns_search_result_add_ptr(search_result, name->host, packet->tcpip_if, packet->ip_protocol);
if (!result) {
continue;//error
}
}
}
if (!_mdns_parse_fqdn(data, data_ptr + MDNS_SRV_FQDN_OFFSET, name)) {
continue;//error
}
uint16_t priority = _mdns_read_u16(data_ptr, MDNS_SRV_PRIORITY_OFFSET);
uint16_t weight = _mdns_read_u16(data_ptr, MDNS_SRV_WEIGHT_OFFSET);
uint16_t port = _mdns_read_u16(data_ptr, MDNS_SRV_PORT_OFFSET);
if (search_result) {
if (search_result->type == MDNS_TYPE_PTR) {
if (!result->hostname) { // assign host/port for this entry only if not previously set
result->port = port;
result->hostname = strdup(name->host);
}
} else {
_mdns_search_result_add_srv(search_result, name->host, port, packet->tcpip_if, packet->ip_protocol);
}
} else if (ours) {
if (parsed_packet->questions && !parsed_packet->probe) {
_mdns_remove_parsed_question(parsed_packet, type, service);
continue;
} else if (parsed_packet->distributed) {
_mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service);
continue;
}
//detect collision (-1=won, 0=none, 1=lost)
int col = 0;
if (mdns_class > 1) {
col = 1;
} else if (!mdns_class) {
col = -1;
} else if (service) { // only detect srv collision if service existed
col = _mdns_check_srv_collision(service->service, priority, weight, port, name->host, name->domain);
}
if (col && (parsed_packet->probe || parsed_packet->authoritative)) {
if (col > 0 || !port) {
do_not_reply = true;
if (_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) {
_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].failed_probes++;
if (!_str_null_or_empty(service->service->instance)) {
char * new_instance = _mdns_mangle_name((char *)service->service->instance);
if (new_instance) {
free((char *)service->service->instance);
service->service->instance = new_instance;
}
_mdns_probe_all_pcbs(&service, 1, false, false);
} else if (!_str_null_or_empty(_mdns_server->instance)) {
char * new_instance = _mdns_mangle_name((char *)_mdns_server->instance);
if (new_instance) {
free((char *)_mdns_server->instance);
_mdns_server->instance = new_instance;
}
_mdns_restart_all_pcbs_no_instance();
} else {
char * new_host = _mdns_mangle_name((char *)_mdns_server->hostname);
if (new_host) {
free((char *)_mdns_server->hostname);
_mdns_server->hostname = new_host;
}
_mdns_restart_all_pcbs();
}
} else {
_mdns_pcb_send_bye(packet->tcpip_if, packet->ip_protocol, &service, 1, false);
_mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, &service, 1, false);
}
}
} else if (ttl > 60 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions) {
_mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service);
}
}
} else if (type == MDNS_TYPE_TXT) {
if (search_result) {
mdns_txt_item_t * txt = NULL;
size_t txt_count = 0;
mdns_result_t * result = NULL;
if (search_result->type == MDNS_TYPE_PTR) {
result = search_result->result;
while (result) {
if (packet->tcpip_if == result->tcpip_if
&& packet->ip_protocol == result->ip_protocol
&& result->instance_name && !strcmp(name->host, result->instance_name)) {
break;
}
result = result->next;
}
if (!result) {
result = _mdns_search_result_add_ptr(search_result, name->host, packet->tcpip_if, packet->ip_protocol);
if (!result) {
continue;//error
}
}
if (!result->txt) {
_mdns_result_txt_create(data_ptr, data_len, &txt, &txt_count);
if (txt_count) {
result->txt = txt;
result->txt_count = txt_count;
}
}
} else {
_mdns_result_txt_create(data_ptr, data_len, &txt, &txt_count);
if (txt_count) {
_mdns_search_result_add_txt(search_result, txt, txt_count, packet->tcpip_if, packet->ip_protocol);
}
}
} else if (ours) {
if (parsed_packet->questions && !parsed_packet->probe) {
_mdns_remove_parsed_question(parsed_packet, type, service);
continue;
}
//detect collision (-1=won, 0=none, 1=lost)
int col = 0;
if (mdns_class > 1) {
col = 1;
} else if (!mdns_class) {
col = -1;
} else if (service) { // only detect txt collision if service existed
col = _mdns_check_txt_collision(service->service, data_ptr, data_len);
}
if (col && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running && service) {
do_not_reply = true;
_mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, &service, 1, true);
} else if (ttl > 2250 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) {
_mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service);
}
}
}
#if CONFIG_LWIP_IPV6
else if (type == MDNS_TYPE_AAAA) {//ipv6
esp_ip_addr_t ip6;
ip6.type = IPADDR_TYPE_V6;
memcpy(ip6.u_addr.ip6.addr, data_ptr, MDNS_ANSWER_AAAA_SIZE);
if (search_result) {
//check for more applicable searches (PTR & A/AAAA at the same time)
while (search_result) {
_mdns_search_result_add_ip(search_result, name->host, &ip6, packet->tcpip_if, packet->ip_protocol);
search_result = _mdns_search_find_from(search_result->next, name, type, packet->tcpip_if, packet->ip_protocol);
}
} else if (ours) {
if (parsed_packet->questions && !parsed_packet->probe) {
_mdns_remove_parsed_question(parsed_packet, type, NULL);
continue;
}
//detect collision (-1=won, 0=none, 1=lost)
int col = 0;
if (mdns_class > 1) {
col = 1;
} else if (!mdns_class) {
col = -1;
} else {
col = _mdns_check_aaaa_collision(&(ip6.u_addr.ip6), packet->tcpip_if);
}
if (col == 2) {
goto clear_rx_packet;
} else if (col == 1) {
do_not_reply = true;
if (_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) {
if (col && (parsed_packet->probe || parsed_packet->authoritative)) {
_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].failed_probes++;
char * new_host = _mdns_mangle_name((char *)_mdns_server->hostname);
if (new_host) {
free((char *)_mdns_server->hostname);
_mdns_server->hostname = new_host;
}
_mdns_restart_all_pcbs();
}
} else {
_mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, NULL, 0, true);
}
} else if (ttl > 60 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) {
_mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, NULL);
}
}
}
#endif
else if (type == MDNS_TYPE_A) {
esp_ip_addr_t ip;
ip.type = IPADDR_TYPE_V4;
memcpy(&(ip.u_addr.ip4.addr), data_ptr, 4);
if (search_result) {
//check for more applicable searches (PTR & A/AAAA at the same time)
while (search_result) {
_mdns_search_result_add_ip(search_result, name->host, &ip, packet->tcpip_if, packet->ip_protocol);
search_result = _mdns_search_find_from(search_result->next, name, type, packet->tcpip_if, packet->ip_protocol);
}
} else if (ours) {
if (parsed_packet->questions && !parsed_packet->probe) {
_mdns_remove_parsed_question(parsed_packet, type, NULL);
continue;
}
//detect collision (-1=won, 0=none, 1=lost)
int col = 0;
if (mdns_class > 1) {
col = 1;
} else if (!mdns_class) {
col = -1;
} else {
col = _mdns_check_a_collision(&(ip.u_addr.ip4), packet->tcpip_if);
}
if (col == 2) {
goto clear_rx_packet;
} else if (col == 1) {
do_not_reply = true;
if (_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) {
if (col && (parsed_packet->probe || parsed_packet->authoritative)) {
_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].failed_probes++;
char * new_host = _mdns_mangle_name((char *)_mdns_server->hostname);
if (new_host) {
free((char *)_mdns_server->hostname);
_mdns_server->hostname = new_host;
}
_mdns_restart_all_pcbs();
}
} else {
_mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, NULL, 0, true);
}
} else if (ttl > 60 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) {
_mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, NULL);
}
}
}
}
//end while
if (parsed_packet->authoritative) {
_mdns_search_finish_done();
}
}
if (!do_not_reply && _mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].state > PCB_PROBE_3 && (parsed_packet->questions || parsed_packet->discovery)) {
_mdns_create_answer_from_parsed_packet(parsed_packet);
}
clear_rx_packet:
while (parsed_packet->questions) {
mdns_parsed_question_t * question = parsed_packet->questions;
parsed_packet->questions = parsed_packet->questions->next;
if (question->host) {
free(question->host);
}
if (question->service) {
free(question->service);
}
if (question->proto) {
free(question->proto);
}
if (question->domain) {
free(question->domain);
}
free(question);
}
free(parsed_packet);
}
/**
* @brief Enable mDNS interface
*/
void _mdns_enable_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
if (!_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb) {
if (_mdns_pcb_init(tcpip_if, ip_protocol)) {
return;
}
}
_mdns_restart_pcb(tcpip_if, ip_protocol);
}
/**
* @brief Disable mDNS interface
*/
void _mdns_disable_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
_mdns_clean_netif_ptr(tcpip_if);
if (_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb) {
_mdns_clear_pcb_tx_queue_head(tcpip_if, ip_protocol);
_mdns_pcb_deinit(tcpip_if, ip_protocol);
mdns_if_t other_if = _mdns_get_other_if (tcpip_if);
if (other_if != MDNS_IF_MAX && _mdns_server->interfaces[other_if].pcbs[ip_protocol].state == PCB_DUP) {
_mdns_server->interfaces[other_if].pcbs[ip_protocol].state = PCB_OFF;
_mdns_enable_pcb(other_if, ip_protocol);
}
}
_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].state = PCB_OFF;
}
/**
* @brief Dispatch interface changes based on system events
*/
static void _mdns_handle_system_event(esp_event_base_t event_base,
int32_t event_id, esp_netif_t* interface)
{
if (!_mdns_server) {
return;
}
esp_netif_dhcp_status_t dcst;
if (event_base == WIFI_EVENT) {
switch(event_id) {
case WIFI_EVENT_STA_CONNECTED:
if (!esp_netif_dhcpc_get_status(_mdns_get_esp_netif(MDNS_IF_STA), &dcst)) {
if (dcst == ESP_NETIF_DHCP_STOPPED) {
_mdns_enable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V4);
}
}
break;
case WIFI_EVENT_STA_DISCONNECTED:
_mdns_disable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V4);
_mdns_disable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V6);
break;
case WIFI_EVENT_AP_START:
_mdns_enable_pcb(MDNS_IF_AP, MDNS_IP_PROTOCOL_V4);
break;
case WIFI_EVENT_AP_STOP:
_mdns_disable_pcb(MDNS_IF_AP, MDNS_IP_PROTOCOL_V4);
_mdns_disable_pcb(MDNS_IF_AP, MDNS_IP_PROTOCOL_V6);
break;
default:
break;
}
}
#if CONFIG_ETH_ENABLED
else if (event_base == ETH_EVENT) {
switch (event_id) {
case ETHERNET_EVENT_CONNECTED:
if (!esp_netif_dhcpc_get_status(_mdns_get_esp_netif(MDNS_IF_ETH), &dcst)) {
if (dcst == ESP_NETIF_DHCP_STOPPED) {
_mdns_enable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V4);
}
}
break;
case ETHERNET_EVENT_DISCONNECTED:
_mdns_disable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V4);
_mdns_disable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V6);
break;
default:
break;
}
}
#endif
else if (event_base == IP_EVENT) {
switch (event_id) {
case IP_EVENT_STA_GOT_IP:
_mdns_enable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V4);
_mdns_announce_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V6, NULL, 0, true);
break;
#if CONFIG_ETH_ENABLED
case IP_EVENT_ETH_GOT_IP:
_mdns_enable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V4);
break;
#endif
case IP_EVENT_GOT_IP6:
{
mdns_if_t mdns_if = _mdns_get_if_from_esp_netif(interface);
if (mdns_if != MDNS_IF_MAX) {
_mdns_enable_pcb(mdns_if, MDNS_IP_PROTOCOL_V6);
_mdns_announce_pcb(mdns_if, MDNS_IP_PROTOCOL_V4, NULL, 0, true);
}
}
break;
default:
break;
}
}
}
/*
* MDNS Search
* */
/**
* @brief Free search structure (except the results)
*/
static void _mdns_search_free(mdns_search_once_t * search)
{
free(search->instance);
free(search->service);
free(search->proto);
vSemaphoreDelete(search->done_semaphore);
free(search);
}
/**
* @brief Allocate new search structure
*/
static mdns_search_once_t * _mdns_search_init(const char * name, const char * service, const char * proto, uint16_t type, uint32_t timeout, uint8_t max_results)
{
mdns_search_once_t * search = (mdns_search_once_t *)malloc(sizeof(mdns_search_once_t));
if (!search) {
HOOK_MALLOC_FAILED;
return NULL;
}
memset(search, 0, sizeof(mdns_search_once_t));
search->done_semaphore = xSemaphoreCreateBinary();
if (!search->done_semaphore) {
free(search);
return NULL;
}
if (!_str_null_or_empty(name)) {
search->instance = strndup(name, MDNS_NAME_BUF_LEN-1);
if (!search->instance) {
_mdns_search_free(search);
return NULL;
}
}
if (!_str_null_or_empty(service)) {
search->service = strndup(service, MDNS_NAME_BUF_LEN-1);
if (!search->service) {
_mdns_search_free(search);
return NULL;
}
}
if (!_str_null_or_empty(proto)) {
search->proto = strndup(proto, MDNS_NAME_BUF_LEN-1);
if (!search->proto) {
_mdns_search_free(search);
return NULL;
}
}
search->type = type;
search->timeout = timeout;
search->num_results = 0;
search->max_results = max_results;
search->result = NULL;
search->state = SEARCH_INIT;
search->sent_at = 0;
search->started_at = xTaskGetTickCount() * portTICK_PERIOD_MS;
search->next = NULL;
return search;
}
/**
* @brief Mark search as finished and remove it from search chain
*/
static void _mdns_search_finish(mdns_search_once_t * search)
{
search->state = SEARCH_OFF;
queueDetach(mdns_search_once_t, _mdns_server->search_once, search);
xSemaphoreGive(search->done_semaphore);
}
/**
* @brief Add new search to the search chain
*/
static void _mdns_search_add(mdns_search_once_t * search)
{
search->next = _mdns_server->search_once;
_mdns_server->search_once = search;
}
/**
* @brief Called from parser to finish any searches that have reached maximum results
*/
static void _mdns_search_finish_done(void)
{
mdns_search_once_t * search = _mdns_server->search_once;
mdns_search_once_t * s = NULL;
while (search) {
s = search;
search = search->next;
if (s->max_results && s->num_results >= s->max_results) {
_mdns_search_finish(s);
}
}
}
/**
* @brief Create linked IP (copy) from parsed one
*/
static mdns_ip_addr_t * _mdns_result_addr_create_ip(esp_ip_addr_t * ip)
{
mdns_ip_addr_t * a = (mdns_ip_addr_t *)malloc(sizeof(mdns_ip_addr_t));
if (!a) {
HOOK_MALLOC_FAILED;
return NULL;
}
memset(a, 0 , sizeof(mdns_ip_addr_t));
a->addr.type = ip->type;
if (ip->type == IPADDR_TYPE_V6) {
memcpy(a->addr.u_addr.ip6.addr, ip->u_addr.ip6.addr, 16);
} else {
a->addr.u_addr.ip4.addr = ip->u_addr.ip4.addr;
}
return a;
}
/**
* @brief Chain new IP to search result
*/
static void _mdns_result_add_ip(mdns_result_t * r, esp_ip_addr_t * ip)
{
mdns_ip_addr_t * a = r->addr;
while (a) {
if (a->addr.type == ip->type) {
if (a->addr.type == IPADDR_TYPE_V4 && a->addr.u_addr.ip4.addr == ip->u_addr.ip4.addr) {
return;
}
if (a->addr.type == IPADDR_TYPE_V6 && !memcmp(a->addr.u_addr.ip6.addr, ip->u_addr.ip6.addr, 16)) {
return;
}
}
a = a->next;
}
a = _mdns_result_addr_create_ip(ip);
if (!a) {
return;
}
a->next = r->addr;
r->addr = a;
}
/**
* @brief Called from parser to add A/AAAA data to search result
*/
static void _mdns_search_result_add_ip(mdns_search_once_t * search, const char * hostname, esp_ip_addr_t * ip, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
mdns_result_t * r = NULL;
mdns_ip_addr_t * a = NULL;
if ((search->type == MDNS_TYPE_A && ip->type == IPADDR_TYPE_V4)
|| (search->type == MDNS_TYPE_AAAA && ip->type == IPADDR_TYPE_V6)
|| search->type == MDNS_TYPE_ANY) {
r = search->result;
while (r) {
if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol) {
_mdns_result_add_ip(r, ip);
return;
}
r = r->next;
}
if (!search->max_results || search->num_results < search->max_results) {
r = (mdns_result_t *)malloc(sizeof(mdns_result_t));
if (!r) {
HOOK_MALLOC_FAILED;
return;
}
memset(r, 0 , sizeof(mdns_result_t));
a = _mdns_result_addr_create_ip(ip);
if (!a) {
free(r);
return;
}
a->next = r->addr;
r->addr = a;
r->tcpip_if = tcpip_if;
r->ip_protocol = ip_protocol;
r->next = search->result;
search->result = r;
search->num_results++;
}
} else if (search->type == MDNS_TYPE_PTR) {
r = search->result;
while (r) {
if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->hostname) && !strcasecmp(hostname, r->hostname)) {
_mdns_result_add_ip(r, ip);
break;
}
r = r->next;
}
}
}
/**
* @brief Called from parser to add PTR data to search result
*/
static mdns_result_t * _mdns_search_result_add_ptr(mdns_search_once_t * search, const char * instance, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
mdns_result_t * r = search->result;
while (r) {
if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->instance_name) && !strcasecmp(instance, r->instance_name)) {
return r;
}
r = r->next;
}
if (!search->max_results || search->num_results < search->max_results) {
r = (mdns_result_t *)malloc(sizeof(mdns_result_t));
if (!r) {
HOOK_MALLOC_FAILED;
return NULL;
}
memset(r, 0 , sizeof(mdns_result_t));
r->instance_name = strdup(instance);
if (!r->instance_name) {
free(r);
return NULL;
}
r->tcpip_if = tcpip_if;
r->ip_protocol = ip_protocol;
r->next = search->result;
search->result = r;
search->num_results++;
return r;
}
return NULL;
}
/**
* @brief Called from parser to add SRV data to search result
*/
static void _mdns_search_result_add_srv(mdns_search_once_t * search, const char * hostname, uint16_t port, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
mdns_result_t * r = search->result;
while (r) {
if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->hostname) && !strcasecmp(hostname, r->hostname)) {
return;
}
r = r->next;
}
if (!search->max_results || search->num_results < search->max_results) {
r = (mdns_result_t *)malloc(sizeof(mdns_result_t));
if (!r) {
HOOK_MALLOC_FAILED;
return;
}
memset(r, 0 , sizeof(mdns_result_t));
r->hostname = strdup(hostname);
if (!r->hostname) {
free(r);
return;
}
r->port = port;
r->tcpip_if = tcpip_if;
r->ip_protocol = ip_protocol;
r->next = search->result;
search->result = r;
search->num_results++;
}
}
/**
* @brief Called from parser to add TXT data to search result
*/
static void _mdns_search_result_add_txt(mdns_search_once_t * search, mdns_txt_item_t * txt, size_t txt_count, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
mdns_result_t * r = search->result;
while (r) {
if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol) {
if (r->txt) {
goto free_txt;
}
r->txt = txt;
r->txt_count = txt_count;
return;
}
r = r->next;
}
if (!search->max_results || search->num_results < search->max_results) {
r = (mdns_result_t *)malloc(sizeof(mdns_result_t));
if (!r) {
HOOK_MALLOC_FAILED;
goto free_txt;
}
memset(r, 0 , sizeof(mdns_result_t));
r->txt = txt;
r->txt_count = txt_count;
r->tcpip_if = tcpip_if;
r->ip_protocol = ip_protocol;
r->next = search->result;
search->result = r;
search->num_results++;
}
return;
free_txt:
for (size_t i=0; i<txt_count; i++) {
free((char *)(txt[i].key));
free((char *)(txt[i].value));
}
free(txt);
}
/**
* @brief Called from packet parser to find matching running search
*/
static mdns_search_once_t * _mdns_search_find_from(mdns_search_once_t * s, mdns_name_t * name, uint16_t type, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
mdns_result_t * r = NULL;
while (s) {
if (s->state == SEARCH_OFF) {
s = s->next;
continue;
}
if (type == MDNS_TYPE_A || type == MDNS_TYPE_AAAA) {
if ((s->type == MDNS_TYPE_ANY && s->service != NULL)
|| (s->type != MDNS_TYPE_ANY && s->type != type && s->type != MDNS_TYPE_PTR))
{
s = s->next;
continue;
}
if (s->type != MDNS_TYPE_PTR) {
if (!strcasecmp(name->host, s->instance)) {
return s;
}
s = s->next;
continue;
}
r = s->result;
while (r) {
if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && !_str_null_or_empty(r->hostname) && !strcasecmp(name->host, r->hostname)) {
return s;
}
r = r->next;
}
s = s->next;
continue;
}
if (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT) {
if ((s->type == MDNS_TYPE_ANY && s->service == NULL)
|| (s->type != MDNS_TYPE_ANY && s->type != type && s->type != MDNS_TYPE_PTR))
{
s = s->next;
continue;
}
if (strcasecmp(name->service, s->service)
|| strcasecmp(name->proto, s->proto))
{
s = s->next;
continue;
}
if (s->type != MDNS_TYPE_PTR) {
if (!strcasecmp(name->host, s->instance)) {
return s;
}
s = s->next;
continue;
}
return s;
}
if (type == MDNS_TYPE_PTR && type == s->type && !strcasecmp(name->service, s->service) && !strcasecmp(name->proto, s->proto)) {
return s;
}
s = s->next;
}
return NULL;
}
/**
* @brief Create search packet for particular interface
*/
static mdns_tx_packet_t * _mdns_create_search_packet(mdns_search_once_t * search, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
mdns_result_t * r = NULL;
mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol);
if (!packet) {
return NULL;
}
mdns_out_question_t * q = (mdns_out_question_t *)malloc(sizeof(mdns_out_question_t));
if (!q) {
HOOK_MALLOC_FAILED;
_mdns_free_tx_packet(packet);
return NULL;
}
q->next = NULL;
q->unicast = search->type != MDNS_TYPE_PTR;
q->type = search->type;
q->host = search->instance;
q->service = search->service;
q->proto = search->proto;
q->domain = MDNS_DEFAULT_DOMAIN;
q->own_dynamic_memory = false;
queueToEnd(mdns_out_question_t, packet->questions, q);
if (search->type == MDNS_TYPE_PTR) {
r = search->result;
while (r) {
//full record on the same interface is available
if (r->tcpip_if != tcpip_if || r->ip_protocol != ip_protocol || r->instance_name == NULL || r->hostname == NULL || r->addr == NULL) {
r = r->next;
continue;
}
mdns_out_answer_t * a = (mdns_out_answer_t *)malloc(sizeof(mdns_out_answer_t));
if (!a) {
HOOK_MALLOC_FAILED;
_mdns_free_tx_packet(packet);
return NULL;
}
a->type = MDNS_TYPE_PTR;
a->service = NULL;
a->custom_instance = r->instance_name;
a->custom_service = search->service;
a->custom_proto = search->proto;
a->bye = false;
a->flush = false;
a->next = NULL;
queueToEnd(mdns_out_answer_t, packet->answers, a);
r = r->next;
}
}
return packet;
}
/**
* @brief Send search packet to particular interface
*/
static void _mdns_search_send_pcb(mdns_search_once_t * search, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
mdns_tx_packet_t * packet = NULL;
if (_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb && _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].state > PCB_INIT) {
packet = _mdns_create_search_packet(search, tcpip_if, ip_protocol);
if (!packet) {
return;
}
_mdns_dispatch_tx_packet(packet);
_mdns_free_tx_packet(packet);
}
}
/**
* @brief Send search packet to all available interfaces
*/
static void _mdns_search_send(mdns_search_once_t * search)
{
mdns_search_once_t* queue = _mdns_server->search_once;
bool found = false;
// looking for this search in active searches
while (queue) {
if (queue == search) {
found = true;
break;
}
queue = queue->next;
}
if (!found) {
// no longer active -> skip sending this search
return;
}
uint8_t i, j;
for (i=0; i<MDNS_IF_MAX; i++) {
for (j=0; j<MDNS_IP_PROTOCOL_MAX; j++) {
_mdns_search_send_pcb(search, (mdns_if_t)i, (mdns_ip_protocol_t)j);
}
}
}
static void _mdns_tx_handle_packet(mdns_tx_packet_t * p)
{
mdns_tx_packet_t * a = NULL;
mdns_out_question_t * q = NULL;
mdns_pcb_t * pcb = &_mdns_server->interfaces[p->tcpip_if].pcbs[p->ip_protocol];
uint32_t send_after = 1000;
if (pcb->state == PCB_OFF) {
_mdns_free_tx_packet(p);
return;
}
_mdns_dispatch_tx_packet(p);
switch(pcb->state) {
case PCB_PROBE_1:
q = p->questions;
while (q) {
q->unicast = false;
q = q->next;
}
//fallthrough
case PCB_PROBE_2:
_mdns_schedule_tx_packet(p, 250);
pcb->state = (mdns_pcb_state_t)((uint8_t)(pcb->state) + 1);
break;
case PCB_PROBE_3:
a = _mdns_create_announce_from_probe(p);
if (!a) {
_mdns_schedule_tx_packet(p, 250);
break;
}
pcb->probe_running = false;
pcb->probe_ip = false;
pcb->probe_services_len = 0;
pcb->failed_probes = 0;
free(pcb->probe_services);
pcb->probe_services = NULL;
_mdns_free_tx_packet(p);
p = a;
send_after = 250;
//fallthrough
case PCB_ANNOUNCE_1:
//fallthrough
case PCB_ANNOUNCE_2:
_mdns_schedule_tx_packet(p, send_after);
pcb->state = (mdns_pcb_state_t)((uint8_t)(pcb->state) + 1);
break;
case PCB_ANNOUNCE_3:
pcb->state = PCB_RUNNING;
_mdns_free_tx_packet(p);
break;
default:
_mdns_free_tx_packet(p);
break;
}
}
/**
* @brief Free action data
*/
static void _mdns_free_action(mdns_action_t * action)
{
switch(action->type) {
case ACTION_HOSTNAME_SET:
free(action->data.hostname);
break;
case ACTION_INSTANCE_SET:
free(action->data.instance);
break;
case ACTION_SERVICE_ADD:
_mdns_free_service(action->data.srv_add.service->service);
free(action->data.srv_add.service);
break;
case ACTION_SERVICE_INSTANCE_SET:
free(action->data.srv_instance.instance);
break;
case ACTION_SERVICE_TXT_REPLACE:
_mdns_free_linked_txt(action->data.srv_txt_replace.txt);
break;
case ACTION_SERVICE_TXT_SET:
free(action->data.srv_txt_set.key);
free(action->data.srv_txt_set.value);
break;
case ACTION_SERVICE_TXT_DEL:
free(action->data.srv_txt_del.key);
break;
case ACTION_SEARCH_ADD:
//fallthrough
case ACTION_SEARCH_SEND:
//fallthrough
case ACTION_SEARCH_END:
_mdns_search_free(action->data.search_add.search);
break;
case ACTION_TX_HANDLE:
_mdns_free_tx_packet(action->data.tx_handle.packet);
break;
case ACTION_RX_HANDLE:
pbuf_free(action->data.rx_handle.packet->pb);
free(action->data.rx_handle.packet);
break;
default:
break;
}
free(action);
}
/**
* @brief Called from service thread to execute given action
*/
static void _mdns_execute_action(mdns_action_t * action)
{
mdns_srv_item_t * a = NULL;
mdns_service_t * service;
char * key;
char * value;
mdns_txt_linked_item_t * txt, * t;
switch(action->type) {
case ACTION_SYSTEM_EVENT:
_mdns_handle_system_event(action->data.sys_event.event_base,
action->data.sys_event.event_id, action->data.sys_event.interface);
break;
case ACTION_HOSTNAME_SET:
_mdns_send_bye_all_pcbs_no_instance(true);
free((char*)_mdns_server->hostname);
_mdns_server->hostname = action->data.hostname;
_mdns_restart_all_pcbs();
break;
case ACTION_INSTANCE_SET:
_mdns_send_bye_all_pcbs_no_instance(false);
free((char*)_mdns_server->instance);
_mdns_server->instance = action->data.instance;
_mdns_restart_all_pcbs_no_instance();
break;
case ACTION_SERVICE_ADD:
action->data.srv_add.service->next = _mdns_server->services;
_mdns_server->services = action->data.srv_add.service;
_mdns_probe_all_pcbs(&action->data.srv_add.service, 1, false, false);
break;
case ACTION_SERVICE_INSTANCE_SET:
if (action->data.srv_instance.service->service->instance) {
_mdns_send_bye(&action->data.srv_instance.service, 1, false);
free((char*)action->data.srv_instance.service->service->instance);
}
action->data.srv_instance.service->service->instance = action->data.srv_instance.instance;
_mdns_probe_all_pcbs(&action->data.srv_instance.service, 1, false, false);
break;
case ACTION_SERVICE_PORT_SET:
action->data.srv_port.service->service->port = action->data.srv_port.port;
_mdns_announce_all_pcbs(&action->data.srv_port.service, 1, true);
break;
case ACTION_SERVICE_TXT_REPLACE:
service = action->data.srv_txt_replace.service->service;
txt = service->txt;
service->txt = NULL;
_mdns_free_linked_txt(txt);
service->txt = action->data.srv_txt_replace.txt;
_mdns_announce_all_pcbs(&action->data.srv_txt_replace.service, 1, false);
break;
case ACTION_SERVICE_TXT_SET:
service = action->data.srv_txt_set.service->service;
key = action->data.srv_txt_set.key;
value = action->data.srv_txt_set.value;
txt = service->txt;
while (txt) {
if (strcmp(txt->key, key) == 0) {
free((char *)txt->value);
free(key);
txt->value = value;
break;
}
txt = txt->next;
}
if (!txt) {
txt = (mdns_txt_linked_item_t *)malloc(sizeof(mdns_txt_linked_item_t));
if (!txt) {
HOOK_MALLOC_FAILED;
_mdns_free_action(action);
return;
}
txt->key = key;
txt->value = value;
txt->next = service->txt;
service->txt = txt;
}
_mdns_announce_all_pcbs(&action->data.srv_txt_set.service, 1, false);
break;
case ACTION_SERVICE_TXT_DEL:
service = action->data.srv_txt_del.service->service;
key = action->data.srv_txt_del.key;
txt = service->txt;
if (!txt) {
break;
}
if (strcmp(txt->key, key) == 0) {
service->txt = txt->next;
free((char *)txt->key);
free((char *)txt->value);
free(txt);
} else {
while (txt->next) {
if (strcmp(txt->next->key, key) == 0) {
t = txt->next;
txt->next = t->next;
free((char *)t->key);
free((char *)t->value);
free(t);
break;
} else {
txt = txt->next;
}
}
}
free(key);
_mdns_announce_all_pcbs(&action->data.srv_txt_set.service, 1, false);
break;
case ACTION_SERVICE_DEL:
a = _mdns_server->services;
if (action->data.srv_del.service) {
if (_mdns_server->services == action->data.srv_del.service) {
_mdns_server->services = a->next;
_mdns_send_bye(&a, 1, false);
_mdns_remove_scheduled_service_packets(a->service);
_mdns_free_service(a->service);
free(a);
} else {
while (a->next && a->next != action->data.srv_del.service) {
a = a->next;
}
if (a->next == action->data.srv_del.service) {
mdns_srv_item_t * b = a->next;
a->next = a->next->next;
_mdns_send_bye(&b, 1, false);
_mdns_remove_scheduled_service_packets(b->service);
_mdns_free_service(b->service);
free(b);
}
}
}
break;
case ACTION_SERVICES_CLEAR:
_mdns_send_final_bye(false);
a = _mdns_server->services;
_mdns_server->services = NULL;
while (a) {
mdns_srv_item_t * s = a;
a = a->next;
_mdns_remove_scheduled_service_packets(s->service);
_mdns_free_service(s->service);
free(s);
}
break;
case ACTION_SEARCH_ADD:
_mdns_search_add(action->data.search_add.search);
break;
case ACTION_SEARCH_SEND:
_mdns_search_send(action->data.search_add.search);
break;
case ACTION_SEARCH_END:
_mdns_search_finish(action->data.search_add.search);
break;
case ACTION_TX_HANDLE:
{
mdns_tx_packet_t * p = _mdns_server->tx_queue_head;
// packet to be handled should be at tx head, but must be consistent with the one pushed to action queue
if (p && p==action->data.tx_handle.packet && p->queued) {
p->queued = false; // clearing, as the packet might be reused (pushed and transmitted again)
_mdns_server->tx_queue_head = p->next;
_mdns_tx_handle_packet(p);
} else {
ESP_LOGD(TAG, "Skipping transmit of an unexpected packet!");
}
}
break;
case ACTION_RX_HANDLE:
mdns_parse_packet(action->data.rx_handle.packet);
pbuf_free(action->data.rx_handle.packet->pb);
free(action->data.rx_handle.packet);
break;
default:
break;
}
free(action);
}
/**
* @brief Queue search action
*/
static esp_err_t _mdns_send_search_action(mdns_action_type_t type, mdns_search_once_t * search)
{
mdns_action_t * action = NULL;
action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
action->type = type;
action->data.search_add.search = search;
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
/**
* @brief Called from timer task to run mDNS responder
*
* periodically checks first unqueued packet (from tx head).
* if it is scheduled to be transmitted, then pushes the packet to action queue to be handled.
*
*/
static void _mdns_scheduler_run(void)
{
MDNS_SERVICE_LOCK();
mdns_tx_packet_t * p = _mdns_server->tx_queue_head;
mdns_action_t * action = NULL;
// find first unqueued packet
while (p && p->queued) {
p = p->next;
}
if (!p) {
MDNS_SERVICE_UNLOCK();
return;
}
if ((int32_t)(p->send_at - (xTaskGetTickCount() * portTICK_PERIOD_MS)) < 0) {
action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (action) {
action->type = ACTION_TX_HANDLE;
action->data.tx_handle.packet = p;
p->queued = true;
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(action);
p->queued = false;
}
} else {
HOOK_MALLOC_FAILED;
// continue
}
}
MDNS_SERVICE_UNLOCK();
}
/**
* @brief Called from timer task to run active searches
*/
static void _mdns_search_run(void)
{
MDNS_SERVICE_LOCK();
mdns_search_once_t * s = _mdns_server->search_once;
uint32_t now = xTaskGetTickCount() * portTICK_PERIOD_MS;
if (!s) {
MDNS_SERVICE_UNLOCK();
return;
}
while (s) {
if (s->state != SEARCH_OFF) {
if (now > (s->started_at + s->timeout)) {
s->state = SEARCH_OFF;
if (_mdns_send_search_action(ACTION_SEARCH_END, s) != ESP_OK) {
s->state = SEARCH_RUNNING;
}
} else if (s->state == SEARCH_INIT || (now - s->sent_at) > 1000) {
s->state = SEARCH_RUNNING;
s->sent_at = now;
if (_mdns_send_search_action(ACTION_SEARCH_SEND, s) != ESP_OK) {
s->sent_at -= 1000;
}
}
}
s = s->next;
}
MDNS_SERVICE_UNLOCK();
}
/**
* @brief the main MDNS service task. Packets are received and parsed here
*/
static void _mdns_service_task(void *pvParameters)
{
mdns_action_t * a = NULL;
for (;;) {
if (_mdns_server && _mdns_server->action_queue) {
if (xQueueReceive(_mdns_server->action_queue, &a, portMAX_DELAY) == pdTRUE) {
if (a->type == ACTION_TASK_STOP) {
break;
}
MDNS_SERVICE_LOCK();
_mdns_execute_action(a);
MDNS_SERVICE_UNLOCK();
}
} else {
vTaskDelay(500 * portTICK_PERIOD_MS);
}
}
_mdns_service_task_handle = NULL;
vTaskDelete(NULL);
}
static void _mdns_timer_cb(void * arg)
{
_mdns_scheduler_run();
_mdns_search_run();
}
static esp_err_t _mdns_start_timer(void){
esp_timer_create_args_t timer_conf = {
.callback = _mdns_timer_cb,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "mdns_timer"
};
esp_err_t err = esp_timer_create(&timer_conf, &(_mdns_server->timer_handle));
if (err) {
return err;
}
return esp_timer_start_periodic(_mdns_server->timer_handle, MDNS_TIMER_PERIOD_US);
}
static esp_err_t _mdns_stop_timer(void){
esp_err_t err = ESP_OK;
if (_mdns_server->timer_handle) {
err = esp_timer_stop(_mdns_server->timer_handle);
if (err) {
return err;
}
err = esp_timer_delete(_mdns_server->timer_handle);
}
return err;
}
/**
* @brief Start the service thread if not running
*
* @return
* - ESP_OK on success
* - ESP_FAIL on error
*/
static esp_err_t _mdns_service_task_start(void)
{
if (!_mdns_service_semaphore) {
_mdns_service_semaphore = xSemaphoreCreateMutex();
if (!_mdns_service_semaphore) {
return ESP_FAIL;
}
}
MDNS_SERVICE_LOCK();
if (_mdns_start_timer()) {
MDNS_SERVICE_UNLOCK();
return ESP_FAIL;
}
if (!_mdns_service_task_handle) {
xTaskCreatePinnedToCore(_mdns_service_task, "mdns", MDNS_SERVICE_STACK_DEPTH, NULL, MDNS_TASK_PRIORITY,
(TaskHandle_t * const)(&_mdns_service_task_handle), MDNS_TASK_AFFINITY);
if (!_mdns_service_task_handle) {
_mdns_stop_timer();
MDNS_SERVICE_UNLOCK();
vSemaphoreDelete(_mdns_service_semaphore);
_mdns_service_semaphore = NULL;
return ESP_FAIL;
}
}
MDNS_SERVICE_UNLOCK();
return ESP_OK;
}
/**
* @brief Stop the service thread
*
* @return
* - ESP_OK
*/
static esp_err_t _mdns_service_task_stop(void)
{
_mdns_stop_timer();
if (_mdns_service_task_handle) {
mdns_action_t action;
mdns_action_t * a = &action;
action.type = ACTION_TASK_STOP;
if (xQueueSend(_mdns_server->action_queue, &a, (portTickType)0) != pdPASS) {
vTaskDelete(_mdns_service_task_handle);
_mdns_service_task_handle = NULL;
}
while (_mdns_service_task_handle) {
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
vSemaphoreDelete(_mdns_service_semaphore);
_mdns_service_semaphore = NULL;
return ESP_OK;
}
/*
* Public Methods
* */
esp_err_t mdns_handle_system_event(void *ctx, system_event_t *event)
{
/* no-op, kept for compatibility */
return ESP_OK;
}
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (!_mdns_server) {
return;
}
mdns_action_t * action = (mdns_action_t *)calloc(1, sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
return;
}
action->type = ACTION_SYSTEM_EVENT;
action->data.sys_event.event_base = event_base;
action->data.sys_event.event_id = event_id;
if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) {
ip_event_got_ip6_t* event = (ip_event_got_ip6_t*) event_data;
action->data.sys_event.interface = event->esp_netif;
}
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(action);
}
}
esp_err_t mdns_init(void)
{
esp_err_t err = ESP_OK;
if (_mdns_server) {
return err;
}
_mdns_server = (mdns_server_t *)malloc(sizeof(mdns_server_t));
if (!_mdns_server) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
memset((uint8_t*)_mdns_server, 0, sizeof(mdns_server_t));
// zero-out local copy of netifs to initiate a fresh search by interface key whenever a netif ptr is needed
memset(s_esp_netifs, 0, sizeof(s_esp_netifs));
_mdns_server->lock = xSemaphoreCreateMutex();
if (!_mdns_server->lock) {
err = ESP_ERR_NO_MEM;
goto free_server;
}
_mdns_server->action_queue = xQueueCreate(MDNS_ACTION_QUEUE_LEN, sizeof(mdns_action_t *));
if (!_mdns_server->action_queue) {
err = ESP_ERR_NO_MEM;
goto free_lock;
}
if ((err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)) != ESP_OK) {
goto free_event_handlers;
}
if ((err = esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)) != ESP_OK) {
goto free_event_handlers;
}
#if CONFIG_ETH_ENABLED
if ((err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)) != ESP_OK) {
goto free_event_handlers;
}
#endif
uint8_t i;
#if CONFIG_LWIP_IPV6
esp_ip6_addr_t tmp_addr6;
#endif
esp_netif_ip_info_t if_ip_info;
for (i=0; i<MDNS_IF_MAX; i++) {
#if CONFIG_LWIP_IPV6
if (!esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(i), &tmp_addr6) && !_ipv6_address_is_zero(tmp_addr6)) {
_mdns_enable_pcb(i, MDNS_IP_PROTOCOL_V6);
}
#endif
if (!esp_netif_get_ip_info(_mdns_get_esp_netif(i), &if_ip_info) && if_ip_info.ip.addr) {
_mdns_enable_pcb(i, MDNS_IP_PROTOCOL_V4);
}
}
if (_mdns_service_task_start()) {
//service start failed!
err = ESP_FAIL;
goto free_all_and_disable_pcbs;
}
return ESP_OK;
free_all_and_disable_pcbs:
for (i=0; i<MDNS_IF_MAX; i++) {
_mdns_disable_pcb(i, MDNS_IP_PROTOCOL_V6);
_mdns_disable_pcb(i, MDNS_IP_PROTOCOL_V4);
}
free_event_handlers:
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler);
esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler);
#if CONFIG_ETH_ENABLED
esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, &event_handler);
#endif
vQueueDelete(_mdns_server->action_queue);
free_lock:
vSemaphoreDelete(_mdns_server->lock);
free_server:
free(_mdns_server);
_mdns_server = NULL;
return err;
}
void mdns_free(void)
{
uint8_t i, j;
if (!_mdns_server) {
return;
}
// Unregister handlers before destroying the mdns internals to avoid receiving async events while deinit
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler);
esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler);
#if CONFIG_ETH_ENABLED
esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, &event_handler);
#endif
mdns_service_remove_all();
_mdns_service_task_stop();
for (i=0; i<MDNS_IF_MAX; i++) {
for (j=0; j<MDNS_IP_PROTOCOL_MAX; j++) {
_mdns_pcb_deinit(i, j);
}
}
free((char*)_mdns_server->hostname);
free((char*)_mdns_server->instance);
if (_mdns_server->action_queue) {
mdns_action_t * c;
while (xQueueReceive(_mdns_server->action_queue, &c, 0) == pdTRUE) {
_mdns_free_action(c);
}
vQueueDelete(_mdns_server->action_queue);
}
_mdns_clear_tx_queue_head();
while (_mdns_server->search_once) {
mdns_search_once_t * h = _mdns_server->search_once;
_mdns_server->search_once = h->next;
free(h->instance);
free(h->service);
free(h->proto);
vSemaphoreDelete(h->done_semaphore);
if (h->result) {
mdns_query_results_free(h->result);
}
free(h);
}
vSemaphoreDelete(_mdns_server->lock);
free(_mdns_server);
_mdns_server = NULL;
}
esp_err_t mdns_hostname_set(const char * hostname)
{
if (!_mdns_server) {
return ESP_ERR_INVALID_ARG;
}
if (_str_null_or_empty(hostname) || strlen(hostname) > (MDNS_NAME_BUF_LEN - 1)) {
return ESP_ERR_INVALID_ARG;
}
char * new_hostname = strndup(hostname, MDNS_NAME_BUF_LEN - 1);
if (!new_hostname) {
return ESP_ERR_NO_MEM;
}
mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
free(new_hostname);
return ESP_ERR_NO_MEM;
}
action->type = ACTION_HOSTNAME_SET;
action->data.hostname = new_hostname;
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(new_hostname);
free(action);
return ESP_ERR_NO_MEM;
}
return ERR_OK;
}
esp_err_t mdns_instance_name_set(const char * instance)
{
if (!_mdns_server) {
return ESP_ERR_INVALID_ARG;
}
if (_str_null_or_empty(instance) || strlen(instance) > (MDNS_NAME_BUF_LEN - 1)) {
return ESP_ERR_INVALID_ARG;
}
char * new_instance = strndup(instance, MDNS_NAME_BUF_LEN - 1);
if (!new_instance) {
return ESP_ERR_NO_MEM;
}
mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
free(new_instance);
return ESP_ERR_NO_MEM;
}
action->type = ACTION_INSTANCE_SET;
action->data.instance = new_instance;
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(new_instance);
free(action);
return ESP_ERR_NO_MEM;
}
return ERR_OK;
}
/*
* MDNS SERVICES
* */
esp_err_t mdns_service_add(const char * instance, const char * service, const char * proto, uint16_t port, mdns_txt_item_t txt[], size_t num_items)
{
if (!_mdns_server || _str_null_or_empty(service) || _str_null_or_empty(proto) || !port) {
return ESP_ERR_INVALID_ARG;
}
if (!_mdns_can_add_more_services()) {
return ESP_ERR_NO_MEM;
}
mdns_srv_item_t * item = _mdns_get_service_item(service, proto);
if (item) {
return ESP_ERR_INVALID_ARG;
}
mdns_service_t * s = _mdns_create_service(service, proto, port, instance, num_items, txt);
if (!s) {
return ESP_ERR_NO_MEM;
}
item = (mdns_srv_item_t *)malloc(sizeof(mdns_srv_item_t));
if (!item) {
HOOK_MALLOC_FAILED;
_mdns_free_service(s);
return ESP_ERR_NO_MEM;
}
item->service = s;
item->next = NULL;
mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
_mdns_free_service(s);
free(item);
return ESP_ERR_NO_MEM;
}
action->type = ACTION_SERVICE_ADD;
action->data.srv_add.service = item;
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
_mdns_free_service(s);
free(item);
free(action);
return ESP_ERR_NO_MEM;
}
size_t start = xTaskGetTickCount();
size_t timeout_ticks = pdMS_TO_TICKS(MDNS_SERVICE_ADD_TIMEOUT_MS);
while (_mdns_get_service_item(service, proto) == NULL)
{
uint32_t expired = xTaskGetTickCount() - start;
if (expired >= timeout_ticks) {
return ESP_FAIL; // Timeout
}
vTaskDelay(MIN(10 / portTICK_RATE_MS, timeout_ticks - expired));
}
return ESP_OK;
}
esp_err_t mdns_service_port_set(const char * service, const char * proto, uint16_t port)
{
if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) || !port) {
return ESP_ERR_INVALID_ARG;
}
mdns_srv_item_t * s = _mdns_get_service_item(service, proto);
if (!s) {
return ESP_ERR_NOT_FOUND;
}
mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
action->type = ACTION_SERVICE_PORT_SET;
action->data.srv_port.service = s;
action->data.srv_port.port = port;
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
esp_err_t mdns_service_txt_set(const char * service, const char * proto, mdns_txt_item_t txt[], uint8_t num_items)
{
if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) || (num_items && txt == NULL)) {
return ESP_ERR_INVALID_ARG;
}
mdns_srv_item_t * s = _mdns_get_service_item(service, proto);
if (!s) {
return ESP_ERR_NOT_FOUND;
}
mdns_txt_linked_item_t * new_txt = NULL;
if (num_items){
new_txt = _mdns_allocate_txt(num_items, txt);
if (!new_txt) {
return ESP_ERR_NO_MEM;
}
}
mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
_mdns_free_linked_txt(new_txt);
return ESP_ERR_NO_MEM;
}
action->type = ACTION_SERVICE_TXT_REPLACE;
action->data.srv_txt_replace.service = s;
action->data.srv_txt_replace.txt = new_txt;
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
_mdns_free_linked_txt(new_txt);
free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
esp_err_t mdns_service_txt_item_set(const char * service, const char * proto, const char * key, const char * value)
{
if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) || _str_null_or_empty(key) || !value) {
return ESP_ERR_INVALID_ARG;
}
mdns_srv_item_t * s = _mdns_get_service_item(service, proto);
if (!s) {
return ESP_ERR_NOT_FOUND;
}
mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
action->type = ACTION_SERVICE_TXT_SET;
action->data.srv_txt_set.service = s;
action->data.srv_txt_set.key = strdup(key);
if (!action->data.srv_txt_set.key) {
free(action);
return ESP_ERR_NO_MEM;
}
action->data.srv_txt_set.value = strdup(value);
if (!action->data.srv_txt_set.value) {
free(action->data.srv_txt_set.key);
free(action);
return ESP_ERR_NO_MEM;
}
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(action->data.srv_txt_set.key);
free(action->data.srv_txt_set.value);
free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
esp_err_t mdns_service_txt_item_remove(const char * service, const char * proto, const char * key)
{
if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) || _str_null_or_empty(key)) {
return ESP_ERR_INVALID_ARG;
}
mdns_srv_item_t * s = _mdns_get_service_item(service, proto);
if (!s) {
return ESP_ERR_NOT_FOUND;
}
mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
action->type = ACTION_SERVICE_TXT_DEL;
action->data.srv_txt_del.service = s;
action->data.srv_txt_del.key = strdup(key);
if (!action->data.srv_txt_del.key) {
free(action);
return ESP_ERR_NO_MEM;
}
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(action->data.srv_txt_del.key);
free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
esp_err_t mdns_service_instance_name_set(const char * service, const char * proto, const char * instance)
{
if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto)) {
return ESP_ERR_INVALID_ARG;
}
if (_str_null_or_empty(instance) || strlen(instance) > (MDNS_NAME_BUF_LEN - 1)) {
return ESP_ERR_INVALID_ARG;
}
mdns_srv_item_t * s = _mdns_get_service_item(service, proto);
if (!s) {
return ESP_ERR_NOT_FOUND;
}
char * new_instance = strndup(instance, MDNS_NAME_BUF_LEN - 1);
if (!new_instance) {
return ESP_ERR_NO_MEM;
}
mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
free(new_instance);
return ESP_ERR_NO_MEM;
}
action->type = ACTION_SERVICE_INSTANCE_SET;
action->data.srv_instance.service = s;
action->data.srv_instance.instance = new_instance;
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(new_instance);
free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
esp_err_t mdns_service_remove(const char * service, const char * proto)
{
if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto)) {
return ESP_ERR_INVALID_ARG;
}
mdns_srv_item_t * s = _mdns_get_service_item(service, proto);
if (!s) {
return ESP_ERR_NOT_FOUND;
}
mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
action->type = ACTION_SERVICE_DEL;
action->data.srv_del.service = s;
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
esp_err_t mdns_service_remove_all(void)
{
if (!_mdns_server) {
return ESP_ERR_INVALID_ARG;
}
if (!_mdns_server->services) {
return ESP_OK;
}
mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
action->type = ACTION_SERVICES_CLEAR;
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
/*
* MDNS QUERY
* */
void mdns_query_results_free(mdns_result_t * results)
{
mdns_result_t * r;
mdns_ip_addr_t * a;
while (results) {
r = results;
free((char *)(r->hostname));
free((char *)(r->instance_name));
for (size_t i=0; i<r->txt_count; i++) {
free((char *)(r->txt[i].key));
free((char *)(r->txt[i].value));
}
free(r->txt);
while (r->addr) {
a = r->addr;
r->addr = r->addr->next;
free(a);
}
results = results->next;
free(r);
}
}
esp_err_t mdns_query(const char * name, const char * service, const char * proto, uint16_t type, uint32_t timeout, size_t max_results, mdns_result_t ** results)
{
mdns_search_once_t * search = NULL;
*results = NULL;
if (!_mdns_server) {
return ESP_ERR_INVALID_STATE;
}
if (!timeout || _str_null_or_empty(service) != _str_null_or_empty(proto)) {
return ESP_ERR_INVALID_ARG;
}
search = _mdns_search_init(name, service, proto, type, timeout, max_results);
if (!search) {
return ESP_ERR_NO_MEM;
}
if (_mdns_send_search_action(ACTION_SEARCH_ADD, search)) {
_mdns_search_free(search);
return ESP_ERR_NO_MEM;
}
xSemaphoreTake(search->done_semaphore, portMAX_DELAY);
*results = search->result;
_mdns_search_free(search);
return ESP_OK;
}
esp_err_t mdns_query_ptr(const char * service, const char * proto, uint32_t timeout, size_t max_results, mdns_result_t ** results)
{
if (_str_null_or_empty(service) || _str_null_or_empty(proto)) {
return ESP_ERR_INVALID_ARG;
}
return mdns_query(NULL, service, proto, MDNS_TYPE_PTR, timeout, max_results, results);
}
esp_err_t mdns_query_srv(const char * instance, const char * service, const char * proto, uint32_t timeout, mdns_result_t ** result)
{
if (_str_null_or_empty(instance) || _str_null_or_empty(service) || _str_null_or_empty(proto)) {
return ESP_ERR_INVALID_ARG;
}
return mdns_query(instance, service, proto, MDNS_TYPE_SRV, timeout, 1, result);
}
esp_err_t mdns_query_txt(const char * instance, const char * service, const char * proto, uint32_t timeout, mdns_result_t ** result)
{
if (_str_null_or_empty(instance) || _str_null_or_empty(service) || _str_null_or_empty(proto)) {
return ESP_ERR_INVALID_ARG;
}
return mdns_query(instance, service, proto, MDNS_TYPE_TXT, timeout, 1, result);
}
esp_err_t mdns_query_a(const char * name, uint32_t timeout, esp_ip4_addr_t * addr)
{
mdns_result_t * result = NULL;
esp_err_t err;
if (_str_null_or_empty(name)) {
return ESP_ERR_INVALID_ARG;
}
err = mdns_query(name, NULL, NULL, MDNS_TYPE_A, timeout, 1, &result);
if (err) {
return err;
}
if (!result) {
return ESP_ERR_NOT_FOUND;
}
mdns_ip_addr_t * a = result->addr;
while (a) {
if (a->addr.type == IPADDR_TYPE_V4) {
addr->addr = a->addr.u_addr.ip4.addr;
mdns_query_results_free(result);
return ESP_OK;
}
a = a->next;
}
mdns_query_results_free(result);
return ESP_ERR_NOT_FOUND;
}
#if CONFIG_LWIP_IPV6
esp_err_t mdns_query_aaaa(const char * name, uint32_t timeout, esp_ip6_addr_t * addr)
{
mdns_result_t * result = NULL;
esp_err_t err;
if (_str_null_or_empty(name)) {
return ESP_ERR_INVALID_ARG;
}
err = mdns_query(name, NULL, NULL, MDNS_TYPE_AAAA, timeout, 1, &result);
if (err) {
return err;
}
if (!result) {
return ESP_ERR_NOT_FOUND;
}
mdns_ip_addr_t * a = result->addr;
while (a) {
if (a->addr.type == IPADDR_TYPE_V6) {
memcpy(addr->addr, a->addr.u_addr.ip6.addr, 16);
mdns_query_results_free(result);
return ESP_OK;
}
a = a->next;
}
mdns_query_results_free(result);
return ESP_ERR_NOT_FOUND;
}
#endif
#ifdef MDNS_ENABLE_DEBUG
void mdns_debug_packet(const uint8_t * data, size_t len)
{
static mdns_name_t n;
mdns_header_t header;
const uint8_t * content = data + MDNS_HEAD_LEN;
uint32_t t = xTaskGetTickCount() * portTICK_PERIOD_MS;
mdns_name_t * name = &n;
memset(name, 0, sizeof(mdns_name_t));
_mdns_dbg_printf("Packet[%u]: ", t);
header.id = _mdns_read_u16(data, MDNS_HEAD_ID_OFFSET);
header.flags.value = _mdns_read_u16(data, MDNS_HEAD_FLAGS_OFFSET);
header.questions = _mdns_read_u16(data, MDNS_HEAD_QUESTIONS_OFFSET);
header.answers = _mdns_read_u16(data, MDNS_HEAD_ANSWERS_OFFSET);
header.servers = _mdns_read_u16(data, MDNS_HEAD_SERVERS_OFFSET);
header.additional = _mdns_read_u16(data, MDNS_HEAD_ADDITIONAL_OFFSET);
_mdns_dbg_printf("%s",
(header.flags.value == MDNS_FLAGS_AUTHORITATIVE)?"AUTHORITATIVE\n":
(header.flags.value == MDNS_FLAGS_DISTRIBUTED)?"DISTRIBUTED\n":
(header.flags.value == 0)?"\n":" "
);
if (header.flags.value && header.flags.value != MDNS_FLAGS_AUTHORITATIVE) {
_mdns_dbg_printf("0x%04X\n", header.flags.value);
}
if (header.questions) {
uint8_t qs = header.questions;
while (qs--) {
content = _mdns_parse_fqdn(data, content, name);
if (!content) {
header.answers = 0;
header.additional = 0;
header.servers = 0;
_mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__);
break;
}
uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET);
uint16_t mdns_class = _mdns_read_u16(content, MDNS_CLASS_OFFSET);
bool unicast = !!(mdns_class & 0x8000);
mdns_class &= 0x7FFF;
content = content + 4;
_mdns_dbg_printf(" Q: ");
if (unicast) {
_mdns_dbg_printf("*U* ");
}
if (type == MDNS_TYPE_PTR) {
_mdns_dbg_printf("%s.%s%s.%s.%s. PTR ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain);
} else if (type == MDNS_TYPE_SRV) {
_mdns_dbg_printf("%s.%s%s.%s.%s. SRV ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain);
} else if (type == MDNS_TYPE_TXT) {
_mdns_dbg_printf("%s.%s%s.%s.%s. TXT ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain);
} else if (type == MDNS_TYPE_A) {
_mdns_dbg_printf("%s.%s. A ", name->host, name->domain);
} else if (type == MDNS_TYPE_AAAA) {
_mdns_dbg_printf("%s.%s. AAAA ", name->host, name->domain);
} else if (type == MDNS_TYPE_NSEC) {
_mdns_dbg_printf("%s.%s%s.%s.%s. NSEC ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain);
} else if (type == MDNS_TYPE_ANY) {
_mdns_dbg_printf("%s.%s%s.%s.%s. ANY ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain);
} else {
_mdns_dbg_printf("%s.%s%s.%s.%s. %04X ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain, type);
}
if (mdns_class == 0x0001) {
_mdns_dbg_printf("IN");
} else {
_mdns_dbg_printf("%04X", mdns_class);
}
_mdns_dbg_printf("\n");
}
}
if (header.answers || header.servers || header.additional) {
uint16_t recordIndex = 0;
while (content < (data + len)) {
content = _mdns_parse_fqdn(data, content, name);
if (!content) {
_mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__);
break;
}
uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET);
uint16_t mdns_class = _mdns_read_u16(content, MDNS_CLASS_OFFSET);
uint32_t ttl = _mdns_read_u32(content, MDNS_TTL_OFFSET);
uint16_t data_len = _mdns_read_u16(content, MDNS_LEN_OFFSET);
const uint8_t * data_ptr = content + MDNS_DATA_OFFSET;
bool flush = !!(mdns_class & 0x8000);
mdns_class &= 0x7FFF;
content = data_ptr + data_len;
if (content > (data + len)) {
_mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__);
break;
}
mdns_parsed_record_type_t record_type = MDNS_ANSWER;
if (recordIndex >= (header.answers + header.servers)) {
record_type = MDNS_EXTRA;
} else if (recordIndex >= (header.answers)) {
record_type = MDNS_NS;
}
recordIndex++;
if (record_type == MDNS_EXTRA) {
_mdns_dbg_printf(" X");
} else if (record_type == MDNS_NS) {
_mdns_dbg_printf(" S");
} else {
_mdns_dbg_printf(" A");
}
if (type == MDNS_TYPE_PTR) {
_mdns_dbg_printf(": %s%s%s.%s.%s. PTR ", name->host, name->host[0]?".":"", name->service, name->proto, name->domain);
} else if (type == MDNS_TYPE_SRV) {
_mdns_dbg_printf(": %s.%s.%s.%s. SRV ", name->host, name->service, name->proto, name->domain);
} else if (type == MDNS_TYPE_TXT) {
_mdns_dbg_printf(": %s.%s.%s.%s. TXT ", name->host, name->service, name->proto, name->domain);
} else if (type == MDNS_TYPE_A) {
_mdns_dbg_printf(": %s.%s. A ", name->host, name->domain);
} else if (type == MDNS_TYPE_AAAA) {
_mdns_dbg_printf(": %s.%s. AAAA ", name->host, name->domain);
} else if (type == MDNS_TYPE_NSEC) {
_mdns_dbg_printf(": %s.%s.%s.%s. NSEC ", name->host, name->service, name->proto, name->domain);
} else if (type == MDNS_TYPE_ANY) {
_mdns_dbg_printf(": %s.%s.%s.%s. ANY ", name->host, name->service, name->proto, name->domain);
} else if (type == MDNS_TYPE_OPT) {
_mdns_dbg_printf(": . OPT ");
} else {
_mdns_dbg_printf(": %s.%s.%s.%s. %04X ", name->host, name->service, name->proto, name->domain, type);
}
if (mdns_class == 0x0001) {
_mdns_dbg_printf("IN ");
} else {
_mdns_dbg_printf("%04X ", mdns_class);
}
if (flush) {
_mdns_dbg_printf("FLUSH ");
}
_mdns_dbg_printf("%u ", ttl);
_mdns_dbg_printf("[%u] ", data_len);
if (type == MDNS_TYPE_PTR) {
if (!_mdns_parse_fqdn(data, data_ptr, name)) {
_mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__);
continue;
}
_mdns_dbg_printf("%s.%s.%s.%s.\n", name->host, name->service, name->proto, name->domain);
} else if (type == MDNS_TYPE_SRV) {
if (!_mdns_parse_fqdn(data, data_ptr + MDNS_SRV_FQDN_OFFSET, name)) {
_mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__);
continue;
}
uint16_t priority = _mdns_read_u16(data_ptr, MDNS_SRV_PRIORITY_OFFSET);
uint16_t weight = _mdns_read_u16(data_ptr, MDNS_SRV_WEIGHT_OFFSET);
uint16_t port = _mdns_read_u16(data_ptr, MDNS_SRV_PORT_OFFSET);
_mdns_dbg_printf("%u %u %u %s.%s.\n", priority, weight, port, name->host, name->domain);
} else if (type == MDNS_TYPE_TXT) {
uint16_t i=0, y;
while (i < data_len) {
uint8_t partLen = data_ptr[i++];
if ((i+partLen) > data_len) {
_mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__);
break;
}
char txt[partLen+1];
for (y=0; y<partLen; y++) {
char d = data_ptr[i++];
txt[y] = d;
}
txt[partLen] = 0;
_mdns_dbg_printf("%s", txt);
if (i<data_len) {
_mdns_dbg_printf("; ");
}
}
_mdns_dbg_printf("\n");
} else if (type == MDNS_TYPE_AAAA) {
esp_ip6_addr_t ip6;
memcpy(&ip6, data_ptr, sizeof(esp_ip6_addr_t));
_mdns_dbg_printf(IPV6STR "\n", IPV62STR(ip6));
} else if (type == MDNS_TYPE_A) {
esp_ip4_addr_t ip;
memcpy(&ip, data_ptr, sizeof(esp_ip4_addr_t));
_mdns_dbg_printf(IPSTR "\n", IP2STR(&ip));
} else if (type == MDNS_TYPE_NSEC) {
const uint8_t * old_ptr = data_ptr;
const uint8_t * new_ptr = _mdns_parse_fqdn(data, data_ptr, name);
if (new_ptr) {
_mdns_dbg_printf("%s.%s.%s.%s. ", name->host, name->service, name->proto, name->domain);
size_t diff = new_ptr - old_ptr;
data_len -= diff;
data_ptr = new_ptr;
}
size_t i;
for (i=0; i<data_len; i++) {
_mdns_dbg_printf(" %02x", data_ptr[i]);
}
_mdns_dbg_printf("\n");
} else if (type == MDNS_TYPE_OPT) {
uint16_t opCode = _mdns_read_u16(data_ptr, 0);
uint16_t opLen = _mdns_read_u16(data_ptr, 2);
_mdns_dbg_printf(" Code: %04x Data[%u]:", opCode, opLen);
size_t i;
for (i=4; i<data_len; i++) {
_mdns_dbg_printf(" %02x", data_ptr[i]);
}
_mdns_dbg_printf("\n");
} else {
size_t i;
for (i=0; i<data_len; i++) {
_mdns_dbg_printf(" %02x", data_ptr[i]);
}
_mdns_dbg_printf("\n");
}
}
}
}
#endif