Files
esp-protocols/components/mdns/mdns_receive.c
David Cermak bed116d98b feat(mdns): Refactor mdns library (stage #1)
The MDNS component has been refactored from a single monolithic file mdns.c
into a set of focused modules with clear responsibilities.
This restructuring maintains the same functionality while improving code organization,
maintainability, and testability.
In the stage#2 we will focus on module based tests
In the stage#3 we will focus on small scale refators and optimizations
2025-11-25 17:30:27 +01:00

1282 lines
51 KiB
C

/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "mdns_private.h"
#include "mdns_networking.h"
#include "mdns.h"
#include "mdns_mem_caps.h"
#include "mdns_utils.h"
#include "mdns_debug.h"
#include "mdns_netif.h"
#include "mdns_send.h"
#include "mdns_browser.h"
#include "mdns_querier.h"
#include "mdns_pcb.h"
#include "mdns_responder.h"
static const char *TAG = "mdns_receive";
/**
* @brief Check if parsed name is discovery
*/
static bool is_discovery(mdns_name_t *name, uint16_t type)
{
return (
(name->host[0] && !strcasecmp(name->host, "_services"))
&& (name->service[0] && !strcasecmp(name->service, "_dns-sd"))
&& (name->proto[0] && !strcasecmp(name->proto, "_udp"))
&& (name->domain[0] && !strcasecmp(name->domain, MDNS_UTILS_DEFAULT_DOMAIN))
&& type == MDNS_TYPE_PTR
);
}
static mdns_srv_item_t *get_service_item_subtype(const char *subtype, const char *service, const char *proto)
{
mdns_srv_item_t *s = mdns_priv_get_services();
while (s) {
if (mdns_utils_service_match(s->service, service, proto, NULL)) {
mdns_subtype_t *subtype_item = s->service->subtype;
while (subtype_item) {
if (!strcasecmp(subtype_item->subtype, subtype)) {
return s;
}
subtype_item = subtype_item->next;
}
}
s = s->next;
}
return NULL;
}
/**
* @brief Check if the parsed name is ours (matches service or host name)
*/
static bool is_ours(mdns_name_t *name)
{
//domain have to be "local"
if (mdns_utils_str_null_or_empty(name->domain) || (strcasecmp(name->domain, MDNS_UTILS_DEFAULT_DOMAIN)
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
&& strcasecmp(name->domain, "arpa")
#endif /* CONFIG_MDNS_RESPOND_REVERSE_QUERIES */
)) {
return false;
}
//if service and proto are empty, host must match out hostname
if (mdns_utils_str_null_or_empty(name->service) && mdns_utils_str_null_or_empty(name->proto)) {
if (!mdns_utils_str_null_or_empty(name->host)
&& !mdns_utils_str_null_or_empty(mdns_priv_get_global_hostname())
&& mdns_utils_hostname_is_ours(name->host)) {
return true;
}
return false;
}
//if service or proto is empty, name is invalid
if (mdns_utils_str_null_or_empty(name->service) || mdns_utils_str_null_or_empty(name->proto)) {
return false;
}
//find the service
mdns_srv_item_t *service;
if (name->sub) {
service = get_service_item_subtype(name->host, name->service, name->proto);
} else if (mdns_utils_str_null_or_empty(name->host)) {
service = mdns_utils_get_service_item(name->service, name->proto, NULL);
} else {
service = mdns_utils_get_service_item_instance(name->host, name->service, name->proto, NULL);
}
if (!service) {
return false;
}
//if query is PTR query and we have service, we have success
if (name->sub || mdns_utils_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_utils_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 Duplicate string or return error
*/
static esp_err_t strdup_check(char **out, char *in)
{
if (in && in[0]) {
*out = mdns_mem_strdup(in);
if (!*out) {
return ESP_FAIL;
}
return ESP_OK;
}
*out = NULL;
return ESP_OK;
}
/*
* @brief Appends/increments a number to name/instance in case of collision
* */
static char *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 = mdns_mem_malloc(strlen(in) + 3);
if (ret == NULL) {
HOOK_MALLOC_FAILED;
return NULL;
}
sprintf(ret, "%s-2", in);
} else {
size_t in_len = strlen(in);
ret = mdns_mem_malloc(in_len + 2); //one extra byte in case 9-10 or 99-100 etc
if (ret == NULL) {
HOOK_MALLOC_FAILED;
return NULL;
}
memcpy(ret, in, in_len);
int baseLen = p - in; //length of 'bla' in 'bla-123'
//overwrite suffix with new suffix
sprintf(ret + baseLen, "-%d", suffix + 1);
}
return ret;
}
/**
* @brief Get number of items in TXT parsed data
*/
static int get_txt_items_count(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 get_txt_item_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 result_txt_create(const uint8_t *data, size_t len, mdns_txt_item_t **out_txt, uint8_t **out_value_len,
size_t *out_count)
{
*out_txt = NULL;
*out_count = 0;
uint16_t i = 0, y;
size_t part_len = 0;
int num_items = get_txt_items_count(data, len);
if (num_items < 0 || num_items > SIZE_MAX / sizeof(mdns_txt_item_t)) {
// Error: num_items is incorrect (or too large to allocate)
return;
}
if (!num_items) {
return;
}
mdns_txt_item_t *txt = (mdns_txt_item_t *)mdns_mem_malloc(sizeof(mdns_txt_item_t) * num_items);
if (!txt) {
HOOK_MALLOC_FAILED;
return;
}
uint8_t *txt_value_len = (uint8_t *)mdns_mem_malloc(num_items);
if (!txt_value_len) {
mdns_mem_free(txt);
HOOK_MALLOC_FAILED;
return;
}
memset(txt, 0, sizeof(mdns_txt_item_t) * num_items);
memset(txt_value_len, 0, num_items);
size_t txt_num = 0;
while (i < len && txt_num < num_items) {
part_len = data[i++];
if (!part_len) {
break;
}
if ((i + part_len) > len) {
goto handle_error;//error
}
int name_len = get_txt_item_len(data + i, part_len);
if (name_len < 0) { //invalid item (no name)
i += part_len;
continue;
}
char *key = (char *) mdns_mem_malloc(name_len + 1);
if (!key) {
HOOK_MALLOC_FAILED;
goto handle_error;//error
}
mdns_txt_item_t *t = &txt[txt_num];
uint8_t *value_len = &txt_value_len[txt_num];
txt_num++;
memcpy(key, data + i, name_len);
key[name_len] = 0;
i += name_len + 1;
t->key = key;
int new_value_len = part_len - name_len - 1;
if (new_value_len > 0) {
char *value = (char *) mdns_mem_malloc(new_value_len + 1);
if (!value) {
HOOK_MALLOC_FAILED;
goto handle_error;//error
}
memcpy(value, data + i, new_value_len);
value[new_value_len] = 0;
*value_len = new_value_len;
i += new_value_len;
t->value = value;
} else {
t->value = NULL;
}
}
*out_txt = txt;
*out_count = txt_num;
*out_value_len = txt_value_len;
return;
handle_error :
for (y = 0; y < txt_num; y++) {
mdns_txt_item_t *t = &txt[y];
mdns_mem_free((char *)t->key);
mdns_mem_free((char *)t->value);
}
mdns_mem_free(txt_value_len);
mdns_mem_free(txt);
}
#ifdef CONFIG_LWIP_IPV4
/**
* @brief Detect IPv4 address collision
*/
static int 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_priv_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_priv_netif_get_other_interface(tcpip_if);
if (other_if == MDNS_MAX_INTERFACES) {
return 1;//AP interface! They win
}
if (esp_netif_get_ip_info(mdns_priv_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_priv_pcb_set_duplicate(tcpip_if);
return 2;//they win
}
return 0;//same
}
#endif /* CONFIG_LWIP_IPV4 */
#ifdef CONFIG_LWIP_IPV6
/**
* @brief Detect IPv6 address collision
*/
static int 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 (mdns_utils_ipv6_address_is_zero(*ip)) {
return 1;//denial! they win
}
if (esp_netif_get_ip6_linklocal(mdns_priv_get_esp_netif(tcpip_if), &if_ip6)) {
return 1;//they win
}
int ret = memcmp((uint8_t *)&if_ip6.addr, (uint8_t *)ip->addr, MDNS_UTILS_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_priv_netif_get_other_interface(tcpip_if);
if (other_if == MDNS_MAX_INTERFACES) {
return 1;//AP interface! They win
}
if (esp_netif_get_ip6_linklocal(mdns_priv_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_UTILS_SIZEOF_IP6_ADDR)) {
return 1;//IPv6 not ours! They win
}
mdns_priv_pcb_set_duplicate(tcpip_if);
return 2;//they win
}
return 0;//same
}
#endif /* CONFIG_LWIP_IPV6 */
/**
* @brief Detect TXT collision
*/
static int check_txt_collision(mdns_service_t *service, const uint8_t *data, size_t len)
{
size_t data_len = 0;
if (len <= 1 && service->txt) { // len==0 means incorrect packet (and handled by the packet parser)
// but handled here again to fix clang-tidy warning on VLA "uint8_t our[0];"
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 += 1 /* record-len */ + strlen(txt->key) + txt->value_len + (txt->value ? 1 : 0 /* "=" */);
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;
txt = service->txt;
while (txt) {
mdns_priv_append_one_txt_record_entry(ours, &index, txt);
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 Detect SRV collision
*/
static int check_srv_collision(mdns_service_t *service, uint16_t priority, uint16_t weight, uint16_t port, const char *host, const char *domain)
{
if (mdns_utils_str_null_or_empty(mdns_priv_get_global_hostname())) {
return 0;
}
size_t our_host_len = strlen(mdns_priv_get_global_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_utils_append_u16(our_data, &our_index, service->priority);
mdns_utils_append_u16(our_data, &our_index, service->weight);
mdns_utils_append_u16(our_data, &our_index, service->port);
our_data[our_index++] = our_host_len;
memcpy(our_data + our_index, mdns_priv_get_global_hostname(), our_host_len);
our_index += our_host_len;
our_data[our_index++] = 5;
memcpy(our_data + our_index, MDNS_UTILS_DEFAULT_DOMAIN, 5);
our_index += 5;
our_data[our_index++] = 0;
uint16_t their_index = 0;
uint8_t their_data[their_len];
mdns_utils_append_u16(their_data, &their_index, priority);
mdns_utils_append_u16(their_data, &their_index, weight);
mdns_utils_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 Check if the parsed name is self-hosted, i.e. we should resolve conflicts
*/
static bool is_name_selfhosted(mdns_name_t *name)
{
if (mdns_utils_str_null_or_empty(mdns_priv_get_global_hostname())) { // self-hostname needs to be defined
return false;
}
// hostname only -- check if selfhosted name
if (mdns_utils_str_null_or_empty(name->service) && mdns_utils_str_null_or_empty(name->proto) &&
strcasecmp(name->host, mdns_priv_get_global_hostname()) == 0) {
return true;
}
// service -- check if selfhosted service
mdns_srv_item_t *srv = mdns_utils_get_service_item(name->service, name->proto, NULL);
if (srv && strcasecmp(mdns_priv_get_global_hostname(), srv->service->hostname) == 0) {
return true;
}
return false;
}
/**
* @brief Called from parser to check if question matches particular service
*/
static bool 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 (question->service && question->proto && question->domain
&& !strcasecmp(service->service->service, question->service)
&& !strcasecmp(service->service->proto, question->proto)
&& !strcasecmp(MDNS_UTILS_DEFAULT_DOMAIN, question->domain)) {
if (!service->service->instance) {
return true;
} else if (service->service->instance && question->host && !strcasecmp(service->service->instance, question->host)) {
return true;
}
}
} else if (service && (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT)) {
const char *name = mdns_utils_get_service_instance_name(service->service);
if (name && question->host && question->service && question->proto && question->domain
&& !strcasecmp(name, question->host)
&& !strcasecmp(service->service->service, question->service)
&& !strcasecmp(service->service->proto, question->proto)
&& !strcasecmp(MDNS_UTILS_DEFAULT_DOMAIN, question->domain)) {
return true;
}
}
return false;
}
/**
* @brief Removes saved question from parsed data
*/
static void 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 (question_matches(q, type, service)) {
parsed_packet->questions = q->next;
mdns_mem_free(q->host);
mdns_mem_free(q->service);
mdns_mem_free(q->proto);
mdns_mem_free(q->domain);
mdns_mem_free(q);
return;
}
while (q->next) {
mdns_parsed_question_t *p = q->next;
if (question_matches(p, type, service)) {
q->next = p->next;
mdns_mem_free(p->host);
mdns_mem_free(p->service);
mdns_mem_free(p->proto);
mdns_mem_free(p->domain);
mdns_mem_free(p);
return;
}
q = q->next;
}
}
/**
* @brief main packet parser
*
* @param packet the packet
*/
static void mdns_parse_packet(mdns_rx_packet_t *packet)
{
static mdns_name_t n;
mdns_header_t header;
const uint8_t *data = mdns_priv_get_packet_data(packet);
size_t len = mdns_priv_get_packet_len(packet);
const uint8_t *content = data + MDNS_HEAD_LEN;
bool do_not_reply = false;
mdns_search_once_t *search_result = NULL;
mdns_browse_t *browse_result = NULL;
char *browse_result_instance = NULL;
char *browse_result_service = NULL;
char *browse_result_proto = NULL;
mdns_browse_sync_t *out_sync_browse = NULL;
DBG_RX_PACKET(packet, data, len);
#ifndef CONFIG_MDNS_SKIP_SUPPRESSING_OWN_QUERIES
// Check if the packet wasn't sent by us
#ifdef CONFIG_LWIP_IPV4
if (packet->ip_protocol == MDNS_IP_PROTOCOL_V4) {
esp_netif_ip_info_t if_ip_info;
if (esp_netif_get_ip_info(mdns_priv_get_esp_netif(packet->tcpip_if), &if_ip_info) == ESP_OK &&
memcmp(&if_ip_info.ip.addr, &packet->src.u_addr.ip4.addr, sizeof(esp_ip4_addr_t)) == 0) {
return;
}
}
#endif /* CONFIG_LWIP_IPV4 */
#ifdef CONFIG_LWIP_IPV6
if (packet->ip_protocol == MDNS_IP_PROTOCOL_V6) {
struct esp_ip6_addr if_ip6;
if (esp_netif_get_ip6_linklocal(mdns_priv_get_esp_netif(packet->tcpip_if), &if_ip6) == ESP_OK &&
memcmp(&if_ip6, &packet->src.u_addr.ip6, sizeof(esp_ip6_addr_t)) == 0) {
return;
}
}
#endif /* CONFIG_LWIP_IPV6 */
#endif // CONFIG_MDNS_SKIP_SUPPRESSING_OWN_QUERIES
// Check for the minimum size of mdns packet
if (len <= MDNS_HEAD_ADDITIONAL_OFFSET) {
return;
}
mdns_parsed_packet_t *parsed_packet = (mdns_parsed_packet_t *)mdns_mem_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_utils_read_u16(data, MDNS_HEAD_ID_OFFSET);
header.flags = mdns_utils_read_u16(data, MDNS_HEAD_FLAGS_OFFSET);
header.questions = mdns_utils_read_u16(data, MDNS_HEAD_QUESTIONS_OFFSET);
header.answers = mdns_utils_read_u16(data, MDNS_HEAD_ANSWERS_OFFSET);
header.servers = mdns_utils_read_u16(data, MDNS_HEAD_SERVERS_OFFSET);
header.additional = mdns_utils_read_u16(data, MDNS_HEAD_ADDITIONAL_OFFSET);
if (header.flags == MDNS_FLAGS_QR_AUTHORITATIVE && packet->src_port != MDNS_SERVICE_PORT) {
mdns_mem_free(parsed_packet);
return;
}
//if we have not set the hostname, we can not answer questions
if (header.questions && !header.answers && mdns_utils_str_null_or_empty(mdns_priv_get_global_hostname())) {
mdns_mem_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 == MDNS_FLAGS_QR_AUTHORITATIVE);
parsed_packet->distributed = header.flags == MDNS_FLAGS_DISTRIBUTED;
parsed_packet->id = header.id;
esp_netif_ip_addr_copy(&parsed_packet->src, &packet->src);
parsed_packet->src_port = packet->src_port;
parsed_packet->records = NULL;
if (header.questions) {
uint8_t qs = header.questions;
while (qs--) {
content = mdns_utils_parse_fqdn(data, content, name, len);
if (!content) {
header.answers = 0;
header.additional = 0;
header.servers = 0;
goto clear_rx_packet; // error
}
if (content + MDNS_CLASS_OFFSET + 1 >= data + len) {
goto clear_rx_packet; // malformed packet, won't read behind it
}
uint16_t type = mdns_utils_read_u16(content, MDNS_TYPE_OFFSET);
uint16_t mdns_class = mdns_utils_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 (is_discovery(name, type)) {
//service discovery
parsed_packet->discovery = true;
mdns_srv_item_t *a = mdns_priv_get_services();
while (a) {
mdns_parsed_question_t *question = (mdns_parsed_question_t *)mdns_mem_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 = mdns_mem_strdup(a->service->service);
question->proto = mdns_mem_strdup(a->service->proto);
question->domain = mdns_mem_strdup(MDNS_UTILS_DEFAULT_DOMAIN);
if (!question->service || !question->proto || !question->domain) {
goto clear_rx_packet;
}
a = a->next;
}
continue;
}
if (!is_ours(name)) {
continue;
}
if (type == MDNS_TYPE_ANY && !mdns_utils_str_null_or_empty(name->host)) {
parsed_packet->probe = true;
}
mdns_parsed_question_t *question = (mdns_parsed_question_t *)mdns_mem_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;
question->sub = name->sub;
if (strdup_check(&(question->host), name->host)
|| strdup_check(&(question->service), name->service)
|| strdup_check(&(question->proto), name->proto)
|| 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_utils_parse_fqdn(data, content, name, len);
if (!content) {
goto clear_rx_packet;
}
if (content + MDNS_LEN_OFFSET + 1 >= data + len) {
goto clear_rx_packet; // malformed packet, won't read behind it
}
uint16_t type = mdns_utils_read_u16(content, MDNS_TYPE_OFFSET);
uint16_t mdns_class = mdns_utils_read_u16(content, MDNS_CLASS_OFFSET);
uint32_t ttl = mdns_utils_read_u32(content, MDNS_TTL_OFFSET);
uint16_t data_len = mdns_utils_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) || data_len == 0) {
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 && is_discovery(name, type)) {
discovery = true;
} else if (!name->sub && is_ours(name)) {
ours = true;
if (name->service[0] && name->proto[0]) {
service = mdns_utils_get_service_item(name->service, name->proto, NULL);
}
} else {
if ((header.flags & MDNS_FLAGS_QUERY_REPSONSE) == 0 || record_type == MDNS_NS) {
//skip this record
continue;
}
search_result = mdns_priv_query_find(name, type, packet->tcpip_if, packet->ip_protocol);
browse_result = mdns_priv_browse_find(name, type, packet->tcpip_if, packet->ip_protocol);
if (browse_result) {
if (!out_sync_browse) {
// will be freed in function `browse_sync`
out_sync_browse = (mdns_browse_sync_t *)mdns_mem_malloc(sizeof(mdns_browse_sync_t));
if (!out_sync_browse) {
HOOK_MALLOC_FAILED;
goto clear_rx_packet;
}
out_sync_browse->browse = browse_result;
out_sync_browse->sync_result = NULL;
}
if (!browse_result_service) {
browse_result_service = (char *)mdns_mem_malloc(MDNS_NAME_BUF_LEN);
if (!browse_result_service) {
HOOK_MALLOC_FAILED;
goto clear_rx_packet;
}
}
memcpy(browse_result_service, browse_result->service, MDNS_NAME_BUF_LEN);
if (!browse_result_proto) {
browse_result_proto = (char *)mdns_mem_malloc(MDNS_NAME_BUF_LEN);
if (!browse_result_proto) {
HOOK_MALLOC_FAILED;
goto clear_rx_packet;
}
}
memcpy(browse_result_proto, browse_result->proto, MDNS_NAME_BUF_LEN);
if (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT) {
if (!browse_result_instance) {
browse_result_instance = (char *)mdns_mem_malloc(MDNS_NAME_BUF_LEN);
if (!browse_result_instance) {
HOOK_MALLOC_FAILED;
goto clear_rx_packet;
}
}
memcpy(browse_result_instance, name->host, MDNS_NAME_BUF_LEN);
}
}
}
if (type == MDNS_TYPE_PTR) {
if (!mdns_utils_parse_fqdn(data, data_ptr, name, len)) {
continue;//error
}
if (search_result) {
mdns_priv_query_result_add_ptr(search_result, name->host, name->service, name->proto,
packet->tcpip_if, packet->ip_protocol, ttl);
} else if ((discovery || ours) && !name->sub && is_ours(name)) {
if (name->host[0]) {
service = mdns_utils_get_service_item_instance(name->host, name->service, name->proto, NULL);
} else {
service = mdns_utils_get_service_item(name->service, name->proto, NULL);
}
if (discovery && service) {
remove_parsed_question(parsed_packet, MDNS_TYPE_SDPTR, service);
} else if (service && parsed_packet->questions && !parsed_packet->probe) {
remove_parsed_question(parsed_packet, type, service);
} else if (service) {
//check if TTL is more than half of the full TTL value (4500)
if (ttl > (MDNS_ANSWER_PTR_TTL / 2)) {
mdns_priv_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service);
}
}
if (service) {
mdns_parsed_record_t *record = mdns_mem_malloc(sizeof(mdns_parsed_record_t));
if (!record) {
HOOK_MALLOC_FAILED;
goto clear_rx_packet;
}
record->next = parsed_packet->records;
parsed_packet->records = record;
record->type = MDNS_TYPE_PTR;
record->record_type = MDNS_ANSWER;
record->ttl = ttl;
record->host = NULL;
record->service = NULL;
record->proto = NULL;
if (name->host[0]) {
record->host = mdns_mem_malloc(MDNS_NAME_BUF_LEN);
if (!record->host) {
HOOK_MALLOC_FAILED;
goto clear_rx_packet;
}
memcpy(record->host, name->host, MDNS_NAME_BUF_LEN);
}
if (name->service[0]) {
record->service = mdns_mem_malloc(MDNS_NAME_BUF_LEN);
if (!record->service) {
HOOK_MALLOC_FAILED;
goto clear_rx_packet;
}
memcpy(record->service, name->service, MDNS_NAME_BUF_LEN);
}
if (name->proto[0]) {
record->proto = mdns_mem_malloc(MDNS_NAME_BUF_LEN);
if (!record->proto) {
HOOK_MALLOC_FAILED;
goto clear_rx_packet;
}
memcpy(record->proto, name->proto, MDNS_NAME_BUF_LEN);
}
}
}
} 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 (mdns_priv_get_esp_netif(packet->tcpip_if) == result->esp_netif
&& packet->ip_protocol == result->ip_protocol
&& result->instance_name && !strcmp(name->host, result->instance_name)) {
break;
}
result = result->next;
}
if (!result) {
result = mdns_priv_query_result_add_ptr(search_result, name->host, name->service, name->proto,
packet->tcpip_if, packet->ip_protocol, ttl);
if (!result) {
continue;
}
}
}
bool is_selfhosted = is_name_selfhosted(name);
if (!mdns_utils_parse_fqdn(data, data_ptr + MDNS_SRV_FQDN_OFFSET, name, len)) {
continue;
}
if (data_ptr + MDNS_SRV_PORT_OFFSET + 1 >= data + len) {
goto clear_rx_packet; // malformed packet, won't read behind it
}
uint16_t priority = mdns_utils_read_u16(data_ptr, MDNS_SRV_PRIORITY_OFFSET);
uint16_t weight = mdns_utils_read_u16(data_ptr, MDNS_SRV_WEIGHT_OFFSET);
uint16_t port = mdns_utils_read_u16(data_ptr, MDNS_SRV_PORT_OFFSET);
if (browse_result) {
mdns_priv_browse_result_add_srv(browse_result, name->host, browse_result_instance,
browse_result_service,
browse_result_proto, port, packet->tcpip_if, packet->ip_protocol,
ttl,
out_sync_browse);
}
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 = mdns_mem_strdup(name->host);
}
} else {
mdns_priv_query_result_add_srv(search_result, name->host, port, packet->tcpip_if,
packet->ip_protocol, ttl);
}
} else if (ours) {
if (parsed_packet->questions && !parsed_packet->probe) {
remove_parsed_question(parsed_packet, type, service);
continue;
} else if (parsed_packet->distributed) {
mdns_priv_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service);
continue;
}
if (!is_selfhosted) {
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 = check_srv_collision(service->service, priority, weight, port, name->host, name->domain);
}
if (service && col && (parsed_packet->probe || parsed_packet->authoritative)) {
if (col > 0 || !port) {
do_not_reply = true;
if (mdns_priv_pcb_is_probing(packet)) {
mdns_priv_pcb_set_probe_failed(packet);
if (!mdns_utils_str_null_or_empty(service->service->instance)) {
char *new_instance = mangle_name((char *) service->service->instance);
if (new_instance) {
mdns_mem_free((char *)service->service->instance);
service->service->instance = new_instance;
}
mdns_priv_probe_all_pcbs(&service, 1, false, false);
} else if (!mdns_utils_str_null_or_empty(mdns_priv_get_instance())) {
char *new_instance = mangle_name((char *) mdns_priv_get_instance());
if (new_instance) {
mdns_priv_set_instance(new_instance);
}
mdns_priv_restart_all_pcbs_no_instance();
} else {
char *new_host = mangle_name((char *) mdns_priv_get_global_hostname());
if (new_host) {
mdns_priv_remap_self_service_hostname(mdns_priv_get_global_hostname(), new_host);
mdns_priv_set_global_hostname(new_host);
}
mdns_priv_restart_all_pcbs();
}
} else if (service) {
mdns_priv_send_bye(packet->tcpip_if, packet->ip_protocol, &service, 1, false);
mdns_priv_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_priv_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service);
}
}
} else if (type == MDNS_TYPE_TXT) {
mdns_txt_item_t *txt = NULL;
uint8_t *txt_value_len = NULL;
size_t txt_count = 0;
mdns_result_t *result = NULL;
if (browse_result) {
result_txt_create(data_ptr, data_len, &txt, &txt_value_len, &txt_count);
mdns_priv_browse_result_add_txt(browse_result, browse_result_instance, browse_result_service,
browse_result_proto,
txt, txt_value_len, txt_count, packet->tcpip_if,
packet->ip_protocol,
ttl, out_sync_browse);
}
if (search_result) {
if (search_result->type == MDNS_TYPE_PTR) {
result = search_result->result;
while (result) {
if (mdns_priv_get_esp_netif(packet->tcpip_if) == result->esp_netif
&& packet->ip_protocol == result->ip_protocol
&& result->instance_name && !strcmp(name->host, result->instance_name)) {
break;
}
result = result->next;
}
if (!result) {
result = mdns_priv_query_result_add_ptr(search_result, name->host, name->service,
name->proto,
packet->tcpip_if, packet->ip_protocol, ttl);
if (!result) {
continue;
}
}
if (!result->txt) {
result_txt_create(data_ptr, data_len, &txt, &txt_value_len, &txt_count);
if (txt_count) {
result->txt = txt;
result->txt_count = txt_count;
result->txt_value_len = txt_value_len;
}
}
} else {
result_txt_create(data_ptr, data_len, &txt, &txt_value_len, &txt_count);
if (txt_count) {
mdns_priv_query_result_add_txt(search_result, txt, txt_value_len, txt_count,
packet->tcpip_if, packet->ip_protocol, ttl);
}
}
} else if (ours) {
if (parsed_packet->questions && !parsed_packet->probe && service) {
remove_parsed_question(parsed_packet, type, service);
continue;
}
if (!is_name_selfhosted(name)) {
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 = check_txt_collision(service->service, data_ptr, data_len);
}
if (col && !mdns_priv_pcb_is_probing(packet) && service) {
do_not_reply = true;
mdns_priv_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, &service, 1, true);
} else if (ttl > (MDNS_ANSWER_TXT_TTL / 2) && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions && !mdns_priv_pcb_is_probing(
packet)) {
mdns_priv_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service);
}
}
}
#ifdef CONFIG_LWIP_IPV6
else if (type == MDNS_TYPE_AAAA) { // ipv6
esp_ip_addr_t ip6;
ip6.type = ESP_IPADDR_TYPE_V6;
memcpy(ip6.u_addr.ip6.addr, data_ptr, MDNS_ANSWER_AAAA_SIZE);
if (browse_result) {
mdns_priv_browse_result_add_ip(browse_result, name->host, &ip6, packet->tcpip_if,
packet->ip_protocol,
ttl, out_sync_browse);
}
if (search_result) {
//check for more applicable searches (PTR & A/AAAA at the same time)
while (search_result) {
mdns_priv_query_result_add_ip(search_result, name->host, &ip6, packet->tcpip_if,
packet->ip_protocol, ttl);
search_result = mdns_priv_query_find_from(search_result->next, name, type, packet->tcpip_if,
packet->ip_protocol);
}
} else if (ours) {
if (parsed_packet->questions && !parsed_packet->probe) {
remove_parsed_question(parsed_packet, type, NULL);
continue;
}
if (!is_name_selfhosted(name)) {
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 = 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_priv_pcb_is_probing(packet)) {
if (col && (parsed_packet->probe || parsed_packet->authoritative)) {
mdns_priv_pcb_set_probe_failed(packet);
char *new_host = mangle_name((char *) mdns_priv_get_global_hostname());
if (new_host) {
mdns_priv_remap_self_service_hostname(mdns_priv_get_global_hostname(), new_host);
mdns_priv_set_global_hostname(new_host);
}
mdns_priv_restart_all_pcbs();
}
} else {
mdns_priv_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_priv_pcb_is_probing(
packet)) {
mdns_priv_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, NULL);
}
}
}
#endif /* CONFIG_LWIP_IPV6 */
#ifdef CONFIG_LWIP_IPV4
else if (type == MDNS_TYPE_A) {
esp_ip_addr_t ip;
ip.type = ESP_IPADDR_TYPE_V4;
memcpy(&(ip.u_addr.ip4.addr), data_ptr, 4);
if (browse_result) {
mdns_priv_browse_result_add_ip(browse_result, name->host, &ip, packet->tcpip_if,
packet->ip_protocol,
ttl, out_sync_browse);
}
if (search_result) {
//check for more applicable searches (PTR & A/AAAA at the same time)
while (search_result) {
mdns_priv_query_result_add_ip(search_result, name->host, &ip, packet->tcpip_if,
packet->ip_protocol, ttl);
search_result = mdns_priv_query_find_from(search_result->next, name, type, packet->tcpip_if,
packet->ip_protocol);
}
} else if (ours) {
if (parsed_packet->questions && !parsed_packet->probe) {
remove_parsed_question(parsed_packet, type, NULL);
continue;
}
if (!is_name_selfhosted(name)) {
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 = 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_priv_pcb_is_probing(packet)) {
if (col && (parsed_packet->probe || parsed_packet->authoritative)) {
mdns_priv_pcb_set_probe_failed(packet);
char *new_host = mangle_name((char *) mdns_priv_get_global_hostname());
if (new_host) {
mdns_priv_remap_self_service_hostname(mdns_priv_get_global_hostname(), new_host);
mdns_priv_set_global_hostname(new_host);
}
mdns_priv_restart_all_pcbs();
}
} else {
mdns_priv_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_priv_pcb_is_probing(
packet)) {
mdns_priv_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, NULL);
}
}
}
#endif /* CONFIG_LWIP_IPV4 */
}
//end while
if (parsed_packet->authoritative) {
mdns_priv_query_done();
}
}
if (!do_not_reply && mdns_priv_pcb_is_after_probing(packet) && (parsed_packet->questions || parsed_packet->discovery)) {
mdns_priv_create_answer_from_parsed_packet(parsed_packet);
}
if (out_sync_browse) {
DBG_BROWSE_RESULTS_WITH_MSG(out_sync_browse->browse->result,
"Browse %s%s total result:", out_sync_browse->browse->service, out_sync_browse->browse->proto);
if (out_sync_browse->sync_result) {
DBG_BROWSE_RESULTS_WITH_MSG(out_sync_browse->sync_result->result,
"Changed result:");
mdns_priv_browse_sync(out_sync_browse);
} else {
mdns_mem_free(out_sync_browse);
}
out_sync_browse = NULL;
}
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) {
mdns_mem_free(question->host);
}
if (question->service) {
mdns_mem_free(question->service);
}
if (question->proto) {
mdns_mem_free(question->proto);
}
if (question->domain) {
mdns_mem_free(question->domain);
}
mdns_mem_free(question);
}
while (parsed_packet->records) {
mdns_parsed_record_t *record = parsed_packet->records;
parsed_packet->records = parsed_packet->records->next;
if (record->host) {
mdns_mem_free(record->host);
}
if (record->service) {
mdns_mem_free(record->service);
}
if (record->proto) {
mdns_mem_free(record->proto);
}
record->next = NULL;
mdns_mem_free(record);
}
mdns_mem_free(parsed_packet);
mdns_mem_free(browse_result_instance);
mdns_mem_free(browse_result_service);
mdns_mem_free(browse_result_proto);
mdns_mem_free(out_sync_browse);
}
void mdns_priv_receive_action(mdns_action_t *action, mdns_action_subtype_t type)
{
if (action->type != ACTION_RX_HANDLE) {
abort();
}
if (type == ACTION_RUN) {
mdns_parse_packet(action->data.rx_handle.packet);
mdns_priv_packet_free(action->data.rx_handle.packet);
} else if (type == ACTION_CLEANUP) {
mdns_priv_packet_free(action->data.rx_handle.packet);
}
}