Adicionar primeiro

This commit is contained in:
2025-06-06 21:17:25 +01:00
parent c188084ba4
commit 282e7f517b
841 changed files with 199592 additions and 1 deletions

View File

@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
if(${IDF_TARGET} STREQUAL "linux")
set(EXTRA_COMPONENT_DIRS "../../../../common_components/linux_compat")
set(COMPONENTS main)
endif()
project(mdns_host)
# Enable sanitizers only without console (we'd see some leaks on argtable when console exits)
if(NOT CONFIG_TEST_CONSOLE AND CONFIG_IDF_TARGET_LINUX)
idf_component_get_property(mdns mdns COMPONENT_LIB)
target_link_options(${mdns} INTERFACE -fsanitize=address -fsanitize=undefined)
endif()

View File

@@ -0,0 +1,33 @@
# Setup dummy network interfaces
Note: Set two addresses so we could use one as source and another as destination
```
sudo ip link add eth2 type dummy
sudo ip addr add 192.168.1.200/24 dev eth2
sudo ip addr add 192.168.1.201/24 dev eth2
sudo ip link set eth2 up
sudo ifconfig eth2 multicast
```
# Dig on a specified interface
```
dig +short -b 192.168.1.200 -p 5353 @224.0.0.251 myesp.local
```
or a reverse query:
```
dig +short -b 192.168.2.200 -p 5353 @224.0.0.251 -x 192.168.1.200
```
# Run avahi to browse services
Avahi needs the netif to have the "multicast" flag set
```bash
david@david-comp:~/esp/idf (feature/mdns_networking_socket)$ avahi-browse -a -r -p
+;eth2;IPv6;myesp-service2;Web Site;local
+;eth2;IPv4;myesp-service2;Web Site;local
=;eth2;IPv6;myesp-service2;Web Site;local;myesp.local;192.168.1.200;80;"board=esp32" "u=user" "p=password"
=;eth2;IPv4;myesp-service2;Web Site;local;myesp.local;192.168.1.200;80;"board=esp32" "u=user" "p=password"
```

View File

@@ -0,0 +1,8 @@
idf_build_get_property(idf_target IDF_TARGET)
if(${IDF_TARGET} STREQUAL "linux")
idf_component_register(SRCS esp_netif_linux.c
INCLUDE_DIRS include $ENV{IDF_PATH}/components/esp_netif/include
REQUIRES esp_event)
else()
idf_component_register()
endif()

View File

@@ -0,0 +1,15 @@
menu "LWIP-MOCK-CONFIG"
config LWIP_IPV6
bool "Enable IPv6"
default y
help
Enable/disable IPv6
config LWIP_IPV4
bool "Enable IPv4"
default y
help
Enable/disable IPv4
endmenu

View File

@@ -0,0 +1,197 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include<stdio.h>
#include <stdlib.h>
#include "esp_netif.h"
#include "esp_err.h"
#include <string.h> //strlen
#include <sys/socket.h>
#include <arpa/inet.h> //inet_addr
#include <sys/types.h>
#include <ifaddrs.h>
#include <net/if.h>
#include "esp_netif_types.h"
#include "esp_log.h"
#define MAX_NETIFS 4
static const char *TAG = "esp_netif_linux";
static esp_netif_t *s_netif_list[MAX_NETIFS] = { 0 };
struct esp_netif_obj {
const char *if_key;
const char *if_desc;
};
esp_netif_t *esp_netif_get_handle_from_ifkey(const char *if_key)
{
for (int i = 0; i < MAX_NETIFS; ++i) {
if (s_netif_list[i] && strcmp(s_netif_list[i]->if_key, if_key) == 0) {
return s_netif_list[i];
}
}
return NULL;
}
esp_err_t esp_netif_get_ip_info(esp_netif_t *esp_netif, esp_netif_ip_info_t *ip_info)
{
if (esp_netif == NULL) {
return ESP_ERR_INVALID_STATE;
}
struct ifaddrs *addrs, *tmp;
getifaddrs(&addrs);
tmp = addrs;
while (tmp) {
if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) {
char addr[20];
struct sockaddr_in *pAddr = (struct sockaddr_in *) tmp->ifa_addr;
inet_ntop(AF_INET, &pAddr->sin_addr, addr, sizeof(addr));
if (strcmp(esp_netif->if_desc, tmp->ifa_name) == 0) {
ESP_LOGD(TAG, "AF_INET4: %s: %s\n", tmp->ifa_name, addr);
memcpy(&ip_info->ip.addr, &pAddr->sin_addr, 4);
}
}
tmp = tmp->ifa_next;
}
freeifaddrs(addrs);
return ESP_OK;
}
esp_err_t esp_netif_dhcpc_get_status(esp_netif_t *esp_netif, esp_netif_dhcp_status_t *status)
{
return ESP_OK;
}
int esp_netif_get_all_ip6(esp_netif_t *esp_netif, esp_ip6_addr_t if_ip6[])
{
if (esp_netif == NULL) {
return 0;
}
struct ifaddrs *addrs, *tmp;
int addr_count = 0;
getifaddrs(&addrs);
tmp = addrs;
while (tmp) {
if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET6) {
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)tmp->ifa_addr;
if (strcmp(esp_netif->if_desc, tmp->ifa_name) == 0) {
memcpy(&if_ip6[addr_count++], &pAddr->sin6_addr, 4 * 4);
}
}
tmp = tmp->ifa_next;
}
freeifaddrs(addrs);
return addr_count;
}
esp_err_t esp_netif_get_ip6_linklocal(esp_netif_t *esp_netif, esp_ip6_addr_t *if_ip6)
{
if (esp_netif == NULL) {
return ESP_ERR_INVALID_STATE;
}
struct ifaddrs *addrs, *tmp;
getifaddrs(&addrs);
tmp = addrs;
while (tmp) {
if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET6) {
char addr[64];
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)tmp->ifa_addr;
inet_ntop(AF_INET6, &pAddr->sin6_addr, addr, sizeof(addr));
if (strcmp(esp_netif->if_desc, tmp->ifa_name) == 0) {
ESP_LOGD(TAG, "AF_INET6: %s: %s\n", tmp->ifa_name, addr);
memcpy(if_ip6->addr, &pAddr->sin6_addr, 4 * 4);
break;
}
}
tmp = tmp->ifa_next;
}
freeifaddrs(addrs);
return ESP_OK;
}
int esp_netif_get_netif_impl_index(esp_netif_t *esp_netif)
{
if (esp_netif == NULL) {
return -1;
}
uint32_t interfaceIndex = if_nametoindex(esp_netif->if_desc);
return interfaceIndex;
}
esp_err_t esp_netif_get_netif_impl_name(esp_netif_t *esp_netif, char *name)
{
if (esp_netif == NULL) {
return ESP_ERR_INVALID_STATE;
}
strcpy(name, esp_netif->if_desc);
return ESP_OK;
}
const char *esp_netif_get_desc(esp_netif_t *esp_netif)
{
if (esp_netif == NULL) {
return NULL;
}
return esp_netif->if_desc;
}
esp_netif_t *esp_netif_new(const esp_netif_config_t *config)
{
if (esp_netif_get_handle_from_ifkey(config->base->if_key)) {
return NULL;
}
esp_netif_t *netif = calloc(1, sizeof(struct esp_netif_obj));
if (netif) {
netif->if_desc = config->base->if_desc;
netif->if_key = config->base->if_key;
}
for (int i = 0; i < MAX_NETIFS; ++i) {
if (s_netif_list[i] == NULL) {
s_netif_list[i] = netif;
break;
}
}
return netif;
}
void esp_netif_destroy(esp_netif_t *esp_netif)
{
for (int i = 0; i < MAX_NETIFS; ++i) {
if (s_netif_list[i] == esp_netif) {
s_netif_list[i] = NULL;
break;
}
}
free(esp_netif);
}
const char *esp_netif_get_ifkey(esp_netif_t *esp_netif)
{
return esp_netif->if_key;
}
esp_err_t esp_netif_str_to_ip4(const char *src, esp_ip4_addr_t *dst)
{
if (src == NULL || dst == NULL) {
return ESP_ERR_INVALID_ARG;
}
struct in_addr addr;
if (inet_pton(AF_INET, src, &addr) != 1) {
return ESP_FAIL;
}
dst->addr = addr.s_addr;
return ESP_OK;
}

View File

@@ -0,0 +1,7 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_event.h"

View File

@@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include_next "endian.h"

View File

@@ -0,0 +1,130 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
import re
import socket
import sys
import dns.message
import dns.query
import dns.rdataclass
import dns.rdatatype
import dns.resolver
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class DnsPythonWrapper:
def __init__(self, server='224.0.0.251', port=5353, retries=3):
self.server = server
self.port = port
self.retries = retries
def send_and_receive_query(self, query, timeout=3):
logger.info(f'Sending DNS query to {self.server}:{self.port}')
try:
# Create a UDP socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.settimeout(timeout)
# Send the DNS query
query_data = query.to_wire()
sock.sendto(query_data, (self.server, self.port))
# Receive the DNS response
response_data, _ = sock.recvfrom(512) # 512 bytes is the typical size for a DNS response
# Parse the response
response = dns.message.from_wire(response_data)
return response
except socket.timeout as e:
logger.warning(f'DNS query timed out: {e}')
return None
except dns.exception.DNSException as e:
logger.error(f'DNS query failed: {e}')
return None
def run_query(self, name, query_type='PTR', timeout=3):
logger.info(f'Running DNS query for {name} with type {query_type}')
query = dns.message.make_query(name, dns.rdatatype.from_text(query_type), dns.rdataclass.IN)
# Print the DNS question section
logger.info(f'DNS question section: {query.question}')
# Send and receive the DNS query
response = None
for attempt in range(1, self.retries + 1):
logger.info(f'Attempt {attempt}/{self.retries}')
response = self.send_and_receive_query(query, timeout)
if response:
break
if response:
logger.info(f'DNS query response:\n{response}')
else:
logger.warning('No response received or response was invalid.')
return response
def parse_answer_section(self, response, query_type):
answers = []
if response:
for answer in response.answer:
if dns.rdatatype.to_text(answer.rdtype) == query_type:
for item in answer.items:
full_answer = (
f'{answer.name} {answer.ttl} '
f'{dns.rdataclass.to_text(answer.rdclass)} '
f'{dns.rdatatype.to_text(answer.rdtype)} '
f'{item.to_text()}'
)
answers.append(full_answer)
return answers
def check_record(self, name, query_type, expected=True, expect=None):
output = self.run_query(name, query_type=query_type)
answers = self.parse_answer_section(output, query_type)
logger.info(f'answers: {answers}')
if expect is None:
expect = name
if expected:
assert any(expect in answer for answer in answers), f"Expected record '{expect}' not found in answer section"
else:
assert not any(expect in answer for answer in answers), f"Unexpected record '{expect}' found in answer section"
if __name__ == '__main__':
if len(sys.argv) < 3:
print('Usage: python dns_fixture.py <query_type> <name>')
sys.exit(1)
query_type = sys.argv[1]
name = sys.argv[2]
ip_only = len(sys.argv) > 3 and sys.argv[3] == '--ip_only'
if ip_only:
logger.setLevel(logging.WARNING)
dns_wrapper = DnsPythonWrapper()
if query_type == 'X' and '.' in name:
# Sends an IPv4 reverse query
reversed_ip = '.'.join(reversed(name.split('.')))
name = f'{reversed_ip}.in-addr.arpa'
query_type = 'PTR'
response = dns_wrapper.run_query(name, query_type=query_type)
answers = dns_wrapper.parse_answer_section(response, query_type)
if answers:
for answer in answers:
logger.info(f'DNS query response: {answer}')
if ip_only:
ipv4_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')
ipv4_addresses = ipv4_pattern.findall(answer)
if ipv4_addresses:
print(f"{', '.join(ipv4_addresses)}")
else:
logger.info(f'No response for {name} with query type {query_type}')
exit(9) # Same as dig timeout

View File

@@ -0,0 +1,4 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS
"."
REQUIRES mdns console nvs_flash)

View File

@@ -0,0 +1,21 @@
menu "Test Configuration"
config TEST_HOSTNAME
string "mDNS Hostname"
default "esp32-mdns"
help
mDNS Hostname for example to use
config TEST_NETIF_NAME
string "Network interface name"
default "eth2"
help
Name/ID if the network interface on which we run the mDNS host test
config TEST_CONSOLE
bool "Start console"
default n
help
Test uses esp_console for interactive testing.
endmenu

