| 
									
										
										
										
											2023-01-18 12:54:06 +01:00
										 |  |  | # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  | # SPDX-License-Identifier: Unlicense OR CC0-1.0 | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  | import re | 
					
						
							| 
									
										
										
										
											2021-05-11 13:47:03 +08:00
										 |  |  | import select | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  | import socket | 
					
						
							|  |  |  | import struct | 
					
						
							| 
									
										
										
										
											2020-07-30 16:12:37 +02:00
										 |  |  | import subprocess | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  | import time | 
					
						
							|  |  |  | from threading import Event, Thread | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-18 12:54:06 +01:00
										 |  |  | try: | 
					
						
							|  |  |  |     import dpkt | 
					
						
							|  |  |  |     import dpkt.dns | 
					
						
							|  |  |  | except ImportError: | 
					
						
							|  |  |  |     pass | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 13:20:58 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | def get_dns_query_for_esp(esp_host): | 
					
						
							|  |  |  |     dns = dpkt.dns.DNS( | 
					
						
							|  |  |  |         b'\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01' | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     dns.qd[0].name = esp_host + u'.local' | 
					
						
							| 
									
										
										
										
											2022-08-19 13:49:32 +04:00
										 |  |  |     print('Created query for esp host: {} '.format(dns.__repr__())) | 
					
						
							| 
									
										
										
										
											2019-06-19 13:20:58 +02:00
										 |  |  |     return dns.pack() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_dns_answer_to_mdns(tester_host): | 
					
						
							|  |  |  |     dns = dpkt.dns.DNS( | 
					
						
							|  |  |  |         b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     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') | 
					
						
							| 
									
										
										
										
											2021-12-14 18:34:04 +01:00
										 |  |  |     dns.an.append(arr) | 
					
						
							| 
									
										
										
										
											2022-08-19 13:49:32 +04:00
										 |  |  |     print('Created answer to mdns query: {} '.format(dns.__repr__())) | 
					
						
							| 
									
										
										
										
											2019-06-19 13:20:58 +02:00
										 |  |  |     return dns.pack() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_dns_answer_to_mdns_lwip(tester_host, id): | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |     dns = dpkt.dns.DNS( | 
					
						
							|  |  |  |         b'\x5e\x39\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x0a\x64\x61\x76\x69\x64' | 
					
						
							|  |  |  |         b'\x2d\x63\x6f\x6d\x70\x05\x6c\x6f\x63\x61\x6c\x00\x00\x01\x00\x01\xc0\x0c' | 
					
						
							|  |  |  |         b'\x00\x01\x00\x01\x00\x00\x00\x0a\x00\x04\xc0\xa8\x0a\x6c') | 
					
						
							| 
									
										
										
										
											2019-06-19 13:20:58 +02:00
										 |  |  |     dns.qd[0].name = tester_host | 
					
						
							|  |  |  |     dns.an[0].name = tester_host | 
					
						
							|  |  |  |     dns.an[0].ip = socket.inet_aton('127.0.0.1') | 
					
						
							|  |  |  |     dns.an[0].rdata = socket.inet_aton('127.0.0.1') | 
					
						
							|  |  |  |     dns.id = id | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |     print('Created answer to mdns (lwip) query: {} '.format(dns.__repr__())) | 
					
						
							| 
									
										
										
										
											2019-06-19 13:20:58 +02:00
										 |  |  |     return dns.pack() | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-04 08:32:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-15 15:48:59 +01:00
										 |  |  | def mdns_server(esp_host, events): | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |     UDP_IP = '0.0.0.0' | 
					
						
							| 
									
										
										
										
											2018-12-04 08:32:48 +01:00
										 |  |  |     UDP_PORT = 5353 | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  |     MCAST_GRP = '224.0.0.251' | 
					
						
							| 
									
										
										
										
											2019-06-19 13:20:58 +02:00
										 |  |  |     TESTER_NAME = u'tinytester.local' | 
					
						
							|  |  |  |     TESTER_NAME_LWIP = u'tinytester-lwip.local' | 
					
						
							| 
									
										
										
										
											2021-05-11 13:47:03 +08:00
										 |  |  |     QUERY_TIMEOUT = 0.2 | 
					
						
							| 
									
										
										
										
											2018-12-04 08:32:48 +01:00
										 |  |  |     sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  |     sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | 
					
						
							|  |  |  |     sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) | 
					
						
							| 
									
										
										
										
											2021-05-11 13:47:03 +08:00
										 |  |  |     sock.setblocking(False) | 
					
						
							| 
									
										
										
										
											2021-12-14 18:34:04 +01:00
										 |  |  |     sock.bind((UDP_IP, UDP_PORT)) | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |     mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY) | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  |     sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) | 
					
						
							| 
									
										
										
										
											2021-05-11 13:47:03 +08:00
										 |  |  |     last_query_timepoint = time.time() | 
					
						
							| 
									
										
										
										
											2021-12-15 15:48:59 +01:00
										 |  |  |     while not events['stop'].is_set(): | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2021-05-11 13:47:03 +08:00
										 |  |  |             current_time = time.time() | 
					
						
							| 
									
										
										
										
											2021-06-30 08:42:09 +02:00
										 |  |  |             if current_time - last_query_timepoint > QUERY_TIMEOUT: | 
					
						
							| 
									
										
										
										
											2021-05-11 13:47:03 +08:00
										 |  |  |                 last_query_timepoint = current_time | 
					
						
							| 
									
										
										
										
											2021-12-15 15:48:59 +01:00
										 |  |  |                 if not events['esp_answered'].is_set(): | 
					
						
							| 
									
										
										
										
											2021-12-14 18:34:04 +01:00
										 |  |  |                     sock.sendto(get_dns_query_for_esp(esp_host), | 
					
						
							|  |  |  |                                 (MCAST_GRP, UDP_PORT)) | 
					
						
							| 
									
										
										
										
											2021-12-15 15:48:59 +01:00
										 |  |  |                 if not events['esp_delegated_answered'].is_set(): | 
					
						
							| 
									
										
										
										
											2021-12-14 18:34:04 +01:00
										 |  |  |                     sock.sendto(get_dns_query_for_esp(esp_host + '-delegated'), | 
					
						
							|  |  |  |                                 (MCAST_GRP, UDP_PORT)) | 
					
						
							| 
									
										
										
										
											2021-05-11 13:47:03 +08:00
										 |  |  |             timeout = max( | 
					
						
							|  |  |  |                 0, QUERY_TIMEOUT - (current_time - last_query_timepoint)) | 
					
						
							|  |  |  |             read_socks, _, _ = select.select([sock], [], [], timeout) | 
					
						
							|  |  |  |             if not read_socks: | 
					
						
							|  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2019-06-19 13:20:58 +02:00
										 |  |  |             data, addr = sock.recvfrom(1024) | 
					
						
							|  |  |  |             dns = dpkt.dns.DNS(data) | 
					
						
							| 
									
										
										
										
											2023-11-03 17:45:57 +08:00
										 |  |  |             if len(dns.qd) > 0: | 
					
						
							|  |  |  |                 for dns_query in dns.qd: | 
					
						
							|  |  |  |                     if dns_query.type == dpkt.dns.DNS_A: | 
					
						
							|  |  |  |                         if dns_query.name == TESTER_NAME: | 
					
						
							|  |  |  |                             print('Received query: {} '.format(dns.__repr__())) | 
					
						
							|  |  |  |                             sock.sendto(get_dns_answer_to_mdns(TESTER_NAME), | 
					
						
							|  |  |  |                                         (MCAST_GRP, UDP_PORT)) | 
					
						
							|  |  |  |                         elif dns_query.name == TESTER_NAME_LWIP: | 
					
						
							|  |  |  |                             print('Received query: {} '.format(dns.__repr__())) | 
					
						
							|  |  |  |                             sock.sendto( | 
					
						
							|  |  |  |                                 get_dns_answer_to_mdns_lwip(TESTER_NAME_LWIP, dns.id), | 
					
						
							|  |  |  |                                 addr) | 
					
						
							|  |  |  |             if len(dns.an) > 0: | 
					
						
							|  |  |  |                 for dns_answer in dns.an: | 
					
						
							|  |  |  |                     if dns_answer.type == dpkt.dns.DNS_A: | 
					
						
							|  |  |  |                         print('Received answer from {}'.format(dns_answer.name)) | 
					
						
							|  |  |  |                         if dns_answer.name == esp_host + u'.local': | 
					
						
							|  |  |  |                             print('Received answer to esp32-mdns query: {}'.format( | 
					
						
							|  |  |  |                                 dns.__repr__())) | 
					
						
							|  |  |  |                             events['esp_answered'].set() | 
					
						
							|  |  |  |                         if dns_answer.name == esp_host + u'-delegated.local': | 
					
						
							|  |  |  |                             print('Received answer to esp32-mdns-delegate query: {}'.format( | 
					
						
							|  |  |  |                                 dns.__repr__())) | 
					
						
							|  |  |  |                             events['esp_delegated_answered'].set() | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  |         except socket.timeout: | 
					
						
							|  |  |  |             break | 
					
						
							| 
									
										
										
										
											2019-01-25 20:24:30 +01:00
										 |  |  |         except dpkt.UnpackError: | 
					
						
							|  |  |  |             continue | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-04 08:32:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-19 13:49:32 +04:00
										 |  |  | def test_examples_protocol_mdns(dut): | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     steps: | | 
					
						
							| 
									
										
										
										
											2022-01-14 14:33:13 +01:00
										 |  |  |       1. obtain IP address + init mdns example | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  |       2. get the dut host name (and IP address) | 
					
						
							|  |  |  |       3. check the mdns name is accessible | 
					
						
							|  |  |  |       4. check DUT output if mdns advertized host is resolved | 
					
						
							| 
									
										
										
										
											2023-02-08 20:20:06 +01:00
										 |  |  |       5. check if DUT responds to dig | 
					
						
							|  |  |  |       6. check the DUT is searchable via reverse IP lookup | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2022-08-19 13:49:32 +04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 20:20:06 +01:00
										 |  |  |     specific_host = dut.expect(r'mdns hostname set to: \[(.*?)\]')[1].decode() | 
					
						
							| 
									
										
										
										
											2021-12-15 15:48:59 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     mdns_server_events = { | 
					
						
							|  |  |  |         'stop': Event(), | 
					
						
							|  |  |  |         'esp_answered': Event(), | 
					
						
							|  |  |  |         'esp_delegated_answered': Event() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     mdns_responder = Thread(target=mdns_server, | 
					
						
							|  |  |  |                             args=(str(specific_host), mdns_server_events)) | 
					
						
							| 
									
										
										
										
											2023-11-03 17:45:57 +08:00
										 |  |  |     ip_addresses = [] | 
					
						
							|  |  |  |     if dut.app.sdkconfig.get('LWIP_IPV4') is True: | 
					
						
							|  |  |  |         ipv4 = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', | 
					
						
							|  |  |  |                           timeout=30)[1].decode() | 
					
						
							|  |  |  |         ip_addresses.append(ipv4) | 
					
						
							| 
									
										
										
										
											2023-02-08 20:20:06 +01:00
										 |  |  |     if dut.app.sdkconfig.get('LWIP_IPV6') is True: | 
					
						
							|  |  |  |         ipv6_r = r':'.join((r'[0-9a-fA-F]{4}', ) * 8) | 
					
						
							|  |  |  |         ipv6 = dut.expect(ipv6_r, timeout=30)[0].decode() | 
					
						
							|  |  |  |         ip_addresses.append(ipv6) | 
					
						
							|  |  |  |     print('Connected with IP addresses: {}'.format(','.join(ip_addresses))) | 
					
						
							| 
									
										
										
										
											2018-10-12 16:45:52 +02:00
										 |  |  |     try: | 
					
						
							| 
									
										
										
										
											2023-11-03 17:45:57 +08:00
										 |  |  |         # TODO: Add test for example disabling IPV4 | 
					
						
							| 
									
										
										
										
											2021-06-30 08:42:09 +02:00
										 |  |  |         mdns_responder.start() | 
					
						
							| 
									
										
										
										
											2023-11-03 17:45:57 +08:00
										 |  |  |         if dut.app.sdkconfig.get('LWIP_IPV4') is True: | 
					
						
							|  |  |  |             # 3. check the mdns name is accessible. | 
					
						
							|  |  |  |             if not mdns_server_events['esp_answered'].wait(timeout=30): | 
					
						
							|  |  |  |                 raise ValueError( | 
					
						
							|  |  |  |                     'Test has failed: did not receive mdns answer within timeout') | 
					
						
							|  |  |  |             if not mdns_server_events['esp_delegated_answered'].wait(timeout=30): | 
					
						
							|  |  |  |                 raise ValueError( | 
					
						
							|  |  |  |                     'Test has failed: did not receive mdns answer for delegated host within timeout' | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             # 4. check DUT output if mdns advertized host is resolved | 
					
						
							|  |  |  |             dut.expect( | 
					
						
							|  |  |  |                 re.compile( | 
					
						
							|  |  |  |                     b'mdns-test: Query A: tinytester.local resolved to: 127.0.0.1') | 
					
						
							| 
									
										
										
										
											2021-04-15 20:03:53 +08:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2023-11-03 17:45:57 +08:00
										 |  |  |             dut.expect( | 
					
						
							|  |  |  |                 re.compile( | 
					
						
							|  |  |  |                     b'mdns-test: gethostbyname: tinytester-lwip.local resolved to: 127.0.0.1' | 
					
						
							|  |  |  |                 )) | 
					
						
							|  |  |  |             dut.expect( | 
					
						
							|  |  |  |                 re.compile( | 
					
						
							|  |  |  |                     b'mdns-test: getaddrinfo: tinytester-lwip.local resolved to: 127.0.0.1' | 
					
						
							|  |  |  |                 )) | 
					
						
							|  |  |  |             # 5. check the DUT answers to `dig` command | 
					
						
							|  |  |  |             dig_output = subprocess.check_output([ | 
					
						
							|  |  |  |                 'dig', '+short', '-p', '5353', '@224.0.0.251', | 
					
						
							|  |  |  |                 '{}.local'.format(specific_host) | 
					
						
							|  |  |  |             ]) | 
					
						
							|  |  |  |             print('Resolving {} using "dig" succeeded with:\n{}'.format( | 
					
						
							|  |  |  |                 specific_host, dig_output)) | 
					
						
							|  |  |  |             if not ipv4.encode('utf-8') in dig_output: | 
					
						
							|  |  |  |                 raise ValueError( | 
					
						
							|  |  |  |                     'Test has failed: Incorrectly resolved DUT hostname using dig' | 
					
						
							|  |  |  |                     "Output should've contained DUT's IP address:{}".format(ipv4)) | 
					
						
							|  |  |  |             # 6. check the DUT reverse lookup | 
					
						
							|  |  |  |             if dut.app.sdkconfig.get('MDNS_RESPOND_REVERSE_QUERIES') is True: | 
					
						
							|  |  |  |                 for ip_address in ip_addresses: | 
					
						
							|  |  |  |                     dig_output = subprocess.check_output([ | 
					
						
							|  |  |  |                         'dig', '+short', '-p', '5353', '@224.0.0.251', '-x', | 
					
						
							|  |  |  |                         '{}'.format(ip_address) | 
					
						
							|  |  |  |                     ]) | 
					
						
							|  |  |  |                     print('Reverse lookup for {} using "dig" succeeded with:\n{}'. | 
					
						
							|  |  |  |                           format(ip_address, dig_output)) | 
					
						
							|  |  |  |                     if specific_host not in dig_output.decode(): | 
					
						
							|  |  |  |                         raise ValueError( | 
					
						
							|  |  |  |                             'Test has failed: Incorrectly resolved DUT IP address using dig' | 
					
						
							|  |  |  |                             "Output should've contained DUT's name:{}".format( | 
					
						
							|  |  |  |                                 specific_host)) | 
					
						
							| 
									
										
										
										
											2023-02-08 20:20:06 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-25 20:24:30 +01:00
										 |  |  |     finally: | 
					
						
							| 
									
										
										
										
											2021-12-15 15:48:59 +01:00
										 |  |  |         mdns_server_events['stop'].set() | 
					
						
							| 
									
										
										
										
											2021-06-30 08:42:09 +02:00
										 |  |  |         mdns_responder.join() |