pytest_example_bridge.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: CC0-1.0
  3. import ipaddress
  4. import logging
  5. import re
  6. import socket
  7. import subprocess
  8. import time
  9. from concurrent.futures import Future, ThreadPoolExecutor
  10. from typing import List, Union
  11. import netifaces
  12. import paramiko # type: ignore
  13. import pytest
  14. from common_test_methods import get_host_ip_by_interface
  15. from netmiko import ConnectHandler
  16. from pytest_embedded import Dut
  17. # Testbed configuration
  18. BR_PORTS_NUM = 2
  19. IPERF_BW_LIM = 6
  20. MIN_UDP_THROUGHPUT = 5
  21. MIN_TCP_THROUGHPUT = 4
  22. class EndnodeSsh:
  23. def __init__(self, host_ip: str, usr: str, passwd: str):
  24. self.host_ip = host_ip
  25. self.ssh_client = paramiko.SSHClient()
  26. self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  27. self.ssh_client.connect(hostname=self.host_ip,
  28. username=usr,
  29. password=passwd)
  30. self.executor: ThreadPoolExecutor
  31. self.async_result: Future
  32. def exec_cmd(self, cmd: str) -> str:
  33. _, stdout, stderr = self.ssh_client.exec_command(cmd)
  34. out = stdout.read().decode().strip()
  35. error = stderr.read().decode().strip()
  36. if error:
  37. out = ''
  38. logging.error('ssh_endnode_exec error: {}'.format(error))
  39. return out # type: ignore
  40. def exec_cmd_async(self, cmd: str) -> None:
  41. self.executor = ThreadPoolExecutor(max_workers=1)
  42. self.async_result = self.executor.submit(self.exec_cmd, cmd)
  43. def get_async_res(self) -> str:
  44. return self.async_result.result(10) # type: ignore
  45. def close(self) -> None:
  46. self.ssh_client.close()
  47. class SwitchSsh:
  48. EDGE_SWITCH_5XP = 0
  49. EDGE_SWITCH_10XP = 1
  50. def __init__(self, host_ip: str, usr: str, passwd: str, device_type: int):
  51. self.host_ip = host_ip
  52. self.type = device_type
  53. if self.type == self.EDGE_SWITCH_5XP:
  54. self.ssh_client = paramiko.SSHClient()
  55. self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  56. self.ssh_client.connect(hostname=self.host_ip,
  57. username=usr,
  58. password=passwd)
  59. else:
  60. edgeSwitch = {
  61. 'device_type': 'ubiquiti_edgeswitch',
  62. 'host': self.host_ip,
  63. 'username': usr,
  64. 'password': passwd,
  65. }
  66. self.ssh_client = ConnectHandler(**edgeSwitch)
  67. def exec_cmd(self, cmd: Union[str, List[str]]) -> str:
  68. if self.type == self.EDGE_SWITCH_5XP:
  69. _, stdout, stderr = self.ssh_client.exec_command(cmd)
  70. out = stdout.read().decode().strip()
  71. error = stderr.read().decode().strip()
  72. if error != 'TSW Init OK!':
  73. raise Exception('switch_5xp exec_cmd error: {}'.format(error))
  74. else:
  75. out = self.ssh_client.send_config_set(cmd, cmd_verify=False, exit_config_mode=False)
  76. return out # type: ignore
  77. def switch_port_down(self, port: int) -> None:
  78. if self.type == self.EDGE_SWITCH_5XP:
  79. command = '/usr/bin/tswconf debug phy set ' + str(port - 1) + ' 0 0x800'
  80. self.exec_cmd(command)
  81. else:
  82. commands = ['interface GigabitEthernet ' + str(port), 'shutdown']
  83. self.exec_cmd(commands)
  84. def switch_port_up(self, port: int) -> None:
  85. if self.type == self.EDGE_SWITCH_5XP:
  86. command = '/usr/bin/tswconf debug phy set ' + str(port - 1) + ' 0 0x1000'
  87. self.exec_cmd(command)
  88. else:
  89. commands = ['interface GigabitEthernet' + str(port), 'no shutdown']
  90. self.exec_cmd(commands)
  91. def close(self) -> None:
  92. if self.type == self.EDGE_SWITCH_5XP:
  93. self.ssh_client.close()
  94. else:
  95. self.ssh_client.disconnect()
  96. def get_endnode_mac_by_interface(endnode: EndnodeSsh, if_name: str) -> str:
  97. ip_info = endnode.exec_cmd(f'ip addr show {if_name}')
  98. regex = if_name + r':.*?link/ether ([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})'
  99. mac_addr = re.search(regex, ip_info, re.DOTALL)
  100. if mac_addr is None:
  101. return ''
  102. return mac_addr.group(1)
  103. def get_endnode_ip_by_interface(endnode: EndnodeSsh, if_name: str) -> str:
  104. ip_info = endnode.exec_cmd(f'ip addr show {if_name}')
  105. regex = if_name + r':.*?inet (\d+[.]\d+[.]\d+[.]\d+)\/'
  106. ip_addr = re.search(regex, ip_info, re.DOTALL)
  107. if ip_addr is None:
  108. return ''
  109. return ip_addr.group(1)
  110. def get_host_interface_name_in_same_net(ip_addr: str) -> str:
  111. ip_net = ipaddress.IPv4Network(f'{ip_addr}/24', strict=False)
  112. for interface in netifaces.interfaces():
  113. addr = get_host_ip_by_interface(interface)
  114. if ipaddress.IPv4Address(addr) in ip_net:
  115. return str(interface)
  116. return ''
  117. def get_host_mac_by_interface(interface_name: str, addr_type: int = netifaces.AF_LINK) -> str:
  118. for _addr in netifaces.ifaddresses(interface_name)[addr_type]:
  119. host_mac = _addr['addr'].replace('%{}'.format(interface_name), '')
  120. assert isinstance(host_mac, str)
  121. return host_mac
  122. return ''
  123. def get_host_brcast_ip_by_interface(interface_name: str, ip_type: int = netifaces.AF_INET) -> str:
  124. for _addr in netifaces.ifaddresses(interface_name)[ip_type]:
  125. host_ip = _addr['broadcast'].replace('%{}'.format(interface_name), '')
  126. assert isinstance(host_ip, str)
  127. return host_ip
  128. return ''
  129. def run_iperf(proto: str, endnode: EndnodeSsh, server_ip: str, bandwidth_lim:int=10, interval:int=5, server_if:str='', client_if:str='') -> float:
  130. if proto == 'tcp':
  131. proto = ''
  132. else:
  133. proto = '-u'
  134. if ipaddress.ip_address(server_ip).is_multicast:
  135. # Configure Multicast Server
  136. server_proc = subprocess.Popen(['iperf', '-u', '-s', '-i', '1', '-t', '%i' % interval, '-B', '%s%%%s'
  137. % (server_ip, server_if)], text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  138. # Configure Multicast Client
  139. endnode_ip = get_endnode_ip_by_interface(endnode, client_if)
  140. if endnode_ip == '':
  141. raise RuntimeError('End node IP address not found')
  142. client_res = endnode.exec_cmd('iperf -u -c %s -t %i -i 1 -b %iM --ttl 5 -B %s' % (server_ip, interval, bandwidth_lim, endnode_ip))
  143. if server_proc.wait(10) is None: # Process did not finish.
  144. server_proc.terminate()
  145. else:
  146. # Configure Server
  147. server_proc = subprocess.Popen(['iperf', '%s' % proto, '-s', '-i', '1', '-t', '%i' % interval], text=True,
  148. stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  149. # Configure Client
  150. client_res = endnode.exec_cmd('iperf %s -c %s -t %i -i 1 -b %iM' % (proto, server_ip, interval, bandwidth_lim))
  151. if server_proc.wait(10) is None: # Process did not finish.
  152. server_proc.terminate()
  153. try:
  154. server_res = server_proc.communicate(timeout=15)[0]
  155. except subprocess.TimeoutExpired:
  156. server_proc.kill()
  157. server_res = server_proc.communicate()[0]
  158. print('\n')
  159. print(client_res)
  160. print('\n')
  161. print(server_res)
  162. SERVER_BANDWIDTH_LOG_PATTERN = r'(\d+\.\d+)\s*-\s*(\d+.\d+)\s+sec\s+[\d.]+\s+MBytes\s+([\d.]+)\s+Mbits\/sec'
  163. performance = re.search(SERVER_BANDWIDTH_LOG_PATTERN, server_res, re.DOTALL)
  164. if performance is None:
  165. return -1.0
  166. return float(performance.group(3))
  167. def send_brcast_msg_host_to_endnode(endnode: EndnodeSsh, host_brcast_ip: str, test_msg: str) -> str:
  168. endnode.exec_cmd_async('timeout 4s nc -u -w 0 -l -p 5100')
  169. time.sleep(1)
  170. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  171. try:
  172. sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
  173. sock.sendto(test_msg.encode('utf-8'), (host_brcast_ip, 5100))
  174. except socket.error as e:
  175. raise Exception('Host brcast send failed %s' % e)
  176. nc_endnode_out = endnode.get_async_res()
  177. sock.close()
  178. return nc_endnode_out
  179. def send_brcast_msg_endnode_to_host(endnode: EndnodeSsh, host_brcast_ip: str, test_msg: str) -> str:
  180. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  181. sock.settimeout(5)
  182. try:
  183. sock.bind(('', 5100))
  184. except socket.error as e:
  185. raise Exception('Host bind failed %s' % e)
  186. endnode.exec_cmd('echo -n "%s" | nc -b -w0 -u %s 5100' % (test_msg, host_brcast_ip))
  187. try:
  188. nc_host_out = sock.recv(1500).decode('utf-8')
  189. except socket.error as e:
  190. raise Exception('Host recv failed %s', e)
  191. sock.close()
  192. return nc_host_out
  193. @pytest.mark.esp32
  194. @pytest.mark.eth_w5500
  195. @pytest.mark.parametrize('config', [
  196. 'w5500',
  197. ], indirect=True)
  198. def test_esp_eth_bridge(
  199. dut: Dut,
  200. dev_user: str,
  201. dev_password: str
  202. ) -> None:
  203. # ------------------------------ #
  204. # Pre-test testbed configuration #
  205. # ------------------------------ #
  206. # Get switch configuration info from the hostname
  207. host_name = socket.gethostname()
  208. regex = r'ethVM-(\d+)-(\d+)'
  209. sw_info = re.search(regex, host_name, re.DOTALL)
  210. if sw_info is None:
  211. raise RuntimeError('Unexpected hostname')
  212. sw_num = int(sw_info.group(1))
  213. port_num = int(sw_info.group(2))
  214. port_num_endnode = int(port_num) + 1 # endnode address is always + 1 to the host
  215. endnode = EndnodeSsh(f'10.10.{sw_num}.{port_num_endnode}',
  216. dev_user,
  217. dev_password)
  218. switch1 = SwitchSsh(f'10.10.{sw_num}.100',
  219. dev_user,
  220. dev_password,
  221. SwitchSsh.EDGE_SWITCH_10XP)
  222. # Collect all addresses in our network
  223. # ------------------------------------
  224. # Bridge (DUT) MAC
  225. br_mac = dut.expect(r'esp_netif_br_glue: ([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})')
  226. br_mac = br_mac.group(1).decode('utf-8')
  227. logging.info('ESP Bridge MAC %s', br_mac)
  228. # Get unique identification of each Ethernet port
  229. p1_id = dut.expect(r'Ethernet \((0x[0-9A-Fa-f]{8})\) Started')
  230. p1_id = p1_id.group(1).decode('utf-8')
  231. p2_id = dut.expect(r'Ethernet \((0x[0-9A-Fa-f]{8})\) Started')
  232. p2_id = p2_id.group(1).decode('utf-8')
  233. # Bridge (DUT) IP
  234. dut.expect_exact('Ethernet Got IP Address')
  235. br_ip = dut.expect(r'ETHIP:(\d+[.]\d+[.]\d+[.]\d+)\r')
  236. br_ip = br_ip.group(1).decode('utf-8')
  237. logging.info('ESP Bridge IP %s', br_ip)
  238. # Host interface is in the same network as DUT
  239. host_if = get_host_interface_name_in_same_net(br_ip)
  240. # Test Host MAC
  241. host_mac = get_host_mac_by_interface(host_if)
  242. logging.info('Host MAC %s', host_mac)
  243. # Test Host IP
  244. host_ip = get_host_ip_by_interface(host_if, netifaces.AF_INET)
  245. logging.info('Host IP %s', host_ip)
  246. endnode_if = host_if # endnode is a clone of the host
  247. # Endnode MAC
  248. endnode_mac = get_endnode_mac_by_interface(endnode, endnode_if)
  249. logging.info('Endnode MAC %s', endnode_mac)
  250. # Toggle link status at the End Node to initiate DHCP request
  251. endnode.exec_cmd(f'sudo ip link set down dev {endnode_if}')
  252. endnode.exec_cmd(f'sudo ip link set up dev {endnode_if}')
  253. # Endnode IP
  254. for i in range(15):
  255. endnode_ip = get_endnode_ip_by_interface(endnode, endnode_if)
  256. if endnode_ip != '':
  257. break
  258. time.sleep(1)
  259. logging.info('End node waiting for DHCP IP addr, %isec...', i)
  260. else:
  261. raise RuntimeError('End node IP address not found')
  262. logging.info('Endnode IP %s', endnode_ip)
  263. # --------------------------------------------------
  264. # TEST Objective 1: Ping the devices on the network
  265. # --------------------------------------------------
  266. # ping bridge
  267. ping_test = subprocess.call(f'ping {br_ip} -c 2', shell=True)
  268. if ping_test != 0:
  269. raise RuntimeError('ESP bridge is not reachable')
  270. # ping the end nodes of the network
  271. ping_test = subprocess.call(f'ping {endnode_ip} -c 2', shell=True)
  272. if ping_test != 0:
  273. raise RuntimeError('End node is not reachable')
  274. # -------------------------------------------------
  275. # TEST Objective 2: Ports Link Up/Down combinations
  276. # -------------------------------------------------
  277. logging.info('link down the port #1')
  278. switch1.switch_port_down(port_num)
  279. dut.expect_exact(f'Ethernet ({p1_id}) Link Down')
  280. logging.info('link down both ports')
  281. switch1.switch_port_down(port_num_endnode)
  282. dut.expect_exact(f'Ethernet ({p2_id}) Link Down')
  283. logging.info('link up the port #1')
  284. switch1.switch_port_up(port_num)
  285. dut.expect_exact(f'Ethernet ({p1_id}) Link Up')
  286. dut.expect_exact('Ethernet Got IP Address') # DHCP Server is connected to port #1
  287. logging.info('link down both ports')
  288. switch1.switch_port_down(port_num)
  289. dut.expect_exact(f'Ethernet ({p1_id}) Link Down')
  290. logging.info('link up the port #2')
  291. switch1.switch_port_up(port_num_endnode)
  292. dut.expect_exact(f'Ethernet ({p2_id}) Link Up') # Note: No "Ethernet Got IP Address" since DHCP Server is connected to port #1
  293. logging.info('link down both ports')
  294. switch1.switch_port_down(port_num_endnode)
  295. dut.expect_exact(f'Ethernet ({p2_id}) Link Down')
  296. logging.info('link up both ports')
  297. switch1.switch_port_up(port_num_endnode)
  298. dut.expect_exact(f'Ethernet ({p2_id}) Link Up')
  299. switch1.switch_port_up(port_num) # link up port #1 as last to ensure we Got IP address after link port #2 is up
  300. dut.expect_exact(f'Ethernet ({p1_id}) Link Up')
  301. dut.expect_exact('Ethernet Got IP Address')
  302. # --------------------------------------------------------------------------
  303. # TEST Objective 3: IP traffic forwarding (iPerf between network end nodes)
  304. # --------------------------------------------------------------------------
  305. # unicast UDP
  306. bandwidth_udp = run_iperf('udp', endnode, host_ip, IPERF_BW_LIM, 5)
  307. logging.info('Unicast UDP average bandwidth: %s Mbits/s', bandwidth_udp)
  308. # unicast TCP
  309. bandwidth_tcp = run_iperf('tcp', endnode, host_ip, IPERF_BW_LIM, 5)
  310. logging.info('Unicast TCP average bandwidth: %s Mbits/s', bandwidth_tcp)
  311. # multicast UDP
  312. bandwidth_mcast_udp = run_iperf('udp', endnode, '224.0.1.4', IPERF_BW_LIM, 5, host_if, endnode_if)
  313. logging.info('Multicast UDP average bandwidth: %s Mbits/s', bandwidth_mcast_udp)
  314. if bandwidth_udp < MIN_UDP_THROUGHPUT:
  315. raise RuntimeError('Unicast UDP throughput expected %.2f, actual %.2f' % (MIN_UDP_THROUGHPUT, bandwidth_udp) + ' Mbits/s')
  316. if bandwidth_tcp < MIN_TCP_THROUGHPUT:
  317. raise RuntimeError('Unicast TCP throughput expected %.2f, actual %.2f' % (MIN_TCP_THROUGHPUT, bandwidth_tcp) + ' Mbits/s')
  318. if bandwidth_mcast_udp < MIN_UDP_THROUGHPUT:
  319. raise RuntimeError('Multicast UDP throughput expected %.2f, actual %.2f' % (MIN_UDP_THROUGHPUT, bandwidth_mcast_udp) + ' Mbits/s')
  320. # ------------------------------------------------
  321. # TEST Objective 4: adding/deleting entries in FDB
  322. # ------------------------------------------------
  323. # At first test the Bridge Example Command Interface
  324. MAC_ADDR = '01:02:03:04:05:06'
  325. dut.write('\n')
  326. dut.expect_exact('bridge>')
  327. # invalid MAC format
  328. dut.write('add --addr=01:125:02:00:00:0A -d')
  329. dut.expect_exact('Ivalid MAC address format')
  330. dut.expect_exact('Command returned non-zero error code: 0x1')
  331. dut.write('add --addr=01:QA:02:00:00:0A -d')
  332. dut.expect_exact('Ivalid MAC address format')
  333. dut.expect_exact('Command returned non-zero error code: 0x1')
  334. dut.write('add --addr=01:00:02:00:0A -d')
  335. dut.expect_exact('Ivalid MAC address format')
  336. dut.expect_exact('Command returned non-zero error code: 0x1')
  337. # invalid number of config parameters
  338. dut.write('add --addr=' + MAC_ADDR + ' -d -c -p 1')
  339. dut.expect_exact('Invalid number or combination of arguments')
  340. dut.expect_exact('Command returned non-zero error code: 0x1')
  341. dut.write('add --addr=' + MAC_ADDR + ' -d -c')
  342. dut.expect_exact('Invalid number or combination of arguments')
  343. dut.expect_exact('Command returned non-zero error code: 0x1')
  344. dut.write('add --addr=' + MAC_ADDR + ' -f -c')
  345. dut.expect_exact('Invalid number or combination of arguments')
  346. dut.expect_exact('Command returned non-zero error code: 0x1')
  347. dut.write('add --addr=' + MAC_ADDR + ' -d -p 1')
  348. dut.expect_exact('Invalid number or combination of arguments')
  349. dut.expect_exact('Command returned non-zero error code: 0x1')
  350. dut.write('add --addr=' + MAC_ADDR + ' -f -p 1 -p 2')
  351. dut.expect_exact('Invalid number or combination of arguments')
  352. dut.expect_exact('Command returned non-zero error code: 0x1')
  353. dut.write('add -p 1')
  354. dut.expect_exact('Command returned non-zero error code: 0x1')
  355. dut.write('add --addr=' + MAC_ADDR + ' -p')
  356. dut.expect_exact('Command returned non-zero error code: 0x1')
  357. dut.write('remove')
  358. dut.expect_exact('Command returned non-zero error code: 0x1')
  359. dut.write('remove --addr=' + MAC_ADDR + ' -d')
  360. dut.expect_exact('Command returned non-zero error code: 0x1')
  361. # Invalid port interval number
  362. dut.write('add --addr=' + MAC_ADDR + ' -p 0')
  363. dut.expect_exact('Invalid port number')
  364. dut.expect_exact('Command returned non-zero error code: 0x1')
  365. dut.write('add --addr=' + MAC_ADDR + ' -p ' + str(BR_PORTS_NUM + 1))
  366. dut.expect_exact('Invalid port number')
  367. dut.expect_exact('Command returned non-zero error code: 0x1')
  368. # try to add more FDB entries than configured max number
  369. for i in range(BR_PORTS_NUM + 1):
  370. dut.write('add --addr=01:02:03:00:00:%02x' % i + ' -d')
  371. if i < BR_PORTS_NUM:
  372. dut.expect_exact('Bridge Config OK!')
  373. else:
  374. dut.expect_exact('Adding FDB entry failed')
  375. dut.expect_exact('Command returned non-zero error code: 0x1')
  376. # try to remove non-existent FDB entry
  377. dut.write('remove --addr=' + MAC_ADDR)
  378. dut.expect_exact('Removing FDB entry failed')
  379. dut.expect_exact('Command returned non-zero error code: 0x1')
  380. # remove dummy entries
  381. for i in range(BR_PORTS_NUM):
  382. dut.write('remove --addr=01:02:03:00:00:%02x' % i)
  383. dut.expect_exact('Bridge Config OK!')
  384. # valid multiple ports at once
  385. dut.write('add --addr=' + MAC_ADDR + ' -c -p 1 -p 2')
  386. dut.expect_exact('Bridge Config OK!')
  387. dut.write('remove --addr=' + MAC_ADDR)
  388. dut.expect_exact('Bridge Config OK!')
  389. dut.write('add --addr=' + MAC_ADDR + ' -p 1 -p 2')
  390. dut.expect_exact('Bridge Config OK!')
  391. dut.write('remove --addr=' + MAC_ADDR)
  392. dut.expect_exact('Bridge Config OK!')
  393. # drop `Endnode` MAC and try to ping it from `Test Host`
  394. logging.info('Drop `Endnode` MAC')
  395. dut.write('add --addr=' + endnode_mac + ' -d')
  396. dut.expect_exact('Bridge Config OK!')
  397. ping_test = subprocess.call(f'ping {endnode_ip} -c 2', shell=True)
  398. if ping_test == 0:
  399. raise RuntimeError('End node should not be reachable')
  400. logging.info('Remove Drop `Endnode` MAC entry')
  401. dut.write('remove --addr=' + endnode_mac)
  402. dut.expect_exact('Bridge Config OK!')
  403. ping_test = subprocess.call(f'ping {endnode_ip} -c 2', shell=True)
  404. if ping_test != 0:
  405. raise RuntimeError('End node is not reachable')
  406. # Since we have only two ports on DUT, it is kind of tricky to verify the forwarding directly with devices'
  407. # specific MAC addresses. However, we can verify it using broadcast address and to observe the system
  408. # behavior in all directions.
  409. # At first, check normal condition
  410. TEST_MSG = 'ESP32 bridge test message'
  411. host_brcast_ip = get_host_brcast_ip_by_interface(host_if, netifaces.AF_INET)
  412. endnode_recv = send_brcast_msg_host_to_endnode(endnode, host_brcast_ip, TEST_MSG)
  413. if endnode_recv != TEST_MSG:
  414. raise RuntimeError('Broadcast message was not received by endnode')
  415. host_recv = send_brcast_msg_endnode_to_host(endnode, host_brcast_ip, TEST_MSG)
  416. if host_recv != TEST_MSG:
  417. raise RuntimeError('Broadcast message was not received by host')
  418. # now, configure forward the broadcast only to port #1
  419. dut.write('add --addr=ff:ff:ff:ff:ff:ff -p 1')
  420. dut.expect_exact('Bridge Config OK!')
  421. # we should not be able to receive a message at endnode (no forward to port #2)...
  422. endnode_recv = send_brcast_msg_host_to_endnode(endnode, host_brcast_ip, TEST_MSG)
  423. if endnode_recv != '':
  424. raise RuntimeError('Broadcast message should not be received by endnode')
  425. # ... but we should be able to do the same in opposite direction (forward to port #1)
  426. host_recv = send_brcast_msg_endnode_to_host(endnode, host_brcast_ip, TEST_MSG)
  427. if host_recv != TEST_MSG:
  428. raise RuntimeError('Broadcast message was not received by host')
  429. # Remove ARP record from Test host computer. ARP is broadcasted, hence Bridge port does not reply to a request since
  430. # it does not receive it (no forward to Bridge port). As a result, Bridge is not pingable.
  431. subprocess.call(f'sudo arp -d {br_ip}', shell=True)
  432. subprocess.call('arp -a', shell=True)
  433. ping_test = subprocess.call(f'ping {br_ip} -c 2', shell=True)
  434. if ping_test == 0:
  435. raise RuntimeError('Bridge should not be reachable')
  436. # Remove current broadcast entry and replace it with extended one which includes Bridge port
  437. # Now, we should be able to ping the Bridge...
  438. dut.write('remove --addr=ff:ff:ff:ff:ff:ff')
  439. dut.expect_exact('Bridge Config OK!')
  440. dut.write('add --addr=ff:ff:ff:ff:ff:ff -p 1 -c')
  441. dut.expect_exact('Bridge Config OK!')
  442. ping_test = subprocess.call(f'ping {br_ip} -c 2', shell=True)
  443. if ping_test != 0:
  444. raise RuntimeError('Bridge is not reachable')
  445. # ...but we should still not be able to receive a message at endnode (no forward to port #2)
  446. endnode_recv = send_brcast_msg_host_to_endnode(endnode, host_brcast_ip, TEST_MSG)
  447. if endnode_recv != '':
  448. raise RuntimeError('Broadcast message should not be received by endnode')
  449. endnode.close()
  450. switch1.close()