Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/mdns__build-target-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ jobs:
shell: bash
run: |
. ${IDF_PATH}/export.sh
if [[ "${{ matrix.idf_ver }}" == "latest" ]]; then
export EXPECTED_WARNING="warning: unknown kconfig symbol 'EXAMPLE_ETH_PHY_IP101'"
else
export EXPECTED_WARNING="warning: unknown kconfig symbol 'EXAMPLE_ETH_PHY_GENERIC'"
fi
python -m pip install idf-build-apps
# Build default configs for all targets
python ./ci/build_apps.py components/mdns/${{ matrix.test.path }} -r default -d
Expand Down Expand Up @@ -71,6 +76,8 @@ jobs:
- name: Run ${{ matrix.test.app }} application on ${{ matrix.idf_target }}
working-directory: components/mdns/${{ matrix.test.path }}
run: |
# python -m pip install pytest pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf --upgrade
pip install --prefer-binary cryptography pytest-embedded pytest-embedded-qemu pytest-embedded-serial-esp pytest-embedded-idf pytest-custom_exit_code --upgrade
unzip ci/artifacts.zip -d ci
for dir in `ls -d ci/build_*`; do
rm -rf build sdkconfig.defaults
Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/mdns__host-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,43 @@ jobs:
diff -q $file /tmp/$file || exit 1
echo "OK"
done

fuzz_test:
if: contains(github.event.pull_request.labels.*.name, 'mdns-fuzz') || github.event_name == 'push'
name: Fuzzer tests for mdns lib
strategy:
matrix:
idf_ver: ["latest"]

runs-on: ubuntu-22.04
container: aflplusplus/aflplusplus
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v4

- name: Checkout ESP-IDF
uses: actions/checkout@v4
with:
repository: espressif/esp-idf
path: idf
submodules: recursive

- name: Install Necessary Libs
run: |
apt-get update -y
apt-get install -y libbsd-dev

- name: Run AFL++
shell: bash
run: |
export IDF_PATH=$GITHUB_WORKSPACE/idf
cd components/mdns/tests/test_afl_fuzz_host/
make fuzz

- name: Upload Crash Artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: fuzz-crashes
path: components/mdns/tests/test_afl_fuzz_host/out/default/crashes.tar.gz
if-no-files-found: ignore
2 changes: 1 addition & 1 deletion ci/build_apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
build_dir='build_@t_@w',
config_rules_str=args.rules,
build_log_filename='build_log.txt',
size_json_filename='size.json' if not args.linux else None,
size_json_filename=None,
check_warnings=True,
manifest_files=args.manifests,
default_build_targets=SUPPORTED_TARGETS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_PHY_GENERIC=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
Expand Down
52 changes: 50 additions & 2 deletions components/mdns/tests/host_test/dnsfixture.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
import re
Expand Down Expand Up @@ -92,10 +92,58 @@ def check_record(self, name, query_type, expected=True, expect=None):
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"
assert any(expect in answer for answer in answers), f"Expected record '{expect}' not in answer section"
else:
assert not any(expect in answer for answer in answers), f"Unexpected record '{expect}' found in answer section"

def parse_section(self, response, section: str, rdtype_text: str):
"""Parse a specific response section (answer, authority, additional) for given rdtype.

Returns list of textual records for that rdtype.
"""
out = []
if not response:
return out
rrsets = []
if section == 'answer':
rrsets = response.answer
elif section == 'authority':
rrsets = response.authority
elif section == 'additional':
rrsets = response.additional
else:
raise ValueError('invalid section')
for rr in rrsets:
if dns.rdatatype.to_text(rr.rdtype) != rdtype_text:
continue
for item in rr.items:
full = (
f'{rr.name} {rr.ttl} '
f'{dns.rdataclass.to_text(rr.rdclass)} '
f'{dns.rdatatype.to_text(rr.rdtype)} '
f'{item.to_text()}'
)
out.append(full)
return out

def check_additional(self, response, rdtype_text: str, owner_contains: str, expected: bool = True, expect_substr: str | None = None):
"""Check Additional section for an RR of type rdtype_text whose owner includes owner_contains.

If expect_substr is provided, also require it to appear in the textual RR.
"""
records = self.parse_section(response, 'additional', rdtype_text)
logger.info(f'additional({rdtype_text}): {records}')

def _matches(line: str) -> bool:
in_owner = owner_contains in line
has_val = (expect_substr in line) if expect_substr else True
return in_owner and has_val
found = any(_matches(r) for r in records)
if expected:
assert found, f"Expected {rdtype_text} for {owner_contains} in Additional not found"
else:
assert not found, f"Unexpected {rdtype_text} for {owner_contains} found in Additional"


if __name__ == '__main__':
if len(sys.argv) < 3:
Expand Down
13 changes: 12 additions & 1 deletion components/mdns/tests/host_test/pytest_mdns.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging

Expand Down Expand Up @@ -65,6 +65,17 @@ def test_add_service(mdns_console, dig_app):
dig_app.check_record('_http._tcp.local', query_type='PTR', expected=True)


