Skip to content

Commit 9601432

Browse files
committed
fix(mdns): Debug dig issues
1 parent 6561d50 commit 9601432

File tree

3 files changed

+118
-53
lines changed

3 files changed

+118
-53
lines changed

.github/workflows/mdns__host-tests.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,12 @@ jobs:
3636
export IDF_PATH=`pwd`
3737
./install.sh --enable-pytest
3838
. export.sh
39+
pip install dnspython
3940
cd $GITHUB_WORKSPACE/protocols
4041
python ./ci/build_apps.py components/mdns/tests/host_test/
4142
cd components/mdns/tests/host_test
4243
./build_linux_app/mdns_host.elf &
43-
dig +short -p 5353 @224.0.0.251 myesp.local > ip.txt
44-
cat ip.txt | xargs dig +short -p 5353 @224.0.0.251 -x
45-
cat ip.txt
46-
# run pytest
44+
python dnsfixture.py A myesp.local
4745
pytest
4846
4947
build_afl_host_test_mdns:
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Unlicense OR CC0-1.0
3+
import logging
4+
import socket
5+
import sys
6+
7+
import dns.message
8+
import dns.query
9+
import dns.rdataclass
10+
import dns.rdatatype
11+
import dns.resolver
12+
13+
# Configure logging
14+
logging.basicConfig(level=logging.INFO)
15+
logger = logging.getLogger(__name__)
16+
17+
18+
class DnsPythonWrapper:
19+
def __init__(self, server='224.0.0.251', port=5353, retries=3):
20+
self.server = server
21+
self.port = port
22+
self.retries = retries
23+
24+
def send_and_receive_query(self, query, timeout=3):
25+
logger.info(f'Sending DNS query to {self.server}:{self.port}')
26+
try:
27+
# Create a UDP socket
28+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
29+
sock.settimeout(timeout)
30+
31+
# Send the DNS query
32+
query_data = query.to_wire()
33+
sock.sendto(query_data, (self.server, self.port))
34+
35+
# Receive the DNS response
36+
response_data, _ = sock.recvfrom(512) # 512 bytes is the typical size for a DNS response
37+
38+
# Parse the response
39+
response = dns.message.from_wire(response_data)
40+
41+
return response
42+
43+
except socket.timeout as e:
44+
logger.warning(f'DNS query timed out: {e}')
45+
return None
46+
except dns.exception.DNSException as e:
47+
logger.error(f'DNS query failed: {e}')
48+
return None
49+
50+
def run_query(self, name, query_type='PTR', timeout=3):
51+
logger.info(f'Running DNS query for {name} with type {query_type}')
52+
query = dns.message.make_query(name, dns.rdatatype.from_text(query_type), dns.rdataclass.IN)
53+
54+
# Print the DNS question section
55+
logger.info(f'DNS question section: {query.question}')
56+
57+
# Send and receive the DNS query
58+
response = None
59+
for attempt in range(1, self.retries + 1):
60+
logger.info(f'Attempt {attempt}/{self.retries}')
61+
response = self.send_and_receive_query(query, timeout)
62+
if response:
63+
break
64+
65+
if response:
66+
logger.info(f'DNS query response:\n{response}')
67+
else:
68+
logger.warning('No response received or response was invalid.')
69+
70+
return response
71+
72+
def parse_answer_section(self, response, query_type):
73+
answers = []
74+
if response:
75+
for answer in response.answer:
76+
if dns.rdatatype.to_text(answer.rdtype) == query_type:
77+
for item in answer.items:
78+
full_answer = (
79+
f'{answer.name} {answer.ttl} '
80+
f'{dns.rdataclass.to_text(answer.rdclass)} '
81+
f'{dns.rdatatype.to_text(answer.rdtype)} '
82+
f'{item.to_text()}'
83+
)
84+
answers.append(full_answer)
85+
return answers
86+
87+
def check_record(self, name, query_type, expected=True):
88+
output = self.run_query(name, query_type=query_type)
89+
answers = self.parse_answer_section(output, query_type)
90+
logger.info(f'answers: {answers}')
91+
if expected:
92+
assert any(name in answer for answer in answers), f"Expected service '{name}' not found in answer section"
93+
else:
94+
assert not any(name in answer for answer in answers), f"Unexpected service '{name}' found in answer section"
95+
96+
97+
if __name__ == '__main__':
98+
if len(sys.argv) != 3:
99+
print('Usage: python dns_fixture.py <query_type> <name>')
100+
sys.exit(1)
101+
102+
query_type = sys.argv[1]
103+
name = sys.argv[2]
104+
105+
dns_wrapper = DnsPythonWrapper()
106+
response = dns_wrapper.run_query(name, query_type=query_type)
107+
print(response)
108+
answers = dns_wrapper.parse_answer_section(response, query_type)
109+
110+
if answers:
111+
for answer in answers:
112+
logger.info(f'DNS query response: {answer}')
113+
else:
114+
logger.info(f'No response for {name} with query type {query_type}')

components/mdns/tests/host_test/pytest_mdns.py

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
22
# SPDX-License-Identifier: Unlicense OR CC0-1.0
33
import logging
4-
import subprocess
54

65
import pexpect
76
import pytest
7+
from dnsfixture import DnsPythonWrapper
88

99
# Configure logging
1010
logging.basicConfig(level=logging.INFO)
@@ -37,53 +37,6 @@ def terminate(self):
3737
assert self.process.exitstatus == 0
3838

3939

40-
class DigWrapper:
41-
def __init__(self, server='224.0.0.251', port=5353):
42-
self.server = server
43-
self.port = port
44-
45-
def parse_answer_section(self, output, query_type):
46-
answer_section = False
47-
answers = []
48-
for line in output.splitlines():
49-
if line.startswith(';; ANSWER SECTION:'):
50-
answer_section = True
51-
continue
52-
if answer_section:
53-
if line.startswith(';;'):
54-
break
55-
if query_type in line:
56-
answers.append(line)
57-
return answers
58-
59-
def run_query(self, name, query_type='PTR'):
60-
command = ['dig', '+timeout=3', '+tries=3', query_type, '-p', str(self.port), f'@{self.server}', name]
61-
logger.info(f"Running dig command: {' '.join(command)}")
62-
try:
63-
result = subprocess.run(command, capture_output=True, text=True, timeout=15)
64-
logger.info(f'dig return code: {result.returncode}')
65-
if result.returncode != 0:
66-
logger.info(f'dig command output: {result.stdout}')
67-
return result.stdout
68-
except subprocess.TimeoutExpired as e:
69-
logger.info(f'dig command timed out: {e}')
70-
return ''
71-
except subprocess.CalledProcessError as e:
72-
logger.error(f'dig command failed: {e}')
73-
logger.info(f'Partial output before timeout: {e.stdout}')
74-
return e.stdout or ''
75-
76-
def check_record(self, name, query_type, expected=True):
77-
output = self.run_query(name, query_type=query_type)
78-
answers = self.parse_answer_section(output, query_type)
79-
logger.info(f'dig answers: {answers}')
80-
if expected:
81-
assert any(name in answer for answer in answers), f"Expected service '{name}' not found in answer section"
82-
else:
83-
assert 'timeout' in output
84-
assert not any(name in answer for answer in answers), f"Unexpected service '{name}' found in answer section"
85-
86-
8740
@pytest.fixture(scope='module')
8841
def mdns_console():
8942
app = MdnsConsole('./build_linux_console/mdns_host.elf')
@@ -93,7 +46,7 @@ def mdns_console():
9346

9447
@pytest.fixture(scope='module')
9548
def dig_app():
96-
return DigWrapper()
49+
return DnsPythonWrapper()
9750

9851

9952
def test_mdns_init(mdns_console, dig_app):

0 commit comments

Comments
 (0)