View File

@@ -0,0 +1,7 @@
dependencies:
idf: ">=5.0"
espressif/mdns:
version: "^1.0.0"
override_path: "../../.."
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common

View File

@@ -0,0 +1,126 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_console.h"
#include "mdns.h"
#include "mdns_console.h"
static const char *TAG = "mdns-test";
static void mdns_test_app(esp_netif_t *interface);
#ifdef CONFIG_TEST_CONSOLE
static EventGroupHandle_t s_exit_signal = NULL;
static int exit_console(int argc, char **argv)
{
xEventGroupSetBits(s_exit_signal, 1);
return 0;
}
#else
static void query_mdns_host(const char *host_name)
{
ESP_LOGI(TAG, "Query A: %s.local", host_name);
struct esp_ip4_addr addr;
addr.addr = 0;
esp_err_t err = mdns_query_a(host_name, 2000, &addr);
if (err) {
if (err == ESP_ERR_NOT_FOUND) {
ESP_LOGW(TAG, "%x: Host was not found!", (err));
return;
}
ESP_LOGE(TAG, "Query Failed: %x", (err));
return;
}
ESP_LOGI(TAG, "Query A: %s.local resolved to: " IPSTR, host_name, IP2STR(&addr));
}
#endif // TEST_CONSOLE
#ifndef CONFIG_IDF_TARGET_LINUX
#include "protocol_examples_common.h"
#include "esp_event.h"
#include "nvs_flash.h"
/**
* @brief This is an entry point for the real target device,
* need to init few components and connect to a network interface
*/
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_connect());
mdns_test_app(EXAMPLE_INTERFACE);
ESP_ERROR_CHECK(example_disconnect());
}
#else
/**
* @brief This is an entry point for the linux target (simulator on host)
* need to create a dummy WiFi station and use it as mdns network interface
*/
int main(int argc, char *argv[])
{
setvbuf(stdout, NULL, _IONBF, 0);
const esp_netif_inherent_config_t base_cg = { .if_key = "WIFI_STA_DEF", .if_desc = CONFIG_TEST_NETIF_NAME };
esp_netif_config_t cfg = { .base = &base_cg };
esp_netif_t *sta = esp_netif_new(&cfg);
mdns_test_app(sta);
esp_netif_destroy(sta);
return 0;
}
#endif
static void mdns_test_app(esp_netif_t *interface)
{
ESP_ERROR_CHECK(mdns_init());
ESP_ERROR_CHECK(mdns_hostname_set(CONFIG_TEST_HOSTNAME));
ESP_LOGI(TAG, "mdns hostname set to: [%s]", CONFIG_TEST_HOSTNAME);
ESP_ERROR_CHECK(mdns_register_netif(interface));
ESP_ERROR_CHECK(mdns_netif_action(interface, MDNS_EVENT_ENABLE_IP4 /*| MDNS_EVENT_ENABLE_IP6 */ | MDNS_EVENT_IP4_REVERSE_LOOKUP | MDNS_EVENT_IP6_REVERSE_LOOKUP));
#ifdef CONFIG_TEST_CONSOLE
esp_console_repl_t *repl = NULL;
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
s_exit_signal = xEventGroupCreate();
repl_config.prompt = "mdns>";
// init console REPL environment
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
const esp_console_cmd_t cmd_exit = {
.command = "exit",
.help = "exit mDNS console application",
.hint = NULL,
.func = exit_console,
.argtable = NULL
};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd_exit));
mdns_console_register();
ESP_ERROR_CHECK(esp_console_start_repl(repl));
xEventGroupWaitBits(s_exit_signal, 1, pdTRUE, pdFALSE, portMAX_DELAY);
repl->del(repl);
#else
vTaskDelay(pdMS_TO_TICKS(10000));
query_mdns_host("david-work");
vTaskDelay(pdMS_TO_TICKS(1000));
#endif
mdns_free();
ESP_LOGI(TAG, "Exit");
}

View File

@@ -0,0 +1,180 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
import pexpect
import pytest
from dnsfixture import DnsPythonWrapper
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
ipv6_enabled = False
class MdnsConsole:
def __init__(self, command):
self.process = pexpect.spawn(command, encoding='utf-8')
self.process.logfile = open('mdns_interaction.log', 'w') # Log all interactions
self.process.expect('mdns> ', timeout=10)
def send_input(self, input_data):
logger.info(f'Sending to stdin: {input_data}')
self.process.sendline(input_data)
def get_output(self, expected_data):
logger.info(f'Expecting: {expected_data}')
self.process.expect(expected_data, timeout=10)
output = self.process.before.strip()
logger.info(f'Received from stdout: {output}')
return output
def terminate(self):
self.send_input('exit')
self.get_output('Exit')
self.process.wait()
self.process.close()
assert self.process.exitstatus == 0
@pytest.fixture(scope='module')
def mdns_console():
app = MdnsConsole('./build_linux_console/mdns_host.elf')
yield app
app.terminate()
@pytest.fixture(scope='module')
def dig_app():
return DnsPythonWrapper()
def test_mdns_init(mdns_console, dig_app):
mdns_console.send_input('mdns_init -h hostname')
mdns_console.get_output('MDNS: Hostname: hostname')
dig_app.check_record('hostname.local', query_type='A', expected=True)
if ipv6_enabled:
dig_app.check_record('hostname.local', query_type='AAAA', expected=True)
def test_add_service(mdns_console, dig_app):
mdns_console.send_input('mdns_service_add _http _tcp 80 -i test_service')
mdns_console.get_output('MDNS: Service Instance: test_service')
mdns_console.send_input('mdns_service_lookup _http _tcp')
mdns_console.get_output('PTR : test_service')
dig_app.check_record('_http._tcp.local', query_type='PTR', expected=True)
def test_remove_service(mdns_console, dig_app):
mdns_console.send_input('mdns_service_remove _http _tcp')
mdns_console.send_input('mdns_service_lookup _http _tcp')
mdns_console.get_output('No results found!')
dig_app.check_record('_http._tcp.local', query_type='PTR', expected=False)
def test_delegate_host(mdns_console, dig_app):
mdns_console.send_input('mdns_delegate_host delegated 1.2.3.4')
dig_app.check_record('delegated.local', query_type='A', expected=True)
def test_undelegate_host(mdns_console, dig_app):
mdns_console.send_input('mdns_undelegate_host delegated')
dig_app.check_record('delegated.local', query_type='A', expected=False)
def test_add_delegated_service(mdns_console, dig_app):
mdns_console.send_input('mdns_delegate_host delegated 1.2.3.4')
dig_app.check_record('delegated.local', query_type='A', expected=True)
mdns_console.send_input('mdns_service_add _test _tcp 80 -i local')
mdns_console.get_output('MDNS: Service Instance: local')
mdns_console.send_input('mdns_service_add _test2 _tcp 80 -i extern -h delegated')
mdns_console.get_output('MDNS: Service Instance: extern')
mdns_console.send_input('mdns_service_lookup _test _tcp')
mdns_console.get_output('PTR : local')
mdns_console.send_input('mdns_service_lookup _test2 _tcp -d')
mdns_console.get_output('PTR : extern')
dig_app.check_record('_test2._tcp.local', query_type='PTR', expected=True)
dig_app.check_record('extern._test2._tcp.local', query_type='SRV', expected=True)
def test_remove_delegated_service(mdns_console, dig_app):
mdns_console.send_input('mdns_service_remove _test2 _tcp -h delegated')
mdns_console.send_input('mdns_service_lookup _test2 _tcp -d')
mdns_console.get_output('No results found!')
dig_app.check_record('_test2._tcp.local', query_type='PTR', expected=False)
# add the delegated service again, would be used in the TXT test
mdns_console.send_input('mdns_service_add _test2 _tcp 80 -i extern -h delegated')
mdns_console.get_output('MDNS: Service Instance: extern')
def check_txt_for_service(instance, service, proto, mdns_console, dig_app, host=None, with_inst=False):
for_host_arg = f'-h {host}' if host is not None else ''
for_inst_arg = f'-i {instance}' if with_inst else ''
mdns_console.send_input(f'mdns_service_txt_set {service} {proto} {for_host_arg} {for_inst_arg} key1 value1')
dig_app.check_record(f'{instance}.{service}.{proto}.local', query_type='SRV', expected=True)
dig_app.check_record(f'{service}.{proto}.local', query_type='TXT', expected=True, expect='key1=value1')
mdns_console.send_input(f'mdns_service_txt_set {service} {proto} {for_host_arg} {for_inst_arg} key2 value2')
dig_app.check_record(f'{service}.{proto}.local', query_type='TXT', expected=True, expect='key2=value2')
mdns_console.send_input(f'mdns_service_txt_remove {service} {proto} {for_host_arg} {for_inst_arg} key2')
dig_app.check_record(f'{service}.{proto}.local', query_type='TXT', expected=False, expect='key2=value2')
dig_app.check_record(f'{service}.{proto}.local', query_type='TXT', expected=True, expect='key1=value1')
mdns_console.send_input(f'mdns_service_txt_replace {service} {proto} {for_host_arg} {for_inst_arg} key3=value3 key4=value4')
dig_app.check_record(f'{service}.{proto}.local', query_type='TXT', expected=False, expect='key1=value1')
dig_app.check_record(f'{service}.{proto}.local', query_type='TXT', expected=True, expect='key3=value3')
dig_app.check_record(f'{service}.{proto}.local', query_type='TXT', expected=True, expect='key4=value4')
def test_update_txt(mdns_console, dig_app):
check_txt_for_service('local', '_test', '_tcp', mdns_console=mdns_console, dig_app=dig_app)
check_txt_for_service('local', '_test', '_tcp', mdns_console=mdns_console, dig_app=dig_app, with_inst=True)
def test_update_delegated_txt(mdns_console, dig_app):
check_txt_for_service('extern', '_test2', '_tcp', mdns_console=mdns_console, dig_app=dig_app, host='delegated')
check_txt_for_service('extern', '_test2', '_tcp', mdns_console=mdns_console, dig_app=dig_app, host='delegated', with_inst=True)
def test_service_port_set(mdns_console, dig_app):
dig_app.check_record('local._test._tcp.local', query_type='SRV', expected=True, expect='80')
mdns_console.send_input('mdns_service_port_set _test _tcp 81')
dig_app.check_record('local._test._tcp.local', query_type='SRV', expected=True, expect='81')
mdns_console.send_input('mdns_service_port_set _test2 _tcp -h delegated 82')
dig_app.check_record('extern._test2._tcp.local', query_type='SRV', expected=True, expect='82')
mdns_console.send_input('mdns_service_port_set _test2 _tcp -h delegated -i extern 83')
dig_app.check_record('extern._test2._tcp.local', query_type='SRV', expected=True, expect='83')
mdns_console.send_input('mdns_service_port_set _test2 _tcp -h delegated -i invalid_inst 84')
mdns_console.get_output('ESP_ERR_NOT_FOUND')
dig_app.check_record('extern._test2._tcp.local', query_type='SRV', expected=True, expect='83')
def test_service_subtype(mdns_console, dig_app):
dig_app.check_record('local._test._tcp.local', query_type='SRV', expected=True)
mdns_console.send_input('mdns_service_subtype _test _tcp _subtest -i local')
dig_app.check_record('_subtest._sub._test._tcp.local', query_type='PTR', expected=True)
mdns_console.send_input('mdns_service_subtype _test2 _tcp _subtest2 -i extern -h delegated')
dig_app.check_record('_subtest2._sub._test2._tcp.local', query_type='PTR', expected=True)
def test_service_set_instance(mdns_console, dig_app):
dig_app.check_record('local._test._tcp.local', query_type='SRV', expected=True)
mdns_console.send_input('mdns_service_instance_set _test _tcp local2')
dig_app.check_record('local2._test._tcp.local', query_type='SRV', expected=True)
mdns_console.send_input('mdns_service_instance_set _test2 _tcp extern2 -h delegated')
mdns_console.send_input('mdns_service_lookup _test2 _tcp -d')
mdns_console.get_output('PTR : extern2')
dig_app.check_record('extern2._test2._tcp.local', query_type='SRV', expected=True)
mdns_console.send_input('mdns_service_instance_set _test2 _tcp extern3 -h delegated -i extern')
mdns_console.get_output('ESP_ERR_NOT_FOUND')
def test_service_remove_all(mdns_console, dig_app):
mdns_console.send_input('mdns_service_remove_all')
mdns_console.send_input('mdns_service_lookup _test2 _tcp -d')
mdns_console.get_output('No results found!')
mdns_console.send_input('mdns_service_lookup _test _tcp')
mdns_console.get_output('No results found!')
dig_app.check_record('_test._tcp.local', query_type='PTR', expected=False)
if __name__ == '__main__':
pytest.main(['-s', 'test_mdns.py'])

View File

@@ -0,0 +1,7 @@
# The following four lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(fuzz_test_update)

View File

@@ -0,0 +1,80 @@
## Introduction
This test uses [american fuzzy lop](http://lcamtuf.coredump.cx/afl/) to mangle real mdns packets and look for exceptions caused by the parser.
A few actual packets are collected and exported as bins in the `in` folder, which is then passed as input to AFL when testing. The setup procedure for the test includes all possible services and scenarios that could be used with the given input packets.The output of the parser before fuzzing can be found in [input_packets.txt](input_packets.txt)
## Building and running the tests using AFL
To build and run the tests using AFL(afl-clang-fast) instrumentation
```bash
cd $IDF_PATH/components/mdns/test_afl_host
make fuzz
```
(Please note you have to install AFL instrumentation first, check `Installing AFL` section)
## Building the tests using GCC INSTR(off)
To build the tests without AFL instrumentations and instead of that use GCC compiler(In this case it will only check for compilation issues and will not run AFL tests).
```bash
cd $IDF_PATH/components/mdns/test_afl_host
make INSTR=off
```
Note, that this setup is useful if we want to reproduce issues reported by fuzzer tests executed in the CI, or to simulate how the packet parser treats the input packets on the host machine.
## Installing AFL
To run the test yourself, you need to download the [latest afl archive](http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz) and extract it to a folder on your computer.
The rest of the document will refer to that folder as ```PATH_TO_AFL```.
### Preparation
- On Mac, you will need to install the latest Xcode and llvm support from [Homebrew](https://brew.sh)
```bash
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install --with-clang --with-lld --HEAD llvm
export PATH="/usr/local/opt/llvm/bin:$PATH"
```
- On Ubuntu you need the following packages:
```bash
sudo apt-get install make clang-4.0(or <=4.0) llvm-4.0(or <=4.0) libbsd-dev
```
Please note that if specified package version can't be installed(due to system is the latest), you can download, build and install it manually.
### Compile AFL
Compiling AFL is as easy as running make:
```bash
cd [PATH_TO_AFL]
make
cd llvm_mode/
make
```
After successful compilation, you can export the following variables to your shell (you can also add them to your profile if you want to use AFL in other projects).
```bash
export AFL_PATH=[PATH_TO_AFL]
export PATH="$AFL_PATH:$PATH"
```
Please note LLVM must be <=4.0.0, otherwise afl does not compile, as there are some limitations with building AFL on MacOS/Linux with the latest LLVM. Also, Windows build on cygwin is not fully supported.
## Additional info
Apple has a crash reporting service that could interfere with AFLs normal operation. To turn that off, run the following command:
```bash
launchctl unload -w /System/Library/LaunchAgents/com.apple.ReportCrash.plist
sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.ReportCrash.Root.plist
```
Ubuntu has a similar service. To turn that off, run as root:
```bash
echo core >/proc/sys/kernel/core_pattern
```

View File

@@ -0,0 +1,162 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include "esp32_mock.h"
#include "esp_log.h"
void *g_queue;
int g_queue_send_shall_fail = 0;
int g_size = 0;
const char *WIFI_EVENT = "wifi_event";
const char *ETH_EVENT = "eth_event";
esp_err_t esp_event_handler_register(const char *event_base,
int32_t event_id,
void *event_handler,
void *event_handler_arg)
{
return ESP_OK;
}
esp_err_t esp_event_handler_unregister(const char *event_base, int32_t event_id, void *event_handler)
{
return ESP_OK;
}
esp_err_t esp_timer_delete(esp_timer_handle_t timer)
{
return ESP_OK;
}
esp_err_t esp_timer_stop(esp_timer_handle_t timer)
{
return ESP_OK;
}
esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period)
{
return ESP_OK;
}
esp_err_t esp_timer_create(const esp_timer_create_args_t *create_args,
esp_timer_handle_t *out_handle)
{
return ESP_OK;
}
uint32_t xTaskGetTickCount(void)
{
static uint32_t tick = 0;
return tick++;
}
/// Queue mock
QueueHandle_t xQueueCreate(uint32_t uxQueueLength, uint32_t uxItemSize)
{
g_size = uxItemSize;
g_queue = malloc((uxQueueLength) * (uxItemSize));
return g_queue;
}
void vQueueDelete(QueueHandle_t xQueue)
{
free(xQueue);
}
uint32_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)
{
if (g_queue_send_shall_fail) {
return pdFALSE;
} else {
memcpy(xQueue, pvItemToQueue, g_size);
return pdPASS;
}
}
uint32_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)
{
return pdFALSE;
}
void GetLastItem(void *pvBuffer)
{
memcpy(pvBuffer, g_queue, g_size);
}
void ForceTaskDelete(void)
{
g_queue_send_shall_fail = 1;
}
TaskHandle_t xTaskGetCurrentTaskHandle(void)
{
return NULL;
}
void xTaskNotifyGive(TaskHandle_t task)
{
return;
}
BaseType_t xTaskNotifyWait(uint32_t bits_entry_clear, uint32_t bits_exit_clear, uint32_t *value, TickType_t wait_time)
{
return pdTRUE;
}
void esp_log_write(esp_log_level_t level, const char *tag, const char *format, ...)
{
}
void esp_log(esp_log_config_t config, const char *tag, const char *format, ...)
{
}
uint32_t esp_log_timestamp(void)
{
return 0;
}
void *mdns_mem_malloc(size_t size)
{
return malloc(size);
}
void *mdns_mem_calloc(size_t num, size_t size)
{
return calloc(num, size);
}
void mdns_mem_free(void *ptr)
{
free(ptr);
}
char *mdns_mem_strdup(const char *s)
{
return strdup(s);
}
char *mdns_mem_strndup(const char *s, size_t n)
{
return strndup(s, n);
}
void *mdns_mem_task_malloc(size_t size)
{
return malloc(size);
}
void mdns_mem_task_free(void *ptr)
{
free(ptr);
}

View File

@@ -0,0 +1,142 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _ESP32_COMPAT_H_
#define _ESP32_COMPAT_H_
// Skip these include files
#define ESP_MDNS_NETWORKING_H_
#define INC_FREERTOS_H
#define QUEUE_H
#define SEMAPHORE_H
#define _ESP_TASK_H_
#ifdef USE_BSD_STRING
#include <features.h>
#include <bsd/string.h>
#endif
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include "esp_timer.h"
#define ESP_FAIL -1
#define ESP_ERR_NO_MEM 0x101
#define ESP_ERR_INVALID_ARG 0x102
#define ESP_ERR_INVALID_STATE 0x103
#define ESP_ERR_INVALID_SIZE 0x104
#define ESP_ERR_NOT_FOUND 0x105
#define ESP_ERR_NOT_SUPPORTED 0x106
#define ESP_ERR_TIMEOUT 0x107
#define ESP_ERR_INVALID_RESPONSE 0x108
#define ESP_ERR_INVALID_CRC 0x109
#define MDNS_TASK_MEMORY_LOG "internal RAM"
#define MALLOC_CAP_8BIT (1<<2)
#define MALLOC_CAP_INTERNAL (1<<11)
#define pdTRUE true
#define pdFALSE false
#define pdPASS ( pdTRUE )
#define pdFAIL ( pdFALSE )
#define portMAX_DELAY 0xFFFFFFFF
#define portTICK_PERIOD_MS 1
#define LWIP_HDR_PBUF_H
#define __ESP_RANDOM_H__
#define INC_TASK_H
#define pdMS_TO_TICKS(a) a
#define xSemaphoreTake(s,d) true
#define xTaskDelete(a)
#define vTaskDelete(a) free(a)
#define xSemaphoreGive(s)
#define xQueueCreateMutex(s)
#define _mdns_pcb_init(a,b) true
#define _mdns_pcb_deinit(a,b) true
#define xSemaphoreCreateMutex() malloc(1)
#define xSemaphoreCreateBinary() malloc(1)
#define vSemaphoreDelete(s) free(s)
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U
#define xTaskCreatePinnedToCore(a,b,c,d,e,f,g) *(f) = malloc(1)
#define xTaskCreateStaticPinnedToCore(a,b,c,d,e,f,g,h) true
#define vTaskDelay(m) usleep((m)*0)
#define esp_random() (rand()%UINT32_MAX)
#define ESP_TASK_PRIO_MAX 25
#define ESP_TASKD_EVENT_PRIO 5
#define _mdns_udp_pcb_write(tcpip_if, ip_protocol, ip, port, data, len) len
#define TaskHandle_t TaskHandle_t
typedef int32_t esp_err_t;
typedef void *SemaphoreHandle_t;
typedef void *QueueHandle_t;
typedef void *TaskHandle_t;
typedef int BaseType_t;
typedef uint32_t TickType_t;
typedef void *StackType_t;
typedef void *StaticTask_t;
struct udp_pcb {
uint8_t dummy;
};
struct ip4_addr {
uint32_t addr;
};
typedef struct ip4_addr ip4_addr_t;
struct ip6_addr {
uint32_t addr[4];
};
typedef struct ip6_addr ip6_addr_t;
typedef void *system_event_t;
struct pbuf {
struct pbuf *next;
void *payload;
uint16_t tot_len;
uint16_t len;
uint8_t /*pbuf_type*/ type;
uint8_t flags;
uint16_t ref;
};
uint32_t xTaskGetTickCount(void);
typedef void (*esp_timer_cb_t)(void *arg);
// Queue mock
QueueHandle_t xQueueCreate(uint32_t uxQueueLength,
uint32_t uxItemSize);
void vQueueDelete(QueueHandle_t xQueue);
uint32_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
uint32_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
void GetLastItem(void *pvBuffer);
void ForceTaskDelete(void);
esp_err_t esp_event_handler_register(const char *event_base, int32_t event_id, void *event_handler, void *event_handler_arg);
esp_err_t esp_event_handler_unregister(const char *event_base, int32_t event_id, void *event_handler);
TaskHandle_t xTaskGetCurrentTaskHandle(void);
void xTaskNotifyGive(TaskHandle_t task);
BaseType_t xTaskNotifyWait(uint32_t bits_entry_clear, uint32_t bits_exit_clear, uint32_t *value, TickType_t wait_time);
#endif //_ESP32_COMPAT_H_

View File

@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#define IRAM_ATTR
#define FLAG_ATTR(TYPE)
#define QUEUE_H
#define __ARCH_CC_H__
#define __XTENSA_API_H__
#define SSIZE_MAX INT_MAX
#define LWIP_HDR_IP6_ADDR_H
#define LWIP_HDR_IP4_ADDR_H

View File

@@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include "esp32_mock.h"
typedef struct esp_netif_s esp_netif_t;
typedef struct esp_netif_ip_info esp_netif_ip_info_t;
typedef struct esp_netif_dhcp_status esp_netif_dhcp_status_t;
const char *IP_EVENT = "IP_EVENT";
esp_err_t esp_netif_add_to_list(esp_netif_t *netif)
{
return ESP_OK;
}
esp_err_t esp_netif_remove_from_list(esp_netif_t *netif)
{
return ESP_ERR_NOT_FOUND;
}
esp_netif_t *esp_netif_next(esp_netif_t *netif)
{
return NULL;
}
esp_netif_t *esp_netif_next_unsafe(esp_netif_t *netif)
{
return NULL;
}
esp_netif_t *esp_netif_get_handle_from_ifkey(const char *if_key)
{
return NULL;
}
esp_err_t esp_netif_get_ip_info(esp_netif_t *esp_netif, esp_netif_ip_info_t *ip_info)
{
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t esp_netif_dhcpc_get_status(esp_netif_t *esp_netif, esp_netif_dhcp_status_t *status)
{
return ESP_ERR_NOT_SUPPORTED;
}

View File

@@ -0,0 +1,166 @@
Input: in/test-14.bin
Packet Length: 568
Questions: 18
Q: _airport._tcp.local. PTR IN
Q: _http._tcp.local. PTR IN
Q: _printer._tcp.local. PTR IN
Q: _sub._http._tcp.local. PTR IN
Q: _airplay._tcp.local. PTR IN
Q: _raop._tcp.local. PTR IN
Q: _uscan._tcp.local. PTR IN
Q: _uscans._tcp.local. PTR IN
Q: _ippusb._tcp.local. PTR IN
Q: _scanner._tcp.local. PTR IN
Q: _ipp._tcp.local. PTR IN
Q: _ipps._tcp.local. PTR IN
Q: _pdl-datastream._tcp.local. PTR IN
Q: _ptp._tcp.local. PTR IN
Q: _sleep-proxy._udp.local. PTR IN
Q: 9801A7E58FA1@Hristo's AirPort Express._raop._tcp.local. TXT IN
Q: Hristo's AirPort Express._airport._tcp.local. TXT IN
Q: Hristo's Time Capsule._airport._tcp.local. TXT IN
Answers: 7 + 0
A: _airport._tcp.local. PTR IN 2272 [2] Hristo's AirPort Express._airport._tcp.local.
A: _airport._tcp.local. PTR IN 2272 [2] Hristo's Time Capsule._airport._tcp.local.
A: _http._tcp.local. PTR IN 2535 [23] HP LaserJet CP1025nw._http._tcp.local.
A: _printer._tcp.local. PTR IN 2535 [23] HP LaserJet CP1025nw._printer._tcp.local.
A: _ipp._tcp.local. PTR IN 2535 [23] HP LaserJet CP1025nw._ipp._tcp.local.
A: _pdl-datastream._tcp.local. PTR IN 2535 [23] HP LaserJet CP1025nw._pdl-datastream._tcp.local.
A: _sleep-proxy._udp.local. PTR IN 2535 [38] 50-34-10-70.1 Hristo's Time Capsule._sleep-proxy._udp.local.
Input: in/test-15.bin
Packet Length: 524
Answers: 3 + 3
A: Hristo's AirPort Express._airport._tcp.local. TXT IN FLUSH 4500 [166] waMA=98-01-A7-E5-8F-A1,raMA=98-01-A7-E8-C2-2E,raM2=98-01-A7-E8-C2-2F,raNm=your-ssid,raCh=1,rCh2=52,raSt=0,raNA=1,syFl=0x8A0C,syAP=115,syVs=7.6.8,srcv=76800.1,bjSd=23
A: 9801A7E58FA1@Hristo's AirPort Express._raop._tcp.local. TXT IN FLUSH 4500 [134] txtvers=1; ch=2; cn=0,1; et=0,4; sv=false; da=true; sr=44100; ss=16; pw=false; vn=65537; tp=TCP,UDP; vs=105.1; am=AirPort10,115; fv=76800.1; sf=0x1
A: _raop._tcp.local. PTR IN 4500 [2] 9801A7E58FA1@Hristo's AirPort Express._raop._tcp.local.
A: 9801A7E58FA1@Hristo's AirPort Express._raop._tcp.local. SRV IN FLUSH 120 [32] 5000 Hristos-AirPort-Express.local.
A: Hristo's AirPort Express.local. NSEC IN FLUSH 4500 [9] Hristo's AirPort Express._airport._tcp.local. 00 05 00 00 80 00 40
A: 9801A7E58FA1@Hristo's AirPort Express.local. NSEC IN FLUSH 4500 [9] 9801A7E58FA1@Hristo's AirPort Express._raop._tcp.local. 00 05 00 00 80 00 40
Input: in/test-16.bin
Packet Length: 254
Answers: 1 + 1
A: Hristo's Time Capsule._airport._tcp.local. TXT IN FLUSH 4500 [168] waMA=70-73-CB-B4-C9-B3,raMA=70-73-CB-BB-04-E7,raM2=70-73-CB-BB-04-E8,raNm=nbis-test,raCh=11,rCh2=132,raSt=0,raNA=0,syFl=0x820C,syAP=116,syVs=7.6.8,srcv=76800.1,bjSd=30
A: Hristo's Time Capsule.local. NSEC IN FLUSH 4500 [9] Hristo's Time Capsule._airport._tcp.local. 00 05 00 00 80 00 40
Input: in/test-28.bin
Packet Length: 62
Questions: 1
Q: Hristo's Time Capsule._afpovertcp._tcp.local. SRV IN FLUSH
Input: in/test-29.bin
Packet Length: 39
Questions: 2
Q: minifritz.local. A IN FLUSH
Q: minifritz.local. AAAA IN FLUSH
Input: in/test-31.bin
Packet Length: 91
Answers: 2 + 1
A: minifritz.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:142e:54ff:b8c4:fd09
A: minifritz.local. A IN FLUSH 120 [4] 192.168.254.16
A: minifritz.local. NSEC IN FLUSH 120 [8] minifritz...local. 00 04 40 00 00 08
Input: in/test-53.bin
Packet Length: 140
Questions: 2
Q: _smb._tcp.local. PTR IN
Q: Sofiya-Ivanovas-MacBook.local. A IN
Answers: 2 + 0
A: _smb._tcp.local. PTR IN 3061 [29] Sofiya Ivanovas MacBook._smb._tcp.local.
A: _smb._tcp.local. PTR IN 3062 [24] Hristo's Time Capsule._smb._tcp.local.
Input: in/test-56.bin
Packet Length: 262
Answers: 2 + 6
A: Hristos Mac mini._device-info._tcp.local. TXT IN 4500 [28] model=Macmini6,2; osxvers=16
A: _smb._tcp.local. PTR IN 4500 [22] Hristos Mac mini._smb._tcp.local.
A: Hristos Mac mini._smb._tcp.local. TXT IN FLUSH 4500 [1]
A: Hristos Mac mini._smb._tcp.local. SRV IN FLUSH 120 [18] 445 minifritz.local.
A: minifritz.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:142e:54ff:b8c4:fd09
A: minifritz.local. A IN FLUSH 120 [4] 192.168.254.16
A: Hristos Mac mini.local. NSEC IN FLUSH 4500 [9] Hristos Mac mini._smb._tcp.local. 00 05 00 00 80 00 40
A: minifritz.local. NSEC IN FLUSH 120 [8] minifritz...local. 00 04 40 00 00 08
Input: in/test-63.bin
Packet Length: 147
Questions: 2
Q: _afpovertcp._tcp.local. PTR IN
Q: Sofiya-Ivanovas-MacBook.local. A IN
Answers: 2 + 0
A: _afpovertcp._tcp.local. PTR IN 2881 [29] Sofiya Ivanovas MacBook._afpovertcp._tcp.local.
A: _afpovertcp._tcp.local. PTR IN 2881 [24] Hristo's Time Capsule._afpovertcp._tcp.local.
Input: in/test-66.bin
Packet Length: 269
Answers: 2 + 6
A: Hristos Mac mini._device-info._tcp.local. TXT IN 4500 [28] model=Macmini6,2; osxvers=16
A: _afpovertcp._tcp.local. PTR IN 4500 [22] Hristos Mac mini._afpovertcp._tcp.local.
A: Hristos Mac mini._afpovertcp._tcp.local. TXT IN FLUSH 4500 [1]
A: Hristos Mac mini._afpovertcp._tcp.local. SRV IN FLUSH 120 [18] 548 minifritz.local.
A: minifritz.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:142e:54ff:b8c4:fd09
A: minifritz.local. A IN FLUSH 120 [4] 192.168.254.16
A: Hristos Mac mini.local. NSEC IN FLUSH 4500 [9] Hristos Mac mini._afpovertcp._tcp.local. 00 05 00 00 80 00 40
A: minifritz.local. NSEC IN FLUSH 120 [8] minifritz...local. 00 04 40 00 00 08
Input: in/test-83.bin
Packet Length: 105
Answers: 1 + 2
A: Sofiya-Ivanovas-MacBook.local. A IN FLUSH 120 [4] 192.168.254.20
A: Sofiya-Ivanovas-MacBook.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:021c:b3ff:feb2:72a3
A: Sofiya-Ivanovas-MacBook.local. NSEC IN FLUSH 120 [8] Sofiya-Ivanovas-MacBook...local. 00 04 40 00 00 08
Input: in/test-88.bin
Packet Length: 48
Questions: 2
Q: _rfb._tcp.local. PTR IN
Q: _airport._tcp.local. PTR IN
Input: in/test-89.bin
Packet Length: 459
Answers: 2 + 7
A: _airport._tcp.local. PTR IN 4500 [24] Hristo's Time Capsule._airport._tcp.local.
A: Hristo's Time Capsule._device-info._tcp.local. TXT IN 4500 [23] model=TimeCapsule6,116
A: Hristos-Time-Capsule.local. A IN FLUSH 120 [4] 192.168.254.49
A: Hristo's Time Capsule._airport._tcp.local. TXT IN FLUSH 4500 [168] waMA=70-73-CB-B4-C9-B3,raMA=70-73-CB-BB-04-E7,raM2=70-73-CB-BB-04-E8,raNm=nbis-test,raCh=11,rCh2=132,raSt=0,raNA=0,syFl=0x820C,syAP=116,syVs=7.6.8,srcv=76800.1,bjSd=30
A: Hristos-Time-Capsule.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:7273:cbff:feb4:c9b3
A: Hristo's Time Capsule._airport._tcp.local. SRV IN FLUSH 120 [8] 5009 Hristos-Time-Capsule.local.
A: Hristos-Time-Capsule.local. A IN FLUSH 120 [4] 169.254.23.40
A: Hristos-Time-Capsule.local. NSEC IN FLUSH 120 [8] Hristos-Time-Capsule...local. 00 04 40 00 00 08
A: Hristo's Time Capsule.local. NSEC IN FLUSH 4500 [9] Hristo's Time Capsule._airport._tcp.local. 00 05 00 00 80 00 40
Input: in/test-91.bin
Packet Length: 279
Answers: 2 + 6
A: Sofiya Ivanovas MacBook._device-info._tcp.local. TXT IN 4500 [17] model=Macmini2,1
A: _rfb._tcp.local. PTR IN 4500 [29] Sofiya Ivanovas MacBook._rfb._tcp.local.
A: Sofiya Ivanovas MacBook._rfb._tcp.local. TXT IN FLUSH 4500 [1]
A: Sofiya Ivanovas MacBook._rfb._tcp.local. SRV IN FLUSH 120 [32] 5900 Sofiya-Ivanovas-MacBook.local.
A: Sofiya-Ivanovas-MacBook.local. AAAA IN FLUSH 120 [16] fe80:0000:0000:0000:021c:b3ff:feb2:72a3
A: Sofiya-Ivanovas-MacBook.local. A IN FLUSH 120 [4] 192.168.254.20
A: Sofiya Ivanovas MacBook.local. NSEC IN FLUSH 4500 [9] Sofiya Ivanovas MacBook._rfb._tcp.local. 00 05 00 00 80 00 40
A: Sofiya-Ivanovas-MacBook.local. NSEC IN FLUSH 120 [8] Sofiya-Ivanovas-MacBook...local. 00 04 40 00 00 08
Input: in/test-95.bin
Packet Length: 286
Questions: 3
Q: _afpovertcp._tcp.local. PTR IN
Q: _smb._tcp.local. PTR IN
Q: _adisk._tcp.local. PTR IN
Answers: 6 + 0
A: _afpovertcp._tcp.local. PTR IN 2353 [29] Sofiya Ivanovas MacBook._afpovertcp._tcp.local.
A: _afpovertcp._tcp.local. PTR IN 3973 [22] Hristos Mac mini._afpovertcp._tcp.local.
A: _afpovertcp._tcp.local. PTR IN 2353 [24] Hristo's Time Capsule._afpovertcp._tcp.local.
A: _smb._tcp.local. PTR IN 2353 [29] Sofiya Ivanovas MacBook._smb._tcp.local.
A: _smb._tcp.local. PTR IN 3792 [22] Hristos Mac mini._smb._tcp.local.
A: _smb._tcp.local. PTR IN 2353 [24] Hristo's Time Capsule._smb._tcp.local.
Input: in/test-96.bin
Packet Length: 319
Answers: 2 + 3
A: Hristo's Time Capsule._device-info._tcp.local. TXT IN 4500 [23] model=TimeCapsule6,116
A: _adisk._tcp.local. PTR IN 4500 [24] Hristo's Time Capsule._adisk._tcp.local.
A: Hristo's Time Capsule._adisk._tcp.local. TXT IN FLUSH 4500 [110] sys=waMA=70:73:CB:B4:C9:B3,adVF=0x1000; dk2=adVF=0x1083,adVN=Capsule,adVU=55fabb8b-a63b-5441-9874-6edb504eb30a
A: Hristo's Time Capsule._adisk._tcp.local. SRV IN FLUSH 120 [29] 9 Hristos-Time-Capsule.local.
A: Hristo's Time Capsule.local. NSEC IN FLUSH 4500 [9] Hristo's Time Capsule._adisk._tcp.local. 00 05 00 00 80 00 40

View File

@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* MDNS Dependecy injection -- preincluded to inject interface test functions into static variables
*
*/
#include "mdns.h"
#include "mdns_private.h"
void (*mdns_test_static_execute_action)(mdns_action_t *) = NULL;
mdns_srv_item_t *(*mdns_test_static_mdns_get_service_item)(const char *service, const char *proto, const char *hostname) = NULL;
mdns_search_once_t *(*mdns_test_static_search_init)(const char *name, const char *service, const char *proto, uint16_t type, bool unicast,
uint32_t timeout, uint8_t max_results,
mdns_query_notify_t notifier) = NULL;
esp_err_t (*mdns_test_static_send_search_action)(mdns_action_type_t type, mdns_search_once_t *search) = NULL;
void (*mdns_test_static_search_free)(mdns_search_once_t *search) = NULL;
static void _mdns_execute_action(mdns_action_t *action);
static mdns_srv_item_t *_mdns_get_service_item(const char *service, const char *proto, const char *hostname);
static mdns_search_once_t *_mdns_search_init(const char *name, const char *service, const char *proto, uint16_t type, bool unicast,
uint32_t timeout, uint8_t max_results, mdns_query_notify_t notifier);
static esp_err_t _mdns_send_search_action(mdns_action_type_t type, mdns_search_once_t *search);
static void _mdns_search_free(mdns_search_once_t *search);
void mdns_test_init_di(void)
{
mdns_test_static_execute_action = _mdns_execute_action;
mdns_test_static_mdns_get_service_item = _mdns_get_service_item;
mdns_test_static_search_init = _mdns_search_init;
mdns_test_static_send_search_action = _mdns_send_search_action;
mdns_test_static_search_free = _mdns_search_free;
}
void mdns_test_execute_action(void *action)
{
mdns_test_static_execute_action((mdns_action_t *)action);
}
void mdns_test_search_free(mdns_search_once_t *search)
{
return mdns_test_static_search_free(search);
}
esp_err_t mdns_test_send_search_action(mdns_action_type_t type, mdns_search_once_t *search)
{
return mdns_test_static_send_search_action(type, search);
}
mdns_search_once_t *mdns_test_search_init(const char *name, const char *service, const char *proto, uint16_t type, uint32_t timeout, uint8_t max_results)
{
return mdns_test_static_search_init(name, service, proto, type, timeout, type != MDNS_TYPE_PTR, max_results, NULL);
}
mdns_srv_item_t *mdns_test_mdns_get_service_item(const char *service, const char *proto)
{
return mdns_test_static_mdns_get_service_item(service, proto, NULL);
}

View File

@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include "esp32_mock.h"
#include "mdns.h"
#include "mdns_private.h"
static inline void *_mdns_get_packet_data(mdns_rx_packet_t *packet)
{
return packet->pb->payload;
}
static inline size_t _mdns_get_packet_len(mdns_rx_packet_t *packet)
{
return packet->pb->len;
}
static inline void _mdns_packet_free(mdns_rx_packet_t *packet)
{
free(packet->pb);
free(packet);
}
static inline bool mdns_is_netif_ready(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
{
return true;
}

View File

@@ -0,0 +1,276 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include "esp32_mock.h"
#include "mdns.h"
#include "mdns_private.h"
//
// Global stuctures containing packet payload, search
mdns_rx_packet_t g_packet;
struct pbuf mypbuf;
mdns_search_once_t *search = NULL;
//
// Dependency injected test functions
void mdns_test_execute_action(void *action);
mdns_srv_item_t *mdns_test_mdns_get_service_item(const char *service, const char *proto);
mdns_search_once_t *mdns_test_search_init(const char *name, const char *service, const char *proto, uint16_t type, uint32_t timeout, uint8_t max_results);
esp_err_t mdns_test_send_search_action(mdns_action_type_t type, mdns_search_once_t *search);
void mdns_test_search_free(mdns_search_once_t *search);
void mdns_test_init_di(void);
extern mdns_server_t *_mdns_server;
//
// mdns function wrappers for mdns setup in test mode
static int mdns_test_hostname_set(const char *mdns_hostname)
{
for (int i = 0; i < MDNS_MAX_INTERFACES; i++) {
_mdns_server->interfaces[i].pcbs[MDNS_IP_PROTOCOL_V4].state = PCB_RUNNING; // mark the PCB running to exercise mdns in fully operational mode
_mdns_server->interfaces[i].pcbs[MDNS_IP_PROTOCOL_V6].state = PCB_RUNNING;
}
int ret = mdns_hostname_set(mdns_hostname);
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
return ret;
}
static int mdns_test_add_delegated_host(const char *mdns_hostname)
{
mdns_ip_addr_t addr = { .addr = { .u_addr = ESP_IPADDR_TYPE_V4 } };
addr.addr.u_addr.ip4.addr = 0x11111111;
int ret = mdns_delegate_hostname_add(mdns_hostname, &addr);
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
return ret;
}
static int mdns_test_service_instance_name_set(const char *service, const char *proto, const char *instance)
{
int ret = mdns_service_instance_name_set(service, proto, instance);
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
return ret;
}
static int mdns_test_service_txt_set(const char *service, const char *proto, uint8_t num_items, mdns_txt_item_t txt[])
{
int ret = mdns_service_txt_set(service, proto, txt, num_items);
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
return ret;
}
static int mdns_test_sub_service_add(const char *sub_name, const char *service_name, const char *proto, uint32_t port)
{
if (mdns_service_add(NULL, service_name, proto, port, NULL, 0)) {
// This is expected failure as the service thread is not running
}
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
if (mdns_test_mdns_get_service_item(service_name, proto) == NULL) {
return ESP_FAIL;
}
int ret = mdns_service_subtype_add_for_host(NULL, service_name, proto, NULL, sub_name);
a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
return ret;
}
static int mdns_test_service_add(const char *service_name, const char *proto, uint32_t port)
{
if (mdns_service_add(NULL, service_name, proto, port, NULL, 0)) {
// This is expected failure as the service thread is not running
}
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
if (mdns_test_mdns_get_service_item(service_name, proto) == NULL) {
return ESP_FAIL;
}
return ESP_OK;
}
static mdns_result_t *mdns_test_query(const char *name, const char *service, const char *proto, uint16_t type)
{
search = mdns_test_search_init(name, service, proto, type, 3000, 20);
if (!search) {
abort();
}
if (mdns_test_send_search_action(ACTION_SEARCH_ADD, search)) {
mdns_test_search_free(search);
abort();
}
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
return NULL;
}
static void mdns_test_query_free(void)
{
mdns_test_search_free(search);
}
//
// function "under test" where afl-mangled packets passed
//
void mdns_parse_packet(mdns_rx_packet_t *packet);
//
// Test starts here
//
int main(int argc, char **argv)
{
int i;
const char *mdns_hostname = "minifritz";
const char *mdns_instance = "Hristo's Time Capsule";
mdns_txt_item_t arduTxtData[4] = {
{"board", "esp32"},
{"tcp_check", "no"},
{"ssh_upload", "no"},
{"auth_upload", "no"}
};
const uint8_t mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x32};
uint8_t buf[1460];
char winstance[21 + strlen(mdns_hostname)];
sprintf(winstance, "%s [%02x:%02x:%02x:%02x:%02x:%02x]", mdns_hostname, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// Init depencency injected methods
mdns_test_init_di();
if (mdns_init()) {
abort();
}
if (mdns_test_hostname_set(mdns_hostname)) {
abort();
}
if (mdns_test_add_delegated_host(mdns_hostname) || mdns_test_add_delegated_host("megafritz")) {
abort();
}
#ifndef MDNS_NO_SERVICES
if (mdns_test_sub_service_add("_server", "_fritz", "_tcp", 22)) {
abort();
}
if (mdns_test_service_add("_telnet", "_tcp", 22)) {
abort();
}
if (mdns_test_service_add("_workstation", "_tcp", 9)) {
abort();
}
if (mdns_test_service_instance_name_set("_workstation", "_tcp", winstance)) {
abort();
}
if (mdns_test_service_add("_arduino", "_tcp", 3232)) {
abort();
}
if (mdns_test_service_txt_set("_arduino", "_tcp", 4, arduTxtData)) {
abort();
}
if (mdns_test_service_add("_http", "_tcp", 80)) {
abort();
}
if (mdns_test_service_instance_name_set("_http", "_tcp", "ESP WebServer")) {
abort();
}
if (
mdns_test_service_add("_afpovertcp", "_tcp", 548)
|| mdns_test_service_add("_rfb", "_tcp", 885)
|| mdns_test_service_add("_smb", "_tcp", 885)
|| mdns_test_service_add("_adisk", "_tcp", 885)
|| mdns_test_service_add("_airport", "_tcp", 885)
|| mdns_test_service_add("_printer", "_tcp", 885)
|| mdns_test_service_add("_airplay", "_tcp", 885)
|| mdns_test_service_add("_raop", "_tcp", 885)
|| mdns_test_service_add("_uscan", "_tcp", 885)
|| mdns_test_service_add("_uscans", "_tcp", 885)
|| mdns_test_service_add("_ippusb", "_tcp", 885)
|| mdns_test_service_add("_scanner", "_tcp", 885)
|| mdns_test_service_add("_ipp", "_tcp", 885)
|| mdns_test_service_add("_ipps", "_tcp", 885)
|| mdns_test_service_add("_pdl-datastream", "_tcp", 885)
|| mdns_test_service_add("_ptp", "_tcp", 885)
|| mdns_test_service_add("_sleep-proxy", "_udp", 885)) {
abort();
}
#endif
mdns_result_t *results = NULL;
FILE *file;
size_t nread;
#ifdef INSTR_IS_OFF
size_t len = 1460;
memset(buf, 0, 1460);
if (argc != 2) {
printf("Non-instrumentation mode: please supply a file name created by AFL to reproduce crash\n");
return 1;
} else {
//
// Note: parameter1 is a file (mangled packet) which caused the crash
file = fopen(argv[1], "r");
assert(file >= 0);
len = fread(buf, 1, 1460, file);
fclose(file);
}
for (i = 0; i < 1; i++) {
#else
while (__AFL_LOOP(1000)) {
memset(buf, 0, 1460);
size_t len = read(0, buf, 1460);
#endif
mypbuf.payload = malloc(len);
memcpy(mypbuf.payload, buf, len);
mypbuf.len = len;
g_packet.pb = &mypbuf;
mdns_test_query("minifritz", "_fritz", "_tcp", MDNS_TYPE_ANY);
mdns_test_query(NULL, "_fritz", "_tcp", MDNS_TYPE_PTR);
mdns_test_query(NULL, "_afpovertcp", "_tcp", MDNS_TYPE_PTR);
mdns_parse_packet(&g_packet);
free(mypbuf.payload);
}
#ifndef MDNS_NO_SERVICES
mdns_service_remove_all();
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
#endif
ForceTaskDelete();
mdns_free();
return 0;
}

View File

@@ -0,0 +1,7 @@
# The following four lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mdns_test_app)

View File

@@ -0,0 +1,49 @@
| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 |
| ----------------- | ----- | -------- | -------- |
# mDNS test project
Main purpose of this application is to test the mDNS library to correctly advertise, lookup services and hosts.
The "app_test.py" logically separated from two sets of test cases 1. "ESP32 board sends queries -> Host sends answers" and 2. "Host sends queries" -> "ESP32 board sends answers".
Two first sets of test cases are starting by 'test_query_' and the second ones by 'start_case'.
## Runtime settings
1. For first sets of test cases python creates and sends dns queries using "dpkt" library
2. For the second sets of test cases this app waits for user input to provide test case(e.g. CONFIG_TEST_QUERY_HOST, CONFIG_TEST_QUERY_HOST_ASYNC or CONFIG_TEST_QUERY_SERVICE)
In order to run both of them just needed to set up the project and run by 'python app_test.py'
## Test app workflow
- mDNS is initialized with hostname and instance name defined through the project configuration and `_http._tcp` service is added to be advertised
- A delegated host `esp32-delegated._local` is added and another `_http._tcp` service is added for this host.
- WiFi STA is started and tries to connect to the access point defined through the project configuration
### Configure the project
* Open the project configuration menu (`idf.py menuconfig`)
* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu.
* Set `mDNS Hostname` as host name prefix for the device and its instance name in `mDNS Instance Name`
### Build and Flash
Build the project and flash it to the board, then run the monitor tool to view the serial output:
```
idf.py -p PORT flash monitor
```
- Wait for WiFi to connect to your access point
- You can now ping the device at `[board-hostname].local`, where `[board-hostname]` is preconfigured hostname, `esp32-mdns` by default.
- You can also browse for `_http._tcp` on the same network to find the advertised service
- Note that for purpose of CI tests, configuration options of `MDNS_RESOLVE_TEST_SERVICES` and `MDNS_ADD_MAC_TO_HOSTNAME` are available, but disabled by default. If enabled, then the hostname suffix of last 3 bytes from device MAC address is added, e.g. `esp32-mdns-80FFFF`, and a query for test service is issued.
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Hardware Required
This test-app can be executed on any ESP32 board, the only required interface is WiFi and connection to a local network and tls server.

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c" "mdns_test.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,40 @@
menu "Example Configuration"
config TEST_MDNS_HOSTNAME
string "mDNS Hostname"
default "esp32-mdns"
help
mDNS Hostname for example to use
config TEST_MDNS_INSTANCE
string "mDNS Instance Name"
default "ESP32 with mDNS"
help
mDNS Instance Name for example to use
config TEST_MDNS_PUBLISH_DELEGATE_HOST
bool "Publish a delegated host"
help
Enable publishing a delegated host other than ESP32.
The example will also add a mock service for this host.
config TEST_MDNS_ADD_MAC_TO_HOSTNAME
bool "Add mac suffix to hostname"
default n
help
If enabled, a portion of MAC address is added to the hostname, this is used
for evaluation of tests in CI
config MDNS_ADD_MAC_TO_HOSTNAME
bool "Add mac suffix to hostname"
default n
help
If enabled, a portion of MAC address is added to the hostname, this is used
for evaluation of tests in CI
config MDNS_PUBLISH_DELEGATE_HOST
bool "Publish a delegated host"
help
Enable publishing a delegated host other than ESP32.
The example will also add a mock service for this host.
endmenu

View File

@@ -0,0 +1,8 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/mdns:
version: "^1.0.0"
override_path: "../../../"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common

View File

@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "esp_mac.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_log.h"
#include "protocol_examples_common.h"
#include "mdns.h"
static const char *TAG = "mdns_test";
void mdns_test(char *line);
static void get_string(char *line, size_t size)
{
int count = 0;
while (count < size) {
int c = fgetc(stdin);
if (c == '\n') {
line[count] = '\0';
break;
} else if (c > 0 && c < 127) {
line[count] = c;
++count;
}
vTaskDelay(20 / portTICK_PERIOD_MS);
}
}
/** Generate host name based on sdkconfig, optionally adding a portion of MAC address to it.
* @return host name string allocated from the heap
*/
static char *generate_hostname(void)
{
#ifndef CONFIG_TEST_MDNS_ADD_MAC_TO_HOSTNAME
return strdup(CONFIG_TEST_MDNS_HOSTNAME);
#else
uint8_t mac[6];
char *hostname;
esp_read_mac(mac, ESP_MAC_WIFI_STA);
if (-1 == asprintf(&hostname, "%s-%02X%02X%02X", CONFIG_TEST_MDNS_HOSTNAME, mac[3], mac[4], mac[5])) {
abort();
}
return hostname;
#endif
}
static void initialise_mdns(void)
{
char *hostname = generate_hostname();
//initialize mDNS
ESP_ERROR_CHECK(mdns_init());
//set mDNS hostname (required if you want to advertise services)
ESP_ERROR_CHECK(mdns_hostname_set(hostname));
ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname);
//set default mDNS instance name
ESP_ERROR_CHECK(mdns_instance_name_set(CONFIG_TEST_MDNS_INSTANCE));
//initialize service
ESP_ERROR_CHECK(mdns_service_add("ESP32-WebServer", "_http", "_tcp", 80, NULL, 0));
#if CONFIG_TEST_MDNS_PUBLISH_DELEGATE_HOST
char *delegated_hostname;
if (-1 == asprintf(&delegated_hostname, "%s-delegated", hostname)) {
abort();
}
mdns_ip_addr_t addr4, addr6;
esp_netif_str_to_ip4("10.0.0.1", &addr4.addr.u_addr.ip4);
addr4.addr.type = ESP_IPADDR_TYPE_V4;
esp_netif_str_to_ip6("fd11:22::1", &addr6.addr.u_addr.ip6);
addr6.addr.type = ESP_IPADDR_TYPE_V6;
addr4.next = &addr6;
addr6.next = NULL;
ESP_ERROR_CHECK(mdns_delegate_hostname_add(delegated_hostname, &addr4));
ESP_ERROR_CHECK(mdns_service_add_for_host("test0", "_http", "_tcp", delegated_hostname, 1234, NULL, 0));
free(delegated_hostname);
#endif // CONFIG_TEST_MDNS_PUBLISH_DELEGATE_HOST
ESP_ERROR_CHECK(mdns_service_subtype_add_for_host("ESP32-WebServer", "_http", "_tcp", NULL, "_server"));
free(hostname);
}
void app_main(void)
{
ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
initialise_mdns();
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
while (1) {
char line[256];
get_string(line, sizeof(line));
mdns_test(line);
continue;
}
}

View File

@@ -0,0 +1,189 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "mdns.h"
#include "esp_log.h"
#include "esp_netif.h"
static const char *TAG = "mdns_test_app";
static const int RETRY_COUNT = 10;
static void mdns_print_results(mdns_result_t *results)
{
mdns_result_t *r = results;
mdns_ip_addr_t *a = NULL;
int t;
while (r) {
if (r->instance_name) {
printf("PTR:%s.%s.%s\n", r->instance_name, r->service_type, r->proto);
}
if (r->hostname) {
printf("SRV:%s.local:%u\n", r->hostname, r->port);
}
if (r->txt_count) {
printf("TXT:[%zu] ", r->txt_count);
for (t = 0; t < r->txt_count; t++) {
printf("%s=%s(%d); ", r->txt[t].key, r->txt[t].value ? r->txt[t].value : "NULL", r->txt_value_len[t]);
}
printf("\n");
}
a = r->addr;
while (a) {
if (a->addr.type == ESP_IPADDR_TYPE_V6) {
printf(" AAAA: " IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6));
} else {
printf(" A : " IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4)));
}
a = a->next;
}
r = r->next;
}
}
static bool check_and_print_result(mdns_search_once_t *search)
{
// Check if any result is available
mdns_result_t *result = NULL;
if (!mdns_query_async_get_results(search, 0, &result, NULL)) {
return false;
}
if (!result) { // search timeout, but no result
return false;
}
// If yes, print the result
mdns_ip_addr_t *a = result->addr;
while (a) {
if (a->addr.type == ESP_IPADDR_TYPE_V6) {
printf("Async query resolved to AAAA:" IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6));
} else {
printf("Async query resolved to A:" IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4)));
}
a = a->next;
}
// and free the result
mdns_query_results_free(result);
return true;
}
static bool query_mdns_hosts_async(const char *host_name)
{
ESP_LOGI(TAG, "Query both A and AAA: %s.local", host_name);
bool res = false;
mdns_search_once_t *s_a = mdns_query_async_new(host_name, NULL, NULL, MDNS_TYPE_A, 1000, 1, NULL);
mdns_query_async_delete(s_a);
mdns_search_once_t *s_aaaa = mdns_query_async_new(host_name, NULL, NULL, MDNS_TYPE_AAAA, 1000, 1, NULL);
while (s_a || s_aaaa) {
if (s_a && check_and_print_result(s_a)) {
ESP_LOGI(TAG, "Query A %s.local finished", host_name);
mdns_query_async_delete(s_a);
s_a = NULL;
res = true;
}
if (s_aaaa && check_and_print_result(s_aaaa)) {
ESP_LOGI(TAG, "Query AAAA %s.local finished", host_name);
mdns_query_async_delete(s_aaaa);
s_aaaa = NULL;
res = true;
}
}
return res;
}
static esp_err_t query_mdns_host(const char *host_name)
{
ESP_LOGI(TAG, "Query A: %s.local", host_name);
struct esp_ip4_addr addr;
addr.addr = 0;
esp_err_t err = mdns_query_a(host_name, 2000, &addr);
if (err) {
if (err == ESP_ERR_NOT_FOUND) {
ESP_LOGW(TAG, "%s: Host was not found!", esp_err_to_name(err));
}
ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG, "Query A: %s.local resolved to: " IPSTR, host_name, IP2STR(&addr));
return ESP_OK;
}
static esp_err_t query_mdns_service(const char *instance, const char *service_name, const char *proto)
{
ESP_LOGI(TAG, "Query SRV: %s.%s.local", service_name, proto);
mdns_result_t *results = NULL;
esp_err_t err = mdns_query_srv(instance, service_name, proto, 3000, &results);
if (err) {
ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err));
return err;
}
if (!results) {
ESP_LOGW(TAG, "No results found!");
}
mdns_print_results(results);
mdns_query_results_free(results);
return ESP_OK;
}
void query_mdns_service_sub_type(const char *subtype, const char *service_name, const char *proto)
{
ESP_LOGI(TAG, "Query PTR: %s.%s.local", service_name, proto);
mdns_result_t *results = NULL;
esp_err_t err = mdns_query_ptr(service_name, proto, 3000, 20, &results);
if (err) {
ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err));
}
if (!results) {
ESP_LOGW(TAG, "No results found!");
}
mdns_print_results(results);
mdns_query_results_free(results);
}
void mdns_test(const char *line)
{
char test_case[32];
int i = 0;
const TickType_t xDelay = 1000 / portTICK_PERIOD_MS;
sscanf(line, "%s", test_case);
ESP_LOGI(TAG, "test case = %s", test_case);
if (strcmp(test_case, "CONFIG_TEST_QUERY_HOST") == 0) {
i = 0;
while (query_mdns_host("tinytester") != ESP_OK && i != RETRY_COUNT) {
query_mdns_host("tinytester");
i++;
vTaskDelay(xDelay);
}
} else if (strcmp(test_case, "CONFIG_TEST_QUERY_HOST_ASYNC") == 0) {
i = 0;
while (query_mdns_hosts_async("tinytester") == false && i != RETRY_COUNT) {
query_mdns_hosts_async("tinytester");
i++;
vTaskDelay(xDelay);
}
} else if (strcmp(test_case, "CONFIG_TEST_QUERY_SERVICE") == 0) {
i = 0;
while (query_mdns_service("ESP32", "_http", "_tcp") != ESP_OK && i != RETRY_COUNT) {
query_mdns_service("ESP32", "_http", "_tcp");
i++;
vTaskDelay(xDelay);
}
} else {
ESP_LOGE(TAG, "%s: No such test case", test_case);
}
}

View File

@@ -0,0 +1,320 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import re
import select
import socket
import struct
import time
from threading import Event, Thread
import dpkt
import dpkt.dns
import pytest
UDP_PORT = 5353
MCAST_GRP = '224.0.0.251'
# This service is created from esp board startup
SERVICE_NAME = u'ESP32-WebServer._http._tcp.local'
SUB_SERVICE_NAME = u'_server._sub._http._tcp.local'
# This host name answer sent by host, when there is query from board
HOST_NAME = u'tinytester.local'
# This servce answer sent by host, when there is query from board
MDNS_HOST_SERVICE = u'ESP32._http._tcp.local'
# Number of retries to receive mdns answear
RETRY_COUNT = 10
stop_mdns_listener = Event()
start_mdns_listener = Event()
esp_service_answered = Event()
esp_sub_service_answered = Event()
esp_host_answered = Event()
esp_delegated_host_answered = Event()
@pytest.mark.skip
# Get query of ESP32-WebServer._http._tcp.local service
def get_mdns_service_query(service): # type:(str) -> dpkt.dns.Msg
dns = dpkt.dns.DNS()
dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
dns.rcode = dpkt.dns.DNS_RCODE_NOERR
arr = dpkt.dns.DNS.RR()
arr.cls = dpkt.dns.DNS_IN
arr.type = dpkt.dns.DNS_SRV
arr.name = service
arr.target = socket.inet_aton('127.0.0.1')
arr.srvname = service
dns.qd.append(arr)
print('Created mdns service query: {} '.format(dns.__repr__()))
return dns.pack()
@pytest.mark.skip
# Get query of _server_.sub._http._tcp.local sub service
def get_mdns_sub_service_query(sub_service): # type:(str) -> dpkt.dns.Msg
dns = dpkt.dns.DNS()
dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
dns.rcode = dpkt.dns.DNS_RCODE_NOERR
arr = dpkt.dns.DNS.RR()
arr.cls = dpkt.dns.DNS_IN
arr.type = dpkt.dns.DNS_PTR
arr.name = sub_service
arr.target = socket.inet_aton('127.0.0.1')
arr.ptrname = sub_service
dns.qd.append(arr)
print('Created mdns subtype service query: {} '.format(dns.__repr__()))
return dns.pack()
@pytest.mark.skip
# Get query for host resolution
def get_dns_query_for_esp(esp_host): # type:(str) -> dpkt.dns.Msg
dns = dpkt.dns.DNS()
arr = dpkt.dns.DNS.RR()
arr.cls = dpkt.dns.DNS_IN
arr.name = esp_host + u'.local'
dns.qd.append(arr)
print('Created query for esp host: {} '.format(dns.__repr__()))
return dns.pack()
@pytest.mark.skip
# Get mdns answer for host resoloution
def get_dns_answer_to_mdns(tester_host): # type:(str) -> dpkt.dns.Msg
dns = dpkt.dns.DNS()
dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
dns.rcode = dpkt.dns.DNS_RCODE_NOERR
arr = dpkt.dns.DNS.RR()
arr.cls = dpkt.dns.DNS_IN
arr.type = dpkt.dns.DNS_A
arr.name = tester_host
arr.ip = socket.inet_aton('127.0.0.1')
dns.an.append(arr)
print('Created answer to mdns query: {} '.format(dns.__repr__()))
return dns.pack()
@pytest.mark.skip
# Get mdns answer for service query
def get_dns_answer_to_service_query(
mdns_service): # type:(str) -> dpkt.dns.Msg
dns = dpkt.dns.DNS()
dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
dns.rcode = dpkt.dns.DNS_RCODE_NOERR
arr = dpkt.dns.DNS.RR()
arr.name = mdns_service
arr.cls = dpkt.dns.DNS_IN
arr.type = dpkt.dns.DNS_SRV
arr.priority = 0
arr.weight = 0
arr.port = 100
arr.srvname = mdns_service
arr.ip = socket.inet_aton('127.0.0.1')
dns.an.append(arr)
print('Created answer to mdns query: {} '.format(dns.__repr__()))
return dns.pack()
@pytest.mark.skip
def mdns_listener(esp_host): # type:(str) -> None
print('mdns_listener thread started')
UDP_IP = '0.0.0.0'
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.setblocking(False)
sock.bind((UDP_IP, UDP_PORT))
mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
last_query_timepoint = time.time()
QUERY_TIMEOUT = 0.2
while not stop_mdns_listener.is_set():
try:
start_mdns_listener.set()
current_time = time.time()
if current_time - last_query_timepoint > QUERY_TIMEOUT:
last_query_timepoint = current_time
timeout = max(
0, QUERY_TIMEOUT - (current_time - last_query_timepoint))
read_socks, _, _ = select.select([sock], [], [], timeout)
if not read_socks:
continue
data, _ = sock.recvfrom(1024)
dns = dpkt.dns.DNS(data)
# Receives queries from esp board and sends answers
if len(dns.qd) > 0:
if dns.qd[0].name == HOST_NAME:
print('Received query: {} '.format(dns.__repr__()))
sock.sendto(get_dns_answer_to_mdns(HOST_NAME),
(MCAST_GRP, UDP_PORT))
if dns.qd[0].name == HOST_NAME:
print('Received query: {} '.format(dns.__repr__()))
sock.sendto(get_dns_answer_to_mdns(HOST_NAME),
(MCAST_GRP, UDP_PORT))
if dns.qd[0].name == MDNS_HOST_SERVICE:
print('Received query: {} '.format(dns.__repr__()))
sock.sendto(
get_dns_answer_to_service_query(MDNS_HOST_SERVICE),
(MCAST_GRP, UDP_PORT))
# Receives answers from esp board and sets event flags for python test cases
if len(dns.an) == 1:
if dns.an[0].name.startswith(SERVICE_NAME):
print('Received answer to service query: {}'.format(
dns.__repr__()))
esp_service_answered.set()
if len(dns.an) > 1:
if dns.an[1].name.startswith(SUB_SERVICE_NAME):
print('Received answer for sub service query: {}'.format(
dns.__repr__()))
esp_sub_service_answered.set()
if len(dns.an) > 0 and dns.an[0].type == dpkt.dns.DNS_A:
if dns.an[0].name == esp_host + u'.local':
print('Received answer to esp32-mdns query: {}'.format(
dns.__repr__()))
esp_host_answered.set()
if dns.an[0].name == esp_host + u'-delegated.local':
print('Received answer to esp32-mdns-delegate query: {}'.
format(dns.__repr__()))
esp_delegated_host_answered.set()
except socket.timeout:
break
except dpkt.UnpackError:
continue
@pytest.mark.skip
def create_socket(): # type:() -> socket.socket
UDP_IP = '0.0.0.0'
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.setblocking(False)
sock.bind((UDP_IP, UDP_PORT))
mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
return sock
@pytest.mark.skip
def test_query_dns_http_service(service): # type: (str) -> None
print('SRV: Query {}'.format(service))
sock = create_socket()
packet = get_mdns_service_query(service)
for _ in range(RETRY_COUNT):
if esp_service_answered.wait(timeout=25):
break
sock.sendto(packet, (MCAST_GRP, UDP_PORT))
else:
raise RuntimeError(
'Test has failed: did not receive mdns answer within timeout')
@pytest.mark.skip
def test_query_dns_sub_service(sub_service): # type: (str) -> None
print('PTR: Query {}'.format(sub_service))
sock = create_socket()
packet = get_mdns_sub_service_query(sub_service)
for _ in range(RETRY_COUNT):
if esp_sub_service_answered.wait(timeout=25):
break
sock.sendto(packet, (MCAST_GRP, UDP_PORT))
else:
raise RuntimeError(
'Test has failed: did not receive mdns answer within timeout')
@pytest.mark.skip
def test_query_dns_host(esp_host): # type: (str) -> None
print('A: {}'.format(esp_host))
sock = create_socket()
packet = get_dns_query_for_esp(esp_host)
for _ in range(RETRY_COUNT):
if esp_host_answered.wait(timeout=25):
break
sock.sendto(packet, (MCAST_GRP, UDP_PORT))
else:
raise RuntimeError(
'Test has failed: did not receive mdns answer within timeout')
@pytest.mark.skip
def test_query_dns_host_delegated(esp_host): # type: (str) -> None
print('A: {}'.format(esp_host))
sock = create_socket()
packet = get_dns_query_for_esp(esp_host + '-delegated')
for _ in range(RETRY_COUNT):
if esp_delegated_host_answered.wait(timeout=25):
break
sock.sendto(packet, (MCAST_GRP, UDP_PORT))
else:
raise RuntimeError(
'Test has failed: did not receive mdns answer within timeout')
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.esp32c3
@pytest.mark.generic
def test_app_esp_mdns(dut):
# 1. get the dut host name (and IP address)
specific_host = dut.expect(
re.compile(b'mdns hostname set to: \[([^\]]+)\]'), # noqa: W605
timeout=30).group(1).decode()
esp_ip = dut.expect(
re.compile(
b' IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)'), # noqa: W605
timeout=30).group(1).decode()
print('Got IP={}'.format(esp_ip))
mdns_responder = Thread(target=mdns_listener, args=(str(specific_host), ))
def start_case(case, desc, result): # type: (str, str, str) -> None
print('Starting {}: {}'.format(case, desc))
dut.write(case)
res = bytes(result, encoding='utf8')
dut.expect(re.compile(res), timeout=30)
try:
# start dns listener thread
mdns_responder.start()
# wait untill mdns listener thred started
if not start_mdns_listener.wait(timeout=5):
raise ValueError(
'Test has failed: mdns listener thread did not start')
# query dns service from host, answer should be received from esp board
test_query_dns_http_service(SERVICE_NAME)
# query dns sub-service from host, answer should be received from esp board
test_query_dns_sub_service(SUB_SERVICE_NAME)
# query dns host name, answer should be received from esp board
test_query_dns_host(specific_host)
# query dns host name delegated, answer should be received from esp board
test_query_dns_host_delegated(specific_host)
# query service from esp board, answer should be received from host
start_case('CONFIG_TEST_QUERY_SERVICE',
'Query SRV ESP32._http._tcp.local', 'SRV:ESP32')
# query dns-host from esp board, answer should be received from host
start_case('CONFIG_TEST_QUERY_HOST', 'Query tinytester.local',
'tinytester.local resolved to: 127.0.0.1')
# query dns-host aynchrounusely from esp board, answer should be received from host
start_case('CONFIG_TEST_QUERY_HOST_ASYNC',
'Query tinytester.local async',
'Async query resolved to A:127.0.0.1')
finally:
stop_mdns_listener.set()
mdns_responder.join()

View File

@@ -0,0 +1,7 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS ../.. "$ENV{IDF_PATH}/tools/unit-test-app/components")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mdns_test)

View File

@@ -0,0 +1,5 @@
idf_component_register(SRCS "test_mdns.c"
REQUIRES test_utils
INCLUDE_DIRS "."
PRIV_REQUIRES unity mdns)

View File

@@ -0,0 +1,299 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "mdns.h"
#include "esp_event.h"
#include "unity.h"
#include "test_utils.h"
#include "unity_fixture.h"
#include "memory_checks.h"
#define MDNS_HOSTNAME "test-hostname"
#define MDNS_DELEGATE_HOSTNAME "delegate-hostname"
#define MDNS_INSTANCE "test-instance"
#define MDNS_SERVICE_NAME "_http"
#define MDNS_SERVICE_PROTO "_tcp"
#define MDNS_SERVICE_PORT 80
TEST_GROUP(mdns);
TEST_SETUP(mdns)
{
test_utils_record_free_mem();
TEST_ESP_OK(test_utils_set_leak_level(0, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL));
}
TEST_TEAR_DOWN(mdns)
{
test_utils_finish_and_evaluate_leaks(32, 64);
}
static void yield_to_all_priorities(void)
{
// Lower the test-task priority before testing to ensure other tasks got executed on forced context switch
size_t test_task_prio_before = uxTaskPriorityGet(NULL);
vTaskPrioritySet(NULL, tskIDLE_PRIORITY);
taskYIELD(); // Let the RTOS to switch context
vTaskPrioritySet(NULL, test_task_prio_before);
}
TEST(mdns, api_fails_with_invalid_state)
{
TEST_ASSERT_NOT_EQUAL(ESP_OK, mdns_init());
TEST_ASSERT_NOT_EQUAL(ESP_OK, mdns_hostname_set(MDNS_HOSTNAME));
TEST_ASSERT_NOT_EQUAL(ESP_OK, mdns_instance_name_set(MDNS_INSTANCE));
TEST_ASSERT_NOT_EQUAL(ESP_OK, mdns_service_add(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT, NULL, 0));
}
TEST(mdns, init_deinit)
{
test_case_uses_tcpip();
TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create_default());
TEST_ASSERT_EQUAL(ESP_OK, mdns_init());
yield_to_all_priorities(); // Make sure that mdns task has executed to complete initialization
mdns_free();
esp_event_loop_delete_default();
}
TEST(mdns, api_fails_with_expected_err)
{
mdns_txt_item_t serviceTxtData[CONFIG_MDNS_MAX_SERVICES] = { {NULL, NULL},
};
mdns_ip_addr_t addr;
addr.addr.type = ESP_IPADDR_TYPE_V4;
addr.addr.u_addr.ip4.addr = esp_ip4addr_aton("127.0.0.1");
addr.next = NULL;
for (int i = 0; i < CONFIG_MDNS_MAX_SERVICES; ++i) {
serviceTxtData[i].key = "Key";
serviceTxtData[i].value = "Value";
}
test_case_uses_tcpip();
TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create_default());
TEST_ASSERT_EQUAL(ESP_OK, mdns_init());
TEST_ASSERT_EQUAL(ESP_OK, mdns_hostname_set(MDNS_HOSTNAME));
TEST_ASSERT_EQUAL(ESP_OK, mdns_delegate_hostname_add(MDNS_DELEGATE_HOSTNAME, &addr));
yield_to_all_priorities(); // Make sure that mdns task has executed to add the hostname
TEST_ASSERT_TRUE(mdns_hostname_exists(MDNS_DELEGATE_HOSTNAME));
TEST_ASSERT_EQUAL(ESP_OK, mdns_instance_name_set(MDNS_INSTANCE));
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_add(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT, serviceTxtData, CONFIG_MDNS_MAX_SERVICES));
TEST_ASSERT_FALSE(mdns_service_exists(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_DELEGATE_HOSTNAME));
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_add_for_host(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_DELEGATE_HOSTNAME,
MDNS_SERVICE_PORT, serviceTxtData, CONFIG_MDNS_MAX_SERVICES));
TEST_ASSERT_TRUE(mdns_service_exists(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_DELEGATE_HOSTNAME));
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_txt_set(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, serviceTxtData, CONFIG_MDNS_MAX_SERVICES));
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_txt_item_set(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, "key1", "value1"));
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_txt_item_remove(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, "key1"));
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_port_set(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 8080));
yield_to_all_priorities(); // to remove the service with the updated txt records
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_remove(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO));
yield_to_all_priorities(); // Make sure that mdns task has executed to remove the service
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_add(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT, NULL, 0));
TEST_ASSERT_EQUAL(ESP_OK, mdns_delegate_hostname_remove(MDNS_DELEGATE_HOSTNAME));
yield_to_all_priorities(); // Make sure that mdns task has executed to remove the hostname
TEST_ASSERT_FALSE(mdns_service_exists(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_DELEGATE_HOSTNAME));
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_remove_all());
yield_to_all_priorities(); // Make sure that mdns task has executed to remove all services
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, mdns_service_port_set(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 8080));
mdns_free();
esp_event_loop_delete_default();
}
TEST(mdns, query_api_fails_with_expected_err)
{
mdns_result_t *results = NULL;
esp_ip6_addr_t addr6;
esp_ip4_addr_t addr4;
test_case_uses_tcpip();
TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create_default());
TEST_ASSERT_EQUAL(ESP_OK, mdns_init());
// check it is not possible to register a service or set an instance without configuring the hostname
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, mdns_service_add(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT, NULL, 0));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, mdns_instance_name_set(MDNS_INSTANCE));
TEST_ASSERT_EQUAL(ESP_OK, mdns_hostname_set(MDNS_HOSTNAME));
// hostname is set, now adding a service and instance should succeed
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_add(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT, NULL, 0));
TEST_ASSERT_EQUAL(ESP_OK, mdns_instance_name_set(MDNS_INSTANCE));
TEST_ASSERT_EQUAL(ESP_OK, mdns_query_ptr(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 10, CONFIG_MDNS_MAX_SERVICES, &results));
mdns_query_results_free(results);
TEST_ASSERT_EQUAL(ESP_OK, mdns_query_srv(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 10, &results));
mdns_query_results_free(results);
TEST_ASSERT_EQUAL(ESP_OK, mdns_query_txt(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 10, &results));
mdns_query_results_free(results);
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, mdns_query_a(MDNS_HOSTNAME, 10, &addr4));
mdns_query_results_free(results);
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, mdns_query_aaaa(MDNS_HOSTNAME, 10, &addr6));
mdns_query_results_free(results);
mdns_free();
esp_event_loop_delete_default();
}
TEST(mdns, add_remove_service)
{
mdns_result_t *results = NULL;
test_case_uses_tcpip();
TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create_default());
TEST_ASSERT_EQUAL(ESP_OK, mdns_init());
TEST_ASSERT_EQUAL(ESP_OK, mdns_hostname_set(MDNS_HOSTNAME));
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_add(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT, NULL, 0));
yield_to_all_priorities(); // Make sure that mdns task has executed to add the hostname
TEST_ASSERT_EQUAL(ESP_OK, mdns_lookup_selfhosted_service(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 1, &results));
TEST_ASSERT_NOT_EQUAL(NULL, results);
TEST_ASSERT_EQUAL_STRING(MDNS_INSTANCE, results->instance_name);
TEST_ASSERT_EQUAL_STRING(MDNS_SERVICE_NAME, results->service_type);
TEST_ASSERT_EQUAL(MDNS_SERVICE_PORT, results->port);
TEST_ASSERT_EQUAL(NULL, results->txt);
mdns_query_results_free(results);
// Update service properties: port
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_port_set(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT + 1));
yield_to_all_priorities(); // Make sure that mdns task has executed to add the hostname
TEST_ASSERT_EQUAL(ESP_OK, mdns_lookup_selfhosted_service(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 1, &results));
TEST_ASSERT_NOT_EQUAL(NULL, results);
TEST_ASSERT_EQUAL(MDNS_SERVICE_PORT + 1, results->port);
mdns_query_results_free(results);
// Update service properties: instance
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_instance_name_set(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_INSTANCE "1"));
yield_to_all_priorities(); // Make sure that mdns task has executed to add the hostname
TEST_ASSERT_EQUAL(ESP_OK, mdns_lookup_selfhosted_service(MDNS_INSTANCE "1", MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 1, &results));
TEST_ASSERT_NOT_EQUAL(NULL, results);
TEST_ASSERT_EQUAL_STRING(MDNS_INSTANCE "1", results->instance_name);
mdns_query_results_free(results);
// Update service properties: txt
mdns_txt_item_t txt_data[] = {
{"key1", "esp32"},
{"key2", "value"},
{"key3", "value3"},
};
const size_t txt_data_cout = sizeof(txt_data) / sizeof(txt_data[0]);
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_txt_set(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, txt_data, txt_data_cout));
yield_to_all_priorities(); // Make sure that mdns task has executed to add the hostname
TEST_ASSERT_EQUAL(ESP_OK, mdns_lookup_selfhosted_service(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 1, &results));
TEST_ASSERT_NOT_EQUAL(NULL, results);
TEST_ASSERT_NOT_EQUAL(NULL, results->txt);
TEST_ASSERT_EQUAL(txt_data_cout, results->txt_count);
// compare txt values by keys
size_t matches = 0;
for (int i = 0; i < results->txt_count; ++i) // iterates over the results we get from mdns_lookup()
for (int j = 0; j < txt_data_cout; ++j) // iterates over our test records
if (strcmp(results->txt[i].key, txt_data[j].key) == 0) { // we compare the value only if the key matches
TEST_ASSERT_EQUAL_STRING(results->txt[i].value, txt_data[j].value);
++matches;
}
TEST_ASSERT_EQUAL(txt_data_cout, matches); // checks that we went over all our txt items
mdns_query_results_free(results);
// Now remove the service
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_remove(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO));
yield_to_all_priorities(); // Make sure that mdns task has executed to add the hostname
TEST_ASSERT_EQUAL(ESP_OK, mdns_lookup_selfhosted_service(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 1, &results));
TEST_ASSERT_EQUAL(NULL, results);
mdns_free();
esp_event_loop_delete_default();
}
TEST(mdns, add_remove_deleg_service)
{
mdns_ip_addr_t addr;
addr.addr.type = ESP_IPADDR_TYPE_V4;
addr.addr.u_addr.ip4.addr = esp_ip4addr_aton("127.0.0.1");
addr.next = NULL;
mdns_result_t *results = NULL;
test_case_uses_tcpip();
TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create_default());
TEST_ASSERT_EQUAL(ESP_OK, mdns_init());
TEST_ASSERT_EQUAL(ESP_OK, mdns_hostname_set(MDNS_HOSTNAME));
TEST_ASSERT_EQUAL(ESP_OK, mdns_delegate_hostname_add(MDNS_DELEGATE_HOSTNAME, &addr));
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_add_for_host(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_DELEGATE_HOSTNAME, MDNS_SERVICE_PORT, NULL, 0));
yield_to_all_priorities(); // Make sure that mdns task has executed to add the hostname
TEST_ASSERT_EQUAL(ESP_OK, mdns_lookup_delegated_service(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 1, &results));
TEST_ASSERT_NOT_EQUAL(NULL, results);
TEST_ASSERT_EQUAL_STRING(MDNS_INSTANCE, results->instance_name);
TEST_ASSERT_EQUAL_STRING(MDNS_SERVICE_NAME, results->service_type);
TEST_ASSERT_EQUAL(MDNS_SERVICE_PORT, results->port);
TEST_ASSERT_EQUAL(NULL, results->txt);
mdns_query_results_free(results);
// Update service properties: port
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_port_set_for_host(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_DELEGATE_HOSTNAME, MDNS_SERVICE_PORT + 1));
yield_to_all_priorities(); // Make sure that mdns task has executed to add the hostname
TEST_ASSERT_EQUAL(ESP_OK, mdns_lookup_delegated_service(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 1, &results));
TEST_ASSERT_NOT_EQUAL(NULL, results);
TEST_ASSERT_EQUAL(MDNS_SERVICE_PORT + 1, results->port);
mdns_query_results_free(results);
// Update service properties: instance
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_instance_name_set_for_host(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_DELEGATE_HOSTNAME, MDNS_INSTANCE "1"));
yield_to_all_priorities(); // Make sure that mdns task has executed to add the hostname
TEST_ASSERT_EQUAL(ESP_OK, mdns_lookup_delegated_service(MDNS_INSTANCE "1", MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 1, &results));
TEST_ASSERT_NOT_EQUAL(NULL, results);
TEST_ASSERT_EQUAL_STRING(MDNS_INSTANCE "1", results->instance_name);
mdns_query_results_free(results);
// Update service properties: txt
mdns_txt_item_t txt_data[] = {
{"key1", "esp32"},
{"key2", "value"},
};
const size_t txt_data_cout = sizeof(txt_data) / sizeof(txt_data[0]);
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_txt_set_for_host(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_DELEGATE_HOSTNAME, txt_data, txt_data_cout));
yield_to_all_priorities(); // Make sure that mdns task has executed to add the hostname
TEST_ASSERT_EQUAL(ESP_OK, mdns_lookup_delegated_service(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 1, &results));
TEST_ASSERT_NOT_EQUAL(NULL, results);
TEST_ASSERT_NOT_EQUAL(NULL, results->txt);
TEST_ASSERT_EQUAL(txt_data_cout, results->txt_count);
// compare txt values by keys
size_t matches = 0;
for (int i = 0; i < results->txt_count; ++i) // iterates over the results we get from mdns_lookup()
for (int j = 0; j < txt_data_cout; ++j) // iterates over our test records
if (strcmp(results->txt[i].key, txt_data[j].key) == 0) { // we compare the value only if the key matches
TEST_ASSERT_EQUAL_STRING(results->txt[i].value, txt_data[j].value);
++matches;
}
TEST_ASSERT_EQUAL(txt_data_cout, matches); // checks that we went over all our txt items
mdns_query_results_free(results);
// Now remove the service
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_remove_for_host(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_DELEGATE_HOSTNAME));
yield_to_all_priorities(); // Make sure that mdns task has executed to add the hostname
TEST_ASSERT_EQUAL(ESP_OK, mdns_lookup_delegated_service(NULL, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 1, &results));
TEST_ASSERT_EQUAL(NULL, results);
mdns_free();
esp_event_loop_delete_default();
}
TEST_GROUP_RUNNER(mdns)
{
RUN_TEST_CASE(mdns, api_fails_with_invalid_state)
RUN_TEST_CASE(mdns, api_fails_with_expected_err)
RUN_TEST_CASE(mdns, query_api_fails_with_expected_err)
RUN_TEST_CASE(mdns, init_deinit)
RUN_TEST_CASE(mdns, add_remove_service)
RUN_TEST_CASE(mdns, add_remove_deleg_service)
}
void app_main(void)
{
UNITY_MAIN(mdns);
}

View File

@@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from pytest_embedded import Dut
def test_mdns(dut: Dut) -> None:
dut.expect_unity_test_output()