pytest_example_bridge.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  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 Exception('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. return nc_endnode_out
  178. def send_brcast_msg_endnode_to_host(endnode: EndnodeSsh, host_brcast_ip: str, test_msg: str) -> str:
  179. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  180. sock.settimeout(5)
  181. try:
  182. sock.bind(('', 5100))
  183. except socket.error as e:
  184. raise Exception('Host bind failed %s' % e)
  185. endnode.exec_cmd('echo -n "%s" | nc -b -w0 -u %s 5100' % (test_msg, host_brcast_ip))
  186. try:
  187. nc_host_out = sock.recv(1500).decode('utf-8')
  188. except socket.error as e:
  189. raise Exception('Host recv failed %s' % e)
  190. return nc_host_out
  191. @pytest.mark.esp32
  192. @pytest.mark.ethernet_w5500
  193. @pytest.mark.parametrize('config', [
  194. 'w5500',
  195. ], indirect=True)
  196. def test_esp_eth_bridge(
  197. dut: Dut,
  198. dev_user: str,
  199. dev_password: str
  200. ) -> None:
  201. # ------------------------------ #
  202. # Pre-test testbed configuration #
  203. # ------------------------------ #
  204. # Get switch configuration info from the hostname
  205. host_name = socket.gethostname()
  206. regex = r'ethVM-(\d+)-(\d+)'
  207. sw_info = re.search(regex, host_name, re.DOTALL)
  208. if sw_info is None:
  209. raise Exception('Unexpected hostname')
  210. sw_num = int(sw_info.group(1))
  211. port_num = int(sw_info.group(2))
  212. port_num_endnode = int(port_num) + 1 # endnode address is always + 1 to the host
  213. endnode = EndnodeSsh(f'10.10.{sw_num}.{port_num_endnode}',
  214. dev_user,
  215. dev_password)
  216. switch1 = SwitchSsh(f'10.10.{sw_num}.100',
  217. dev_user,
  218. dev_password,
  219. SwitchSsh.EDGE_SWITCH_10XP)
  220. # Collect all addresses in our network
  221. # ------------------------------------
  222. # Bridge (DUT) MAC
  223. 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})')
  224. br_mac = br_mac.group(1).decode('utf-8')
  225. logging.info('ESP Bridge MAC %s', br_mac)
  226. # Get unique identification of each Ethernet port
  227. p1_id = dut.expect(r'Ethernet \((0x[0-9A-Fa-f]{8})\) Started')
  228. p1_id = p1_id.group(1).decode('utf-8')
  229. p2_id = dut.expect(r'Ethernet \((0x[0-9A-Fa-f]{8})\) Started')
  230. p2_id = p2_id.group(1).decode('utf-8')
  231. # Bridge (DUT) IP
  232. dut.expect_exact('Ethernet Got IP Address')
  233. br_ip = dut.expect(r'ETHIP:(\d+[.]\d+[.]\d+[.]\d+)\r')
  234. br_ip = br_ip.group(1).decode('utf-8')
  235. logging.info('ESP Bridge IP %s', br_ip)
  236. # Host interface is in the same network as DUT
  237. host_if = get_host_interface_name_in_same_net(br_ip)
  238. # Test Host MAC
  239. host_mac = get_host_mac_by_interface(host_if)
  240. logging.info('Host MAC %s', host_mac)
  241. # Test Host IP
  242. host_ip = get_host_ip_by_interface(host_if, netifaces.AF_INET)
  243. logging.info('Host IP %s', host_ip)
  244. endnode_if = host_if # endnode is a clone of the host
  245. # Endnode MAC
  246. endnode_mac = get_endnode_mac_by_interface(endnode, endnode_if)
  247. logging.info('Endnode MAC %s', endnode_mac)
  248. # Toggle link status at the End Node to initiate DHCP request
  249. endnode.exec_cmd(f'sudo ip link set down dev {endnode_if}')
  250. endnode.exec_cmd(f'sudo ip link set up dev {endnode_if}')
  251. # Endnode IP
  252. for i in range(15):
  253. endnode_ip = get_endnode_ip_by_interface(endnode, endnode_if)
  254. if endnode_ip != '':
  255. break
  256. time.sleep(1)
  257. logging.info('End node waiting for DHCP IP addr, %isec...', i)
  258. else:
  259. raise Exception('End node IP address not found')
  260. logging.info('Endnode IP %s', endnode_ip)
  261. # --------------------------------------------------
  262. # TEST Objective 1: Ping the devices on the network
  263. # --------------------------------------------------
  264. # ping bridge
  265. ping_test = subprocess.call(f'ping {br_ip} -c 2', shell=True)
  266. if ping_test != 0:
  267. raise Exception('ESP bridge is not reachable')
  268. # ping the end nodes of the network
  269. ping_test = subprocess.call(f'ping {endnode_ip} -c 2', shell=True)
  270. if ping_test != 0:
  271. raise Exception('End node is not reachable')
  272. # -------------------------------------------------
  273. # TEST Objective 2: Ports Link Up/Down combinations
  274. # -------------------------------------------------
  275. logging.info('link down the port #1')
  276. switch1.switch_port_down(port_num)
  277. dut.expect_exact(f'Ethernet ({p1_id}) Link Down')
  278. logging.info('link down both ports')
  279. switch1.switch_port_down(port_num_endnode)
  280. dut.expect_exact(f'Ethernet ({p2_id}) Link Down')
  281. logging.info('link up the port #1')
  282. switch1.switch_port_up(port_num)
  283. dut.expect_exact(f'Ethernet ({p1_id}) Link Up')
  284. dut.expect_exact('Ethernet Got IP Address') # DHCP Server is connected to port #1
  285. logging.info('link down both ports')
  286. switch1.switch_port_down(port_num)
  287. dut.expect_exact(f'Ethernet ({p1_id}) Link Down')
  288. logging.info('link up the port #2')
  289. switch1.switch_port_up(port_num_endnode)
  290. dut.expect_exact(f'Ethernet ({p2_id}) Link Up') # Note: No "Ethernet Got IP Address" since DHCP Server is connected to port #1
  291. logging.info('link down both ports')
  292. switch1.switch_port_down(port_num_endnode)
  293. dut.expect_exact(f'Ethernet ({p2_id}) Link Down')
  294. logging.info('link up both ports')
  295. switch1.switch_port_up(port_num_endnode)
  296. dut.expect_exact(f'Ethernet ({p2_id}) Link Up')
  297. switch1.switch_port_up(port_num) # link up port #1 as last to ensure we Got IP address after link port #2 is up
  298. dut.expect_exact(f'Ethernet ({p1_id}) Link Up')
  299. dut.expect_exact('Ethernet Got IP Address')
  300. # --------------------------------------------------------------------------
  301. # TEST Objective 3: IP traffic forwarding (iPerf between network end nodes)
  302. # --------------------------------------------------------------------------
  303. # unicast UDP
  304. bandwidth_udp = run_iperf('udp', endnode, host_ip, IPERF_BW_LIM, 5)
  305. logging.info('Unicast UDP average bandwidth: %s Mbits/s', bandwidth_udp)
  306. # unicast TCP
  307. bandwidth_tcp = run_iperf('tcp', endnode, host_ip, IPERF_BW_LIM, 5)
  308. logging.info('Unicast TCP average bandwidth: %s Mbits/s', bandwidth_tcp)
  309. # multicast UDP
  310. bandwidth_mcast_udp = run_iperf('udp', endnode, '224.0.1.4', IPERF_BW_LIM, 5, host_if, endnode_if)
  311. logging.info('Multicast UDP average bandwidth: %s Mbits/s', bandwidth_mcast_udp)
  312. if bandwidth_udp < MIN_UDP_THROUGHPUT:
  313. raise Exception('Unicast UDP throughput expected %.2f, actual %.2f' % (MIN_UDP_THROUGHPUT, bandwidth_udp) + ' Mbits/s')
  314. if bandwidth_tcp < MIN_TCP_THROUGHPUT:
  315. raise Exception('Unicast TCP throughput expected %.2f, actual %.2f' % (MIN_TCP_THROUGHPUT, bandwidth_tcp) + ' Mbits/s')
  316. if bandwidth_mcast_udp < MIN_UDP_THROUGHPUT:
  317. raise Exception('Multicast UDP throughput expected %.2f, actual %.2f' % (MIN_UDP_THROUGHPUT, bandwidth_mcast_udp) + ' Mbits/s')
  318. # ------------------------------------------------
  319. # TEST Objective 4: adding/deleting entries in FDB
  320. # ------------------------------------------------
  321. # At first test the Bridge Example Command Interface
  322. MAC_ADDR = '01:02:03:04:05:06'
  323. dut.write('\n')
  324. dut.expect_exact('bridge>')
  325. # invalid MAC format
  326. dut.write('add --addr=01:125:02:00:00:0A -d')
  327. dut.expect_exact('Ivalid MAC address format')
  328. dut.expect_exact('Command returned non-zero error code: 0x1')
  329. dut.write('add --addr=01:QA:02:00:00:0A -d')
  330. dut.expect_exact('Ivalid MAC address format')
  331. dut.expect_exact('Command returned non-zero error code: 0x1')
  332. dut.write('add --addr=01:00:02:00:0A -d')
  333. dut.expect_exact('Ivalid MAC address format')
  334. dut.expect_exact('Command returned non-zero error code: 0x1')
  335. # invalid number of config parameters
  336. dut.write('add --addr=' + MAC_ADDR + ' -d -c -p 1')
  337. dut.expect_exact('Invalid number or combination of arguments')
  338. dut.expect_exact('Command returned non-zero error code: 0x1')
  339. dut.write('add --addr=' + MAC_ADDR + ' -d -c')
  340. dut.expect_exact('Invalid number or combination of arguments')
  341. dut.expect_exact('Command returned non-zero error code: 0x1')
  342. dut.write('add --addr=' + MAC_ADDR + ' -f -c')
  343. dut.expect_exact('Invalid number or combination of arguments')
  344. dut.expect_exact('Command returned non-zero error code: 0x1')
  345. dut.write('add --addr=' + MAC_ADDR + ' -d -p 1')
  346. dut.expect_exact('Invalid number or combination of arguments')
  347. dut.expect_exact('Command returned non-zero error code: 0x1')
  348. dut.write('add --addr=' + MAC_ADDR + ' -f -p 1 -p 2')
  349. dut.expect_exact('Invalid number or combination of arguments')
  350. dut.expect_exact('Command returned non-zero error code: 0x1')
  351. dut.write('add -p 1')
  352. dut.expect_exact('Command returned non-zero error code: 0x1')
  353. dut.write('add --addr=' + MAC_ADDR + ' -p')
  354. dut.expect_exact('Command returned non-zero error code: 0x1')
  355. dut.write('remove')
  356. dut.expect_exact('Command returned non-zero error code: 0x1')
  357. dut.write('remove --addr=' + MAC_ADDR + ' -d')
  358. dut.expect_exact('Command returned non-zero error code: 0x1')
  359. # Invalid port interval number
  360. dut.write('add --addr=' + MAC_ADDR + ' -p 0')
  361. dut.expect_exact('Invalid port number')
  362. dut.expect_exact('Command returned non-zero error code: 0x1')
  363. dut.write('add --addr=' + MAC_ADDR + ' -p ' + str(BR_PORTS_NUM + 1))
  364. dut.expect_exact('Invalid port number')
  365. dut.expect_exact('Command returned non-zero error code: 0x1')
  366. # try to add more FDB entries than configured max number
  367. for i in range(BR_PORTS_NUM + 1):
  368. dut.write('add --addr=01:02:03:00:00:%02x' % i + ' -d')
  369. if i < BR_PORTS_NUM:
  370. dut.expect_exact('Bridge Config OK!')
  371. else:
  372. dut.expect_exact('Adding FDB entry failed')
  373. dut.expect_exact('Command returned non-zero error code: 0x1')
  374. # try to remove non-existent FDB entry
  375. dut.write('remove --addr=' + MAC_ADDR)
  376. dut.expect_exact('Removing FDB entry failed')
  377. dut.expect_exact('Command returned non-zero error code: 0x1')
  378. # remove dummy entries
  379. for i in range(BR_PORTS_NUM):
  380. dut.write('remove --addr=01:02:03:00:00:%02x' % i)
  381. dut.expect_exact('Bridge Config OK!')
  382. # valid multiple ports at once
  383. dut.write('add --addr=' + MAC_ADDR + ' -c -p 1 -p 2')
  384. dut.expect_exact('Bridge Config OK!')
  385. dut.write('remove --addr=' + MAC_ADDR)
  386. dut.expect_exact('Bridge Config OK!')
  387. dut.write('add --addr=' + MAC_ADDR + ' -p 1 -p 2')
  388. dut.expect_exact('Bridge Config OK!')
  389. dut.write('remove --addr=' + MAC_ADDR)
  390. dut.expect_exact('Bridge Config OK!')
  391. # drop `Endnode` MAC and try to ping it from `Test Host`
  392. logging.info('Drop `Endnode` MAC')
  393. dut.write('add --addr=' + endnode_mac + ' -d')
  394. dut.expect_exact('Bridge Config OK!')
  395. ping_test = subprocess.call(f'ping {endnode_ip} -c 2', shell=True)
  396. if ping_test == 0:
  397. raise Exception('End node should not be reachable')
  398. logging.info('Remove Drop `Endnode` MAC entry')
  399. dut.write('remove --addr=' + endnode_mac)
  400. dut.expect_exact('Bridge Config OK!')
  401. ping_test = subprocess.call(f'ping {endnode_ip} -c 2', shell=True)
  402. if ping_test != 0:
  403. raise Exception('End node is not reachable')
  404. # Since we have only two ports on DUT, it is kind of tricky to verify the forwarding directly with devices'
  405. # specific MAC addresses. However, we can verify it using broadcast address and to observe the system
  406. # behavior in all directions.
  407. # At first, check normal condition
  408. TEST_MSG = 'ESP32 bridge test message'
  409. host_brcast_ip = get_host_brcast_ip_by_interface(host_if, netifaces.AF_INET)
  410. endnode_recv = send_brcast_msg_host_to_endnode(endnode, host_brcast_ip, TEST_MSG)
  411. if endnode_recv != TEST_MSG:
  412. raise Exception('Broadcast message was not received by endnode')
  413. # now, configure forward the broadcast only to port #1
  414. dut.write('add --addr=ff:ff:ff:ff:ff:ff -p 1')
  415. dut.expect_exact('Bridge Config OK!')
  416. # we should not be able to receive a message at endnode (no forward to port #2)...
  417. endnode_recv = send_brcast_msg_host_to_endnode(endnode, host_brcast_ip, TEST_MSG)
  418. if endnode_recv != '':
  419. raise Exception('Broadcast message should not be received by endnode')
  420. # ... but we should be able to do the same in opposite direction (forward to port #1)
  421. host_recv = send_brcast_msg_endnode_to_host(endnode, host_brcast_ip, TEST_MSG)
  422. if host_recv != TEST_MSG:
  423. raise Exception('Broadcast message was not received by host')
  424. # Remove ARP record from Test host computer. ARP is broadcasted, hence Bridge port does not reply to a request since
  425. # it does not receive it (no forward to Bridge port). As a result, Bridge is not pingable.
  426. subprocess.call(f'sudo arp -d {br_ip}', shell=True)
  427. subprocess.call('arp -a', shell=True)
  428. ping_test = subprocess.call(f'ping {br_ip} -c 2', shell=True)
  429. if ping_test == 0:
  430. raise Exception('Bridge should not be reachable')
  431. # Remove current broadcast entry and replace it with extended one which includes Bridge port
  432. # Now, we should be able to ping the Bridge...
  433. dut.write('remove --addr=ff:ff:ff:ff:ff:ff')
  434. dut.expect_exact('Bridge Config OK!')
  435. dut.write('add --addr=ff:ff:ff:ff:ff:ff -p 1 -c')
  436. dut.expect_exact('Bridge Config OK!')
  437. ping_test = subprocess.call(f'ping {br_ip} -c 2', shell=True)
  438. if ping_test != 0:
  439. raise Exception('Bridge is not reachable')
  440. # ...but we should still not be able to receive a message at endnode (no forward to port #2)
  441. endnode_recv = send_brcast_msg_host_to_endnode(endnode, host_brcast_ip, TEST_MSG)
  442. if endnode_recv != '':
  443. raise Exception('Broadcast message should not be received by endnode')
  444. endnode.close()
  445. switch1.close()