def test_ptr_additional_records_for_service(dig_app):
# Query PTR for the service type and ensure SRV/TXT are in Additional (RFC 6763 §12.1)
resp = dig_app.run_query('_http._tcp.local', query_type='PTR')
# Answer section should have at least one PTR to the instance
answers = dig_app.parse_answer_section(resp, 'PTR')
assert any('test_service._http._tcp.local' in a for a in answers)
# Additional section should include SRV and TXT for the same instance
dig_app.check_additional(resp, 'SRV', 'test_service._http._tcp.local', expected=True)
dig_app.check_additional(resp, 'TXT', 'test_service._http._tcp.local', 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')
Expand Down
19 changes: 16 additions & 3 deletions components/mdns/tests/test_afl_fuzz_host/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#INSTR=off
TEST_NAME=test
FUZZ=afl-fuzz
COMPONENTS_DIR=$(IDF_PATH)/components
COMPILER_ICLUDE_DIR=$(shell echo `which xtensa-esp32-elf-gcc | xargs dirname | xargs dirname`/xtensa-esp32-elf)
# Use ESP32 toolchain include path if available, otherwise fall back to system includes for host-based compilation
COMPILER_INCLUDE_DIR=$(shell if command -v xtensa-esp32-elf-gcc >/dev/null 2>&1; then echo `which xtensa-esp32-elf-gcc | xargs dirname | xargs dirname`/xtensa-esp32-elf; else echo /usr; fi)

CFLAGS=-g -Wno-unused-value -Wno-missing-declarations -Wno-pointer-bool-conversion -Wno-macro-redefined -Wno-int-to-void-pointer-cast -DHOOK_MALLOC_FAILED -DESP_EVENT_H_ -D__ESP_LOG_H__ \
-I. -I../.. -I../../include -I../../private_include -I ./build/config \
Expand Down Expand Up @@ -35,7 +37,7 @@ CFLAGS=-g -Wno-unused-value -Wno-missing-declarations -Wno-pointer-bool-conversi
-I$(COMPONENTS_DIR)/xtensa/include \
-I$(COMPONENTS_DIR)/xtensa/esp32/include \
-I$(COMPONENTS_DIR)/esp_hw_support/etm/include \
-I$(COMPILER_ICLUDE_DIR)/include
-I$(COMPILER_INCLUDE_DIR)/include


MDNS_C_DEPENDENCY_INJECTION=-include mdns_di.h
Expand Down Expand Up @@ -77,7 +79,18 @@ $(TEST_NAME): $(OBJECTS)
@$(LD) $(OBJECTS) -o $@ $(LDLIBS)

fuzz: $(TEST_NAME)
@$(FUZZ) -i "in" -o "out" -- ./$(TEST_NAME)
# timeout returns 124 if time limit is reached, original return code otherwise
# pass only if: fuzzing was running smoothly until timeout AND no crash found
@timeout 10m $(FUZZ) -i "in" -o "out" -- ./$(TEST_NAME) || \
if [ $$? -eq 124 ]; then \
if [ -n "$$(find out/default/crashes -type f 2>/dev/null)" ]; then \
echo "Crashes found!"; \
tar -czf out/default/crashes.tar.gz -C out/default crashes; \
exit 1; \
fi \
else \
exit 1; \
fi

clean:
@rm -rf *.o *.SYM $(TEST_NAME) out
9 changes: 6 additions & 3 deletions components/mdns/tests/test_afl_fuzz_host/esp32_mock.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@

#define pdMS_TO_TICKS(a) a
#define xSemaphoreTake(s,d) true
#define xTaskDelete(a)
#define vTaskDelete(a) free(a)
#define vTaskDelete(a) free(NULL)
#define xSemaphoreGive(s)
#define xQueueCreateMutex(s)
#define _mdns_pcb_init(a,b) true
Expand All @@ -66,7 +65,7 @@
#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 xTaskCreateStaticPinnedToCore(a,b,c,d,e,f,g,h) ((void*)1)
#define vTaskDelay(m) usleep((m)*0)
#define esp_random() (rand()%UINT32_MAX)

Expand Down Expand Up @@ -139,4 +138,8 @@ 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);

static inline void xTaskGetStaticBuffers(void *pvTaskBuffer, void *pvStackBuffer, void *pvTaskTCB)
{
}

#endif //_ESP32_COMPAT_H_
19 changes: 3 additions & 16 deletions components/mdns/tests/test_afl_fuzz_host/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,30 +78,20 @@ static int mdns_test_service_txt_set(const char *service, const char *proto, ui
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
return ESP_FAIL;
}
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;
return mdns_service_subtype_add_for_host(NULL, service_name, proto, NULL, sub_name);
}

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
return ESP_FAIL;
}
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;
Expand Down Expand Up @@ -266,9 +256,6 @@ int main(int argc, char **argv)
}
#ifndef MDNS_NO_SERVICES
mdns_service_remove_all();
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
#endif
ForceTaskDelete();
mdns_free();
Expand Down
Loading