app_test.py 11 KB

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