Skip to content

Commit 0bd74cb

Browse files
committed
fix(mdns): Refactor fuzzer test suite
1 parent 2b15776 commit 0bd74cb

File tree

15 files changed

+1651
-9
lines changed

15 files changed

+1651
-9
lines changed

.github/workflows/mdns__host-tests.yml

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,11 @@ jobs:
5555
shell: bash
5656
run: |
5757
. ${IDF_PATH}/export.sh
58-
cd components/mdns/tests/test_afl_fuzz_host/
59-
make INSTR=off
58+
cd components/mdns/tests/host_unit_test/
59+
idf.py reconfigure
60+
mkdir build2 && cd build2
61+
cmake ..
62+
cmake --build .
6063
- name: Test no malloc functions
6164
shell: bash
6265
run: |
@@ -99,13 +102,28 @@ jobs:
99102
shell: bash
100103
run: |
101104
export IDF_PATH=$GITHUB_WORKSPACE/idf
102-
cd components/mdns/tests/test_afl_fuzz_host/
103-
make fuzz
105+
cd components/mdns/tests/host_unit_test/
106+
pip install dnslib
107+
cd input && python generate_cases.py && cd ..
108+
mkdir build2 && cd build2
109+
cmake -DCMAKE_C_COMPILER=afl-cc ..
110+
cmake --build .
111+
cd ..
112+
timeout 10m afl-fuzz -i input -o out -- build2/mdns_host_unit_test || \
113+
if [ $? -eq 124 ]; then # timeout exit code
114+
if [ -n "$(find out/default/crashes -type f 2>/dev/null)" ]; then
115+
echo "Crashes found!";
116+
tar -czf out/default/crashes.tar.gz -C out/default crashes;
117+
exit 1;
118+
fi
119+
else
120+
exit 1;
121+
fi
104122
105123
- name: Upload Crash Artifacts
106124
if: failure()
107125
uses: actions/upload-artifact@v4
108126
with:
109127
name: fuzz-crashes
110-
path: components/mdns/tests/test_afl_fuzz_host/out/default/crashes.tar.gz
128+
path: components/mdns/tests/host_unit_test/out/default/crashes.tar.gz
111129
if-no-files-found: ignore

components/mdns/mdns_responder.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const char *mdns_priv_get_global_hostname(void)
6868

6969
mdns_srv_item_t *mdns_priv_get_services(void)
7070
{
71-
return s_mdns_server->services;
71+
return s_mdns_server ? s_mdns_server->services : NULL;
7272
}
7373

7474
mdns_host_item_t *mdns_priv_get_hosts(void)

components/mdns/private_include/mdns_responder.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ void mdns_priv_free_delegated_hostnames(void);
9898
*/
9999
void mdns_priv_remap_self_service_hostname(const char *old_hostname, const char *new_hostname);
100100

101-
102101
#ifdef __cplusplus
103102
}
104103
#endif
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
if(ESP_PLATFORM)
3+
set(EXTRA_COMPONENT_DIRS ../../)
4+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
5+
project(mdns_host_unit_test_config)
6+
return()
7+
endif ()
8+
9+
project(mdns_host_unit_test C)
10+
11+
# Set variables for directories
12+
set(TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR})
13+
set(COMPONENT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../)
14+
set(ESP_NETIF_LINUX_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../host_test/components/esp_netif_linux/include)
15+
set(COMMON_COMPONENTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../common_components/linux_compat/)
16+
set(IDF_COMPONENTS_DIR "$ENV{IDF_PATH}/components")
17+
18+
# Debug prints to see directory values
19+
message(STATUS "TEST_DIR: ${TEST_DIR}")
20+
message(STATUS "COMPONENT_DIR: ${COMPONENT_DIR}")
21+
22+
# Include directories for the test files and other required files
23+
include_directories(
24+
${TEST_DIR}
25+
${TEST_DIR}/stubs
26+
${TEST_DIR}/build/config
27+
${COMMON_COMPONENTS_DIR}/freertos/include
28+
${COMMON_COMPONENTS_DIR}/esp_timer/include
29+
${IDF_COMPONENTS_DIR}/esp_event/include
30+
${IDF_COMPONENTS_DIR}/esp_netif/include
31+
${IDF_COMPONENTS_DIR}/esp_common/include
32+
${IDF_COMPONENTS_DIR}/esp_system/include
33+
${IDF_COMPONENTS_DIR}/log/include
34+
${IDF_COMPONENTS_DIR}/esp_rom/include
35+
${IDF_COMPONENTS_DIR}/heap/include
36+
${IDF_COMPONENTS_DIR}/esp_rom/linux/include/linux/
37+
${ESP_NETIF_LINUX_DIR}
38+
${COMPONENT_DIR}
39+
${COMPONENT_DIR}/include
40+
${COMPONENT_DIR}/private_include
41+
)
42+
43+
# Unity testing framework
44+
set(UNITY_DIR "$ENV{IDF_PATH}/components/unity")
45+
include_directories(${UNITY_DIR}/unity/src)
46+
47+
# Source files
48+
set(SOURCES
49+
${CMAKE_CURRENT_SOURCE_DIR}/main.c
50+
${CMAKE_CURRENT_SOURCE_DIR}/stubs/mdns_mem_caps.c
51+
${CMAKE_CURRENT_SOURCE_DIR}/stubs/esp_idf.c
52+
${CMAKE_CURRENT_SOURCE_DIR}/stubs/mdns_networking.c
53+
${CMAKE_CURRENT_SOURCE_DIR}/stubs/mdns_engine.c
54+
${CMAKE_CURRENT_SOURCE_DIR}/../../mdns_receive.c
55+
${CMAKE_CURRENT_SOURCE_DIR}/../../mdns_utils.c
56+
${CMAKE_CURRENT_SOURCE_DIR}/../../mdns_browser.c
57+
${CMAKE_CURRENT_SOURCE_DIR}/../../mdns_querier.c
58+
${CMAKE_CURRENT_SOURCE_DIR}/../../mdns_responder.c
59+
${CMAKE_CURRENT_SOURCE_DIR}/../../mdns_send.c
60+
${CMAKE_CURRENT_SOURCE_DIR}/../../mdns_pcb.c
61+
${CMAKE_CURRENT_SOURCE_DIR}/../../mdns_netif.c
62+
${UNITY_DIR}/unity/src/unity.c
63+
)
64+
65+
# Enable sanitizers
66+
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
67+
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
68+
69+
# Setting C flags with debug symbols and disable optimization for better debugging
70+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0")
71+
72+
# Create the test executable
73+
add_executable(${PROJECT_NAME} ${SOURCES})
74+
75+
find_library(LIB_BSD bsd)
76+
if(LIB_BSD)
77+
target_link_libraries(${PROJECT_NAME} PRIVATE ${LIB_BSD})
78+
elseif(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
79+
message(WARNING "Missing LIBBSD library. Install libbsd-dev package and/or check linker directories.")
80+
endif()
81+
# Setting C flags
82+
#set_target_properties(${PROJECT_NAME} PROPERTIES
83+
# C_STANDARD 99
84+
# COMPILE_FLAGS "-Wall -Werror"
85+
#)
86+
87+
# Test command with ASAN options
88+
#enable_testing()
89+
#add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME})
90+
#set_tests_properties(${PROJECT_NAME} PROPERTIES
91+
# ENVIRONMENT "ASAN_OPTIONS=detect_leaks=1:verbosity=1:log_path=asan.log:print_stats=1:malloc_context_size=20"
92+
#)
93+
94+
# Add libbsd dependency
95+
#find_package(PkgConfig REQUIRED)
96+
#pkg_check_modules(LIBBSD REQUIRED libbsd-overlay)
97+
98+
# Link against libbsd for strlcat
99+
#target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBBSD_LIBRARIES})
100+
#target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=address -fsanitize=undefined)
101+
#target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=address -fsanitize=undefined)
102+
103+
# Add include directories if needed
104+
#target_include_directories(${PROJECT_NAME} PRIVATE ${LIBBSD_INCLUDE_DIRS})
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Unlicense OR CC0-1.0
3+
from dnslib import (AAAA, PTR, RR, SRV, TXT, A, DNSHeader, DNSQuestion,
4+
DNSRecord)
5+
6+
7+
def create_mdns_packet(queries, answers, additional, output_file='mdns_packet.bin'):
8+
dns_header = DNSHeader(id=0, qr=1, aa=1, ra=0)
9+
dns_record = DNSRecord(dns_header)
10+
for qname, qtype in queries:
11+
dns_record.add_question(DNSQuestion(qname, qtype))
12+
for name, qtype, value, ttl in answers:
13+
if qtype == 'A':
14+
rr = RR(name, rdata=A(value), ttl=ttl)
15+
elif qtype == 'PTR':
16+
rr = RR(name, rdata=PTR(value), ttl=ttl)
17+
elif qtype == 'TXT':
18+
rr = RR(name, rdata=TXT(value), ttl=ttl)
19+
elif qtype == 'AAAA':
20+
rr = RR(name, rdata=AAAA(value), ttl=ttl)
21+
elif qtype == 'SRV':
22+
# SRV value should be in format: "priority weight port target"
23+
parts = value.split()
24+
priority, weight, port = map(int, parts[:3])
25+
target = parts[3]
26+
rr = RR(name, rdata=SRV(priority, weight, port, target), ttl=ttl)
27+
else:
28+
print(f'Unsupported type: {qtype}')
29+
continue
30+
dns_record.add_answer(rr)
31+
for name, qtype, value, ttl in additional:
32+
if qtype == 'A':
33+
rr = RR(name, rdata=A(value), ttl=ttl)
34+
elif qtype == 'TXT':
35+
rr = RR(name, rdata=TXT(value), ttl=ttl)
36+
elif qtype == 'AAAA':
37+
rr = RR(name, rdata=AAAA(value), ttl=ttl)
38+
elif qtype == 'SRV':
39+
parts = value.split()
40+
priority, weight, port = map(int, parts[:3])
41+
target = parts[3]
42+
rr = RR(name, rdata=SRV(priority, weight, port, target), ttl=ttl)
43+
else:
44+
print(f'Unsupported type: {qtype}')
45+
continue
46+
dns_record.add_ar(rr)
47+
mdns_payload = dns_record.pack()
48+
49+
with open(output_file, 'wb') as f:
50+
f.write(mdns_payload)
51+
52+
print(f'mDNS packet saved as {output_file}')
53+
54+
55+
# Test case 1: Basic hostname queries
56+
queries = [
57+
('test.local.', 1), # A record for main hostname
58+
('test2.local.', 1), # A record for instance name
59+
('test3.local.', 1), # A record for delegate hostname
60+
('test4.local.', 28), # AAAA record for delegate hostname
61+
]
62+
answers = [
63+
('test.local.', 'A', '192.168.1.100', 120),
64+
('test2.local.', 'A', '192.168.1.101', 120),
65+
('test3.local.', 'A', '192.168.1.102', 120),
66+
('test4.local.', 'AAAA', 'fe80::1', 120),
67+
]
68+
additional = [
69+
('test.local.', 'TXT', 'board=esp32', 120),
70+
]
71+
create_mdns_packet(queries, answers, additional, 'test_hostname_queries.bin')
72+
print('Test case 1: Hostname queries')
73+
74+
# Test case 2: HTTP service discovery with subtypes
75+
queries = [
76+
('_http._tcp.local.', 12), # PTR query
77+
('subtype._sub._http._tcp.local.', 12), # Subtype query
78+
]
79+
answers = [
80+
('_http._tcp.local.', 'PTR', 'inst1._http._tcp.local.', 120),
81+
('_http._tcp.local.', 'PTR', 'inst2._http._tcp.local.', 120),
82+
('subtype._sub._http._tcp.local.', 'PTR', 'inst1._http._tcp.local.', 120),
83+
]
84+
additional = [
85+
('inst1._http._tcp.local.', 'SRV', '0 0 80 test.local.', 120),
86+
('inst1._http._tcp.local.', 'TXT', 'board=esp32\0tcp_check=no\0ssh_upload=no\0auth_upload=no', 120),
87+
('inst2._http._tcp.local.', 'SRV', '0 0 80 test.local.', 120),
88+
('inst2._http._tcp.local.', 'TXT', 'board=esp32\0tcp_check=no\0ssh_upload=no\0auth_upload=no', 120),
89+
]
90+
create_mdns_packet(queries, answers, additional, 'test_http_services.bin')
91+
print('Test case 2: HTTP service discovery with subtypes')
92+
93+
# Test case 3: Scanner service discovery
94+
queries = [
95+
('_scanner._tcp.local.', 12), # PTR query
96+
]
97+
answers = [
98+
('_scanner._tcp.local.', 'PTR', 'inst4._scanner._tcp.local.', 120),
99+
('_scanner._tcp.local.', 'PTR', 'inst5._scanner._tcp.local.', 120),
100+
]
101+
additional = [
102+
('inst4._scanner._tcp.local.', 'SRV', '0 0 80 test.local.', 120),
103+
('inst5._scanner._tcp.local.', 'SRV', '0 0 80 test.local.', 120),
104+
]
105+
create_mdns_packet(queries, answers, additional, 'test_scanner_services.bin')
106+
print('Test case 3: Scanner service discovery')
107+
108+
# Test case 4: UDP service (sleep protocol)
109+
queries = [
110+
('_sleep._udp.local.', 12), # PTR query
111+
]
112+
answers = [
113+
('_sleep._udp.local.', 'PTR', 'inst7._sleep._udp.local.', 120),
114+
]
115+
additional = [
116+
('inst7._sleep._udp.local.', 'SRV', '0 0 80 test.local.', 120),
117+
]
118+
create_mdns_packet(queries, answers, additional, 'test_udp_service.bin')
119+
print('Test case 4: UDP service discovery')
120+
121+
# Test case 5: Async queries from main.c
122+
queries = [
123+
('host_name.local.', 1), # A query
124+
('host_name2.local.', 28), # AAAA query
125+
('minifritz._http._tcp.local.', 12), # PTR query
126+
]
127+
answers = [
128+
('host_name.local.', 'A', '192.168.1.10', 120),
129+
('host_name2.local.', 'AAAA', 'fe80::1234', 120),
130+
('minifritz._http._tcp.local.', 'PTR', 'minifritz._http._tcp.local.', 120),
131+
]
132+
additional = [
133+
('minifritz._http._tcp.local.', 'SRV', '0 0 80 minifritz.local.', 120),
134+
('minifritz.local.', 'A', '192.168.1.20', 120),
135+
]
136+
create_mdns_packet(queries, answers, additional, 'test_async_queries.bin')
137+
print('Test case 5: Async queries')
138+
139+
# Test case 6: Multiple services on same instance
140+
queries = [
141+
('_http._tcp.local.', 12),
142+
('_scanner._tcp.local.', 12),
143+
]
144+
answers = [
145+
('_http._tcp.local.', 'PTR', 'inst1._http._tcp.local.', 120),
146+
('_http._tcp.local.', 'PTR', 'inst2._http._tcp.local.', 120),
147+
('_http._tcp.local.', 'PTR', 'inst3._http._tcp.local.', 120),
148+
('_scanner._tcp.local.', 'PTR', 'inst4._scanner._tcp.local.', 120),
149+
]
150+
additional = [
151+
('inst1._http._tcp.local.', 'SRV', '0 0 80 test.local.', 120),
152+
('inst2._http._tcp.local.', 'SRV', '0 0 80 test.local.', 120),
153+
('test.local.', 'A', '192.168.1.100', 120),
154+
]
155+
create_mdns_packet(queries, answers, additional, 'test_multiple_services.bin')
156+
print('Test case 6: Multiple services')
157+
158+
# Test case 7: Subtype queries for different hosts
159+
queries = [
160+
('subtype._sub._http._tcp.local.', 12),
161+
('subtype3._sub._http._tcp.local.', 12),
162+
]
163+
answers = [
164+
('subtype._sub._http._tcp.local.', 'PTR', 'inst1._http._tcp.local.', 120),
165+
('subtype3._sub._http._tcp.local.', 'PTR', 'inst2._http._tcp.local.', 120),
166+
]
167+
additional = [
168+
('inst1._http._tcp.local.', 'SRV', '0 0 80 test.local.', 120),
169+
('inst2._http._tcp.local.', 'SRV', '0 0 80 test.local.', 120),
170+
('test.local.', 'A', '192.168.1.100', 120),
171+
]
172+
create_mdns_packet(queries, answers, additional, 'test_subtype_queries.bin')
173+
print('Test case 7: Subtype queries')

0 commit comments

Comments
 (0)