app_test.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. # SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. import re
  4. import select
  5. import socket
  6. import struct
  7. import time
  8. from threading import Event, Thread
  9. import dpkt
  10. import dpkt.dns
  11. import ttfw_idf
  12. from tiny_test_fw.Utility import console_log
  13. UDP_PORT = 5353
  14. MCAST_GRP = '224.0.0.251'
  15. # This service is created from esp board startup
  16. SERVICE_NAME = u'ESP32-WebServer._http._tcp.local'
  17. SUB_SERVICE_NAME = u'_server._sub._http._tcp.local'
  18. # This host name answer sent by host, when there is query from board
  19. HOST_NAME = u'tinytester.local'
  20. # This servce answer sent by host, when there is query from board
  21. MDNS_HOST_SERVICE = u'ESP32._http._tcp.local'
  22. stop_mdns_listener = Event()
  23. start_mdns_listener = Event()
  24. esp_service_answered = Event()
  25. esp_sub_service_answered = Event()
  26. esp_host_answered = Event()
  27. esp_delegated_host_answered = Event()
  28. # Get query of ESP32-WebServer._http._tcp.local service
  29. def get_mdns_service_query(service): # type:(str) -> dpkt.dns.Msg
  30. dns = dpkt.dns.DNS()
  31. dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
  32. dns.rcode = dpkt.dns.DNS_RCODE_NOERR
  33. arr = dpkt.dns.DNS.RR()
  34. arr.cls = dpkt.dns.DNS_IN
  35. arr.type = dpkt.dns.DNS_SRV
  36. arr.name = service
  37. arr.target = socket.inet_aton('127.0.0.1')
  38. arr.srvname = service
  39. dns.qd.append(arr)
  40. console_log('Created mdns service query: {} '.format(dns.__repr__()))
  41. return dns.pack()
  42. # Get query of _server_.sub._http._tcp.local sub service
  43. def get_mdns_sub_service_query(sub_service): # type:(str) -> dpkt.dns.Msg
  44. dns = dpkt.dns.DNS()
  45. dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
  46. dns.rcode = dpkt.dns.DNS_RCODE_NOERR
  47. arr = dpkt.dns.DNS.RR()
  48. arr.cls = dpkt.dns.DNS_IN
  49. arr.type = dpkt.dns.DNS_PTR
  50. arr.name = sub_service
  51. arr.target = socket.inet_aton('127.0.0.1')
  52. arr.ptrname = sub_service
  53. dns.qd.append(arr)
  54. console_log('Created mdns subtype service query: {} '.format(dns.__repr__()))
  55. return dns.pack()
  56. # Get query for host resolution
  57. def get_dns_query_for_esp(esp_host): # type:(str) -> dpkt.dns.Msg
  58. dns = dpkt.dns.DNS()
  59. arr = dpkt.dns.DNS.RR()
  60. arr.cls = dpkt.dns.DNS_IN
  61. arr.name = esp_host + u'.local'
  62. dns.qd.append(arr)
  63. console_log('Created query for esp host: {} '.format(dns.__repr__()))
  64. return dns.pack()
  65. # Get mdns answer for host resoloution
  66. def get_dns_answer_to_mdns(tester_host): # type:(str) -> dpkt.dns.Msg
  67. dns = dpkt.dns.DNS()
  68. dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
  69. dns.rcode = dpkt.dns.DNS_RCODE_NOERR
  70. arr = dpkt.dns.DNS.RR()
  71. arr.cls = dpkt.dns.DNS_IN
  72. arr.type = dpkt.dns.DNS_A
  73. arr.name = tester_host
  74. arr.ip = socket.inet_aton('127.0.0.1')
  75. dns. an.append(arr)
  76. console_log('Created answer to mdns query: {} '.format(dns.__repr__()))
  77. return dns.pack()
  78. # Get mdns answer for service query
  79. def get_dns_answer_to_service_query(mdns_service): # type:(str) -> dpkt.dns.Msg
  80. dns = dpkt.dns.DNS()
  81. dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
  82. dns.rcode = dpkt.dns.DNS_RCODE_NOERR
  83. arr = dpkt.dns.DNS.RR()
  84. arr.name = mdns_service
  85. arr.cls = dpkt.dns.DNS_IN
  86. arr.type = dpkt.dns.DNS_SRV
  87. arr.priority = 0
  88. arr.weight = 0
  89. arr.port = 100
  90. arr.srvname = mdns_service
  91. arr.ip = socket.inet_aton('127.0.0.1')
  92. dns. an.append(arr)
  93. console_log('Created answer to mdns query: {} '.format(dns.__repr__()))
  94. return dns.pack()
  95. def mdns_listener(esp_host): # type:(str) -> None
  96. print('mdns_listener thread started')
  97. UDP_IP = '0.0.0.0'
  98. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  99. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  100. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  101. sock.setblocking(False)
  102. sock.bind((UDP_IP,UDP_PORT))
  103. mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
  104. sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
  105. last_query_timepoint = time.time()
  106. QUERY_TIMEOUT = 0.2
  107. while not stop_mdns_listener.is_set():
  108. try:
  109. start_mdns_listener.set()
  110. current_time = time.time()
  111. if current_time - last_query_timepoint > QUERY_TIMEOUT:
  112. last_query_timepoint = current_time
  113. timeout = max(0, QUERY_TIMEOUT - (current_time - last_query_timepoint))
  114. read_socks, _, _ = select.select([sock], [], [], timeout)
  115. if not read_socks:
  116. continue
  117. data, _ = sock.recvfrom(1024)
  118. dns = dpkt.dns.DNS(data)
  119. if len(dns.qd) > 0:
  120. if dns.qd[0].name == HOST_NAME:
  121. console_log('Received query: {} '.format(dns.__repr__()))
  122. sock.sendto(get_dns_answer_to_mdns(HOST_NAME), (MCAST_GRP,UDP_PORT))
  123. if dns.qd[0].name == HOST_NAME:
  124. console_log('Received query: {} '.format(dns.__repr__()))
  125. sock.sendto(get_dns_answer_to_mdns(HOST_NAME), (MCAST_GRP,UDP_PORT))
  126. if dns.qd[0].name == MDNS_HOST_SERVICE:
  127. print(dns.qd[0].name)
  128. console_log('Received query: {} '.format(dns.__repr__()))
  129. sock.sendto(get_dns_answer_to_service_query(MDNS_HOST_SERVICE), (MCAST_GRP,UDP_PORT))
  130. if len(dns.an) == 1:
  131. if dns.an[0].name == SERVICE_NAME:
  132. console_log('Received answer to service query: {}'.format(dns.__repr__()))
  133. esp_service_answered.set()
  134. if len(dns.an) > 1:
  135. if dns.an[1].name == SUB_SERVICE_NAME:
  136. console_log('Received answer for sub service query: {}'.format(dns.__repr__()))
  137. esp_sub_service_answered.set()
  138. if len(dns.an) > 0 and dns.an[0].type == dpkt.dns.DNS_A:
  139. if dns.an[0].name == esp_host + u'.local':
  140. console_log('Received answer to esp32-mdns query: {}'.format(dns.__repr__()))
  141. esp_host_answered.set()
  142. if dns.an[0].name == esp_host + u'-delegated.local':
  143. console_log('Received answer to esp32-mdns-delegate query: {}'.format(dns.__repr__()))
  144. esp_delegated_host_answered.set()
  145. except socket.timeout:
  146. break
  147. except dpkt.UnpackError:
  148. continue
  149. def create_socket(): # type:() -> socket.socket
  150. UDP_IP = '0.0.0.0'
  151. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  152. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  153. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  154. sock.setblocking(False)
  155. sock.bind((UDP_IP,UDP_PORT))
  156. mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
  157. sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
  158. return sock
  159. def test_query_dns_http_service(service): # type: (str) -> None
  160. print('SRV: Query {}'.format(service))
  161. sock = create_socket()
  162. sock.sendto(get_mdns_service_query(service), (MCAST_GRP,UDP_PORT))
  163. if not esp_service_answered.wait(timeout=25):
  164. raise ValueError('Test has failed: did not receive mdns answer within timeout')
  165. def test_query_dns_sub_service(sub_service): # type: (str) -> None
  166. print('PTR: Query {}'.format(sub_service))
  167. sock = create_socket()
  168. sock.sendto(get_mdns_sub_service_query(sub_service), (MCAST_GRP,UDP_PORT))
  169. if not esp_sub_service_answered.wait(timeout=25):
  170. raise ValueError('Test has failed: did not receive mdns answer within timeout')
  171. def test_query_dns_host(esp_host): # type: (str) -> None
  172. print('A: {}'.format(esp_host))
  173. sock = create_socket()
  174. sock.sendto(get_dns_query_for_esp(esp_host), (MCAST_GRP,UDP_PORT))
  175. if not esp_host_answered.wait(timeout=25):
  176. raise ValueError('Test has failed: did not receive mdns answer within timeout')
  177. def test_query_dns_host_delegated(esp_host): # type: (str) -> None
  178. print('A: {}'.format(esp_host))
  179. sock = create_socket()
  180. sock.sendto(get_dns_query_for_esp(esp_host + '-delegated'), (MCAST_GRP,UDP_PORT))
  181. if not esp_delegated_host_answered.wait(timeout=25):
  182. raise ValueError('Test has failed: did not receive mdns answer within timeout')
  183. @ttfw_idf.idf_custom_test(env_tag='Example_WIFI', group='test-apps')
  184. def test_app_esp_mdns(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None
  185. dut1 = env.get_dut('mdns', 'tools/test_apps/protocols/mdns', dut_class=ttfw_idf.ESP32DUT)
  186. # 1. start mdns application
  187. dut1.start_app()
  188. # 2. get the dut host name (and IP address)
  189. specific_host = dut1.expect(re.compile(r'mdns hostname set to: \[([^\]]+)\]'), timeout=30)[0]
  190. esp_ip = dut1.expect(re.compile(r' IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)'), timeout=30)
  191. print('Got IP={}'.format(esp_ip[0]))
  192. mdns_responder = Thread(target=mdns_listener, args=(str(specific_host),))
  193. def start_case(case, desc, result): # type: (str, str, str) -> None
  194. print('Starting {}: {}'.format(case, desc))
  195. dut1.write(case)
  196. dut1.expect(re.compile(result), timeout=10)
  197. try:
  198. # start dns listener thread
  199. mdns_responder.start()
  200. # wait untill mdns listener thred started
  201. if not start_mdns_listener.wait(timeout=5):
  202. raise ValueError('Test has failed: mdns listener thread did not start')
  203. # query dns service from host, answer should be received from esp board
  204. test_query_dns_http_service(SERVICE_NAME)
  205. # query dns sub-service from host, answer should be received from esp board
  206. test_query_dns_sub_service(SUB_SERVICE_NAME)
  207. # query dns host name, answer should be received from esp board
  208. test_query_dns_host(specific_host)
  209. # query dns host name delegated, answer should be received from esp board
  210. test_query_dns_host_delegated(specific_host)
  211. # query dns-host from esp board, answer should be received from host
  212. start_case('CONFIG_TEST_QUERY_HOST', 'Query tinytester.local', 'tinytester.local resolved to: 127.0.0.1')
  213. # query dns-host aynchrounusely from esp board, answer should be received from host
  214. start_case('CONFIG_TEST_QUERY_HOST_ASYNC', 'Query tinytester.local async', 'Async query resolved to A:127.0.0.1')
  215. # query service from esp board, answer should be received from host
  216. start_case('CONFIG_TEST_QUERY_SERVICE', 'Query SRV ESP32._http._tcp.local', 'SRV:ESP32')
  217. finally:
  218. stop_mdns_listener.set()
  219. mdns_responder.join()
  220. if __name__ == '__main__':
  221. test_app_esp_mdns()