1*cfb92d14SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*cfb92d14SAndroid Build Coastguard Worker# 3*cfb92d14SAndroid Build Coastguard Worker# Copyright (c) 2022, The OpenThread Authors. 4*cfb92d14SAndroid Build Coastguard Worker# All rights reserved. 5*cfb92d14SAndroid Build Coastguard Worker# 6*cfb92d14SAndroid Build Coastguard Worker# Redistribution and use in source and binary forms, with or without 7*cfb92d14SAndroid Build Coastguard Worker# modification, are permitted provided that the following conditions are met: 8*cfb92d14SAndroid Build Coastguard Worker# 1. Redistributions of source code must retain the above copyright 9*cfb92d14SAndroid Build Coastguard Worker# notice, this list of conditions and the following disclaimer. 10*cfb92d14SAndroid Build Coastguard Worker# 2. Redistributions in binary form must reproduce the above copyright 11*cfb92d14SAndroid Build Coastguard Worker# notice, this list of conditions and the following disclaimer in the 12*cfb92d14SAndroid Build Coastguard Worker# documentation and/or other materials provided with the distribution. 13*cfb92d14SAndroid Build Coastguard Worker# 3. Neither the name of the copyright holder nor the 14*cfb92d14SAndroid Build Coastguard Worker# names of its contributors may be used to endorse or promote products 15*cfb92d14SAndroid Build Coastguard Worker# derived from this software without specific prior written permission. 16*cfb92d14SAndroid Build Coastguard Worker# 17*cfb92d14SAndroid Build Coastguard Worker# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18*cfb92d14SAndroid Build Coastguard Worker# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19*cfb92d14SAndroid Build Coastguard Worker# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20*cfb92d14SAndroid Build Coastguard Worker# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21*cfb92d14SAndroid Build Coastguard Worker# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22*cfb92d14SAndroid Build Coastguard Worker# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23*cfb92d14SAndroid Build Coastguard Worker# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24*cfb92d14SAndroid Build Coastguard Worker# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25*cfb92d14SAndroid Build Coastguard Worker# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26*cfb92d14SAndroid Build Coastguard Worker# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27*cfb92d14SAndroid Build Coastguard Worker# POSSIBILITY OF SUCH DAMAGE. 28*cfb92d14SAndroid Build Coastguard Worker# 29*cfb92d14SAndroid Build Coastguard Worker 30*cfb92d14SAndroid Build Coastguard Workerimport argparse 31*cfb92d14SAndroid Build Coastguard Workerimport ctypes 32*cfb92d14SAndroid Build Coastguard Workerimport ctypes.util 33*cfb92d14SAndroid Build Coastguard Workerimport ipaddress 34*cfb92d14SAndroid Build Coastguard Workerimport json 35*cfb92d14SAndroid Build Coastguard Workerimport logging 36*cfb92d14SAndroid Build Coastguard Workerimport os 37*cfb92d14SAndroid Build Coastguard Workerimport signal 38*cfb92d14SAndroid Build Coastguard Workerimport socket 39*cfb92d14SAndroid Build Coastguard Workerimport struct 40*cfb92d14SAndroid Build Coastguard Workerimport subprocess 41*cfb92d14SAndroid Build Coastguard Workerimport sys 42*cfb92d14SAndroid Build Coastguard Workerfrom typing import Iterable 43*cfb92d14SAndroid Build Coastguard Workerimport yaml 44*cfb92d14SAndroid Build Coastguard Worker 45*cfb92d14SAndroid Build Coastguard Workerfrom otbr_sim import otbr_docker 46*cfb92d14SAndroid Build Coastguard Worker 47*cfb92d14SAndroid Build Coastguard WorkerGROUP = 'ff02::114' 48*cfb92d14SAndroid Build Coastguard WorkerPORT = 12345 49*cfb92d14SAndroid Build Coastguard Worker 50*cfb92d14SAndroid Build Coastguard Worker 51*cfb92d14SAndroid Build Coastguard Workerdef if_nametoindex(ifname: str) -> int: 52*cfb92d14SAndroid Build Coastguard Worker libc = ctypes.CDLL(ctypes.util.find_library('c')) 53*cfb92d14SAndroid Build Coastguard Worker ret = libc.if_nametoindex(ifname.encode('ascii')) 54*cfb92d14SAndroid Build Coastguard Worker if not ret: 55*cfb92d14SAndroid Build Coastguard Worker raise RuntimeError('Invalid interface name') 56*cfb92d14SAndroid Build Coastguard Worker return ret 57*cfb92d14SAndroid Build Coastguard Worker 58*cfb92d14SAndroid Build Coastguard Worker 59*cfb92d14SAndroid Build Coastguard Workerdef get_ipaddr(ifname: str) -> str: 60*cfb92d14SAndroid Build Coastguard Worker for line in os.popen(f'ip addr list dev {ifname} | grep inet | grep global'): 61*cfb92d14SAndroid Build Coastguard Worker addr = line.strip().split()[1] 62*cfb92d14SAndroid Build Coastguard Worker return addr.split('/')[0] 63*cfb92d14SAndroid Build Coastguard Worker raise RuntimeError(f'No IP address on dev {ifname}') 64*cfb92d14SAndroid Build Coastguard Worker 65*cfb92d14SAndroid Build Coastguard Worker 66*cfb92d14SAndroid Build Coastguard Workerdef init_socket(ifname: str, group: str, port: int) -> socket.socket: 67*cfb92d14SAndroid Build Coastguard Worker # Look up multicast group address in name server and find out IP version 68*cfb92d14SAndroid Build Coastguard Worker addrinfo = socket.getaddrinfo(group, None)[0] 69*cfb92d14SAndroid Build Coastguard Worker assert addrinfo[0] == socket.AF_INET6 70*cfb92d14SAndroid Build Coastguard Worker 71*cfb92d14SAndroid Build Coastguard Worker # Create a socket 72*cfb92d14SAndroid Build Coastguard Worker s = socket.socket(addrinfo[0], socket.SOCK_DGRAM) 73*cfb92d14SAndroid Build Coastguard Worker s.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, (ifname + '\0').encode('ascii')) 74*cfb92d14SAndroid Build Coastguard Worker 75*cfb92d14SAndroid Build Coastguard Worker # Bind it to the port 76*cfb92d14SAndroid Build Coastguard Worker s.bind((group, port)) 77*cfb92d14SAndroid Build Coastguard Worker 78*cfb92d14SAndroid Build Coastguard Worker group_bin = socket.inet_pton(addrinfo[0], addrinfo[4][0]) 79*cfb92d14SAndroid Build Coastguard Worker # Join group 80*cfb92d14SAndroid Build Coastguard Worker interface_index = if_nametoindex(ifname) 81*cfb92d14SAndroid Build Coastguard Worker mreq = group_bin + struct.pack('@I', interface_index) 82*cfb92d14SAndroid Build Coastguard Worker s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) 83*cfb92d14SAndroid Build Coastguard Worker 84*cfb92d14SAndroid Build Coastguard Worker return s 85*cfb92d14SAndroid Build Coastguard Worker 86*cfb92d14SAndroid Build Coastguard Worker 87*cfb92d14SAndroid Build Coastguard Workerdef _advertise(s: socket.socket, dst, info): 88*cfb92d14SAndroid Build Coastguard Worker logging.info('Advertise: %r', info) 89*cfb92d14SAndroid Build Coastguard Worker s.sendto(json.dumps(info).encode('utf-8'), dst) 90*cfb92d14SAndroid Build Coastguard Worker 91*cfb92d14SAndroid Build Coastguard Worker 92*cfb92d14SAndroid Build Coastguard Workerdef advertise_devices(s: socket.socket, dst, ven: str, add: str, nodeids: Iterable[int], tag: str): 93*cfb92d14SAndroid Build Coastguard Worker for nodeid in nodeids: 94*cfb92d14SAndroid Build Coastguard Worker info = { 95*cfb92d14SAndroid Build Coastguard Worker 'ven': ven, 96*cfb92d14SAndroid Build Coastguard Worker 'mod': 'OpenThread', 97*cfb92d14SAndroid Build Coastguard Worker 'ver': '4', 98*cfb92d14SAndroid Build Coastguard Worker 'add': f'{tag}_{nodeid}@{add}', 99*cfb92d14SAndroid Build Coastguard Worker 'por': 22, 100*cfb92d14SAndroid Build Coastguard Worker } 101*cfb92d14SAndroid Build Coastguard Worker _advertise(s, dst, info) 102*cfb92d14SAndroid Build Coastguard Worker 103*cfb92d14SAndroid Build Coastguard Worker 104*cfb92d14SAndroid Build Coastguard Workerdef advertise_sniffers(s: socket.socket, dst, add: str, ports: Iterable[int]): 105*cfb92d14SAndroid Build Coastguard Worker for port in ports: 106*cfb92d14SAndroid Build Coastguard Worker info = { 107*cfb92d14SAndroid Build Coastguard Worker 'add': add, 108*cfb92d14SAndroid Build Coastguard Worker 'por': port, 109*cfb92d14SAndroid Build Coastguard Worker } 110*cfb92d14SAndroid Build Coastguard Worker _advertise(s, dst, info) 111*cfb92d14SAndroid Build Coastguard Worker 112*cfb92d14SAndroid Build Coastguard Worker 113*cfb92d14SAndroid Build Coastguard Workerdef start_sniffer(addr: str, port: int, ot_path: str, max_nodes_num: int) -> subprocess.Popen: 114*cfb92d14SAndroid Build Coastguard Worker if isinstance(ipaddress.ip_address(addr), ipaddress.IPv6Address): 115*cfb92d14SAndroid Build Coastguard Worker server = f'[{addr}]:{port}' 116*cfb92d14SAndroid Build Coastguard Worker else: 117*cfb92d14SAndroid Build Coastguard Worker server = f'{addr}:{port}' 118*cfb92d14SAndroid Build Coastguard Worker 119*cfb92d14SAndroid Build Coastguard Worker cmd = [ 120*cfb92d14SAndroid Build Coastguard Worker 'python3', 121*cfb92d14SAndroid Build Coastguard Worker os.path.join(ot_path, 'tools/harness-simulation/posix/sniffer_sim/sniffer.py'), 122*cfb92d14SAndroid Build Coastguard Worker '--grpc-server', 123*cfb92d14SAndroid Build Coastguard Worker server, 124*cfb92d14SAndroid Build Coastguard Worker '--max-nodes-num', 125*cfb92d14SAndroid Build Coastguard Worker str(max_nodes_num), 126*cfb92d14SAndroid Build Coastguard Worker ] 127*cfb92d14SAndroid Build Coastguard Worker logging.info('Executing command: %s', ' '.join(cmd)) 128*cfb92d14SAndroid Build Coastguard Worker return subprocess.Popen(cmd) 129*cfb92d14SAndroid Build Coastguard Worker 130*cfb92d14SAndroid Build Coastguard Worker 131*cfb92d14SAndroid Build Coastguard Workerdef main(): 132*cfb92d14SAndroid Build Coastguard Worker logging.basicConfig(level=logging.INFO) 133*cfb92d14SAndroid Build Coastguard Worker 134*cfb92d14SAndroid Build Coastguard Worker # Parse arguments 135*cfb92d14SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 136*cfb92d14SAndroid Build Coastguard Worker parser.add_argument('-c', 137*cfb92d14SAndroid Build Coastguard Worker '--config', 138*cfb92d14SAndroid Build Coastguard Worker dest='config', 139*cfb92d14SAndroid Build Coastguard Worker type=str, 140*cfb92d14SAndroid Build Coastguard Worker required=True, 141*cfb92d14SAndroid Build Coastguard Worker help='the path of the configuration JSON file') 142*cfb92d14SAndroid Build Coastguard Worker args = parser.parse_args() 143*cfb92d14SAndroid Build Coastguard Worker with open(args.config, 'rt') as f: 144*cfb92d14SAndroid Build Coastguard Worker config = yaml.safe_load(f) 145*cfb92d14SAndroid Build Coastguard Worker 146*cfb92d14SAndroid Build Coastguard Worker ot_path = config['ot_path'] 147*cfb92d14SAndroid Build Coastguard Worker ot_build = config['ot_build'] 148*cfb92d14SAndroid Build Coastguard Worker max_nodes_num = ot_build['max_number'] 149*cfb92d14SAndroid Build Coastguard Worker # No test case requires more than 2 sniffers 150*cfb92d14SAndroid Build Coastguard Worker MAX_SNIFFER_NUM = 2 151*cfb92d14SAndroid Build Coastguard Worker 152*cfb92d14SAndroid Build Coastguard Worker ot_devices = [(item['tag'], item['number']) for item in ot_build['ot']] 153*cfb92d14SAndroid Build Coastguard Worker otbr_devices = [(item['tag'], item['number']) for item in ot_build['otbr']] 154*cfb92d14SAndroid Build Coastguard Worker ot_nodes_num = sum(x[1] for x in ot_devices) 155*cfb92d14SAndroid Build Coastguard Worker otbr_nodes_num = sum(x[1] for x in otbr_devices) 156*cfb92d14SAndroid Build Coastguard Worker nodes_num = ot_nodes_num + otbr_nodes_num 157*cfb92d14SAndroid Build Coastguard Worker sniffer_num = config['sniffer']['number'] 158*cfb92d14SAndroid Build Coastguard Worker 159*cfb92d14SAndroid Build Coastguard Worker # Check validation of numbers 160*cfb92d14SAndroid Build Coastguard Worker if not all(0 <= x[1] <= max_nodes_num for x in ot_devices): 161*cfb92d14SAndroid Build Coastguard Worker raise ValueError(f'The number of devices of each OT version should be between 0 and {max_nodes_num}') 162*cfb92d14SAndroid Build Coastguard Worker 163*cfb92d14SAndroid Build Coastguard Worker if not all(0 <= x[1] <= max_nodes_num for x in otbr_devices): 164*cfb92d14SAndroid Build Coastguard Worker raise ValueError(f'The number of devices of each OTBR version should be between 0 and {max_nodes_num}') 165*cfb92d14SAndroid Build Coastguard Worker 166*cfb92d14SAndroid Build Coastguard Worker if not 1 <= nodes_num <= max_nodes_num: 167*cfb92d14SAndroid Build Coastguard Worker raise ValueError(f'The number of devices should be between 1 and {max_nodes_num}') 168*cfb92d14SAndroid Build Coastguard Worker 169*cfb92d14SAndroid Build Coastguard Worker if not 1 <= sniffer_num <= MAX_SNIFFER_NUM: 170*cfb92d14SAndroid Build Coastguard Worker raise ValueError(f'The number of sniffers should be between 1 and {MAX_SNIFFER_NUM}') 171*cfb92d14SAndroid Build Coastguard Worker 172*cfb92d14SAndroid Build Coastguard Worker # Get the local IP address on the specified interface 173*cfb92d14SAndroid Build Coastguard Worker ifname = config['discovery_ifname'] 174*cfb92d14SAndroid Build Coastguard Worker addr = get_ipaddr(ifname) 175*cfb92d14SAndroid Build Coastguard Worker 176*cfb92d14SAndroid Build Coastguard Worker # Start the sniffer 177*cfb92d14SAndroid Build Coastguard Worker sniffer_server_port_base = config['sniffer']['server_port_base'] 178*cfb92d14SAndroid Build Coastguard Worker sniffer_procs = [] 179*cfb92d14SAndroid Build Coastguard Worker for i in range(sniffer_num): 180*cfb92d14SAndroid Build Coastguard Worker sniffer_procs.append(start_sniffer(addr, i + sniffer_server_port_base, ot_path, max_nodes_num)) 181*cfb92d14SAndroid Build Coastguard Worker 182*cfb92d14SAndroid Build Coastguard Worker # OTBR firewall scripts create rules inside the Docker container 183*cfb92d14SAndroid Build Coastguard Worker # Run modprobe to load the kernel modules for iptables 184*cfb92d14SAndroid Build Coastguard Worker subprocess.run(['sudo', 'modprobe', 'ip6table_filter']) 185*cfb92d14SAndroid Build Coastguard Worker # Start the BRs 186*cfb92d14SAndroid Build Coastguard Worker otbr_dockers = [] 187*cfb92d14SAndroid Build Coastguard Worker nodeid = ot_nodes_num 188*cfb92d14SAndroid Build Coastguard Worker for item in ot_build['otbr']: 189*cfb92d14SAndroid Build Coastguard Worker tag = item['tag'] 190*cfb92d14SAndroid Build Coastguard Worker ot_rcp_path = os.path.join(ot_path, item['rcp_subpath'], 'examples/apps/ncp/ot-rcp') 191*cfb92d14SAndroid Build Coastguard Worker docker_image = item['docker_image'] 192*cfb92d14SAndroid Build Coastguard Worker for _ in range(item['number']): 193*cfb92d14SAndroid Build Coastguard Worker nodeid += 1 194*cfb92d14SAndroid Build Coastguard Worker otbr_dockers.append( 195*cfb92d14SAndroid Build Coastguard Worker otbr_docker.OtbrDocker(nodeid=nodeid, 196*cfb92d14SAndroid Build Coastguard Worker ot_path=ot_path, 197*cfb92d14SAndroid Build Coastguard Worker ot_rcp_path=ot_rcp_path, 198*cfb92d14SAndroid Build Coastguard Worker docker_image=docker_image, 199*cfb92d14SAndroid Build Coastguard Worker docker_name=f'{tag}_{nodeid}')) 200*cfb92d14SAndroid Build Coastguard Worker 201*cfb92d14SAndroid Build Coastguard Worker s = init_socket(ifname, GROUP, PORT) 202*cfb92d14SAndroid Build Coastguard Worker 203*cfb92d14SAndroid Build Coastguard Worker logging.info('Advertising on interface %s group %s ...', ifname, GROUP) 204*cfb92d14SAndroid Build Coastguard Worker 205*cfb92d14SAndroid Build Coastguard Worker # Terminate all sniffer simulation server processes and then exit 206*cfb92d14SAndroid Build Coastguard Worker def exit_handler(signum, context): 207*cfb92d14SAndroid Build Coastguard Worker # Return code is non-zero if any return code of the processes is non-zero 208*cfb92d14SAndroid Build Coastguard Worker ret = 0 209*cfb92d14SAndroid Build Coastguard Worker for sniffer_proc in sniffer_procs: 210*cfb92d14SAndroid Build Coastguard Worker sniffer_proc.terminate() 211*cfb92d14SAndroid Build Coastguard Worker ret = max(ret, sniffer_proc.wait()) 212*cfb92d14SAndroid Build Coastguard Worker 213*cfb92d14SAndroid Build Coastguard Worker for otbr in otbr_dockers: 214*cfb92d14SAndroid Build Coastguard Worker otbr.close() 215*cfb92d14SAndroid Build Coastguard Worker 216*cfb92d14SAndroid Build Coastguard Worker sys.exit(ret) 217*cfb92d14SAndroid Build Coastguard Worker 218*cfb92d14SAndroid Build Coastguard Worker signal.signal(signal.SIGINT, exit_handler) 219*cfb92d14SAndroid Build Coastguard Worker signal.signal(signal.SIGTERM, exit_handler) 220*cfb92d14SAndroid Build Coastguard Worker 221*cfb92d14SAndroid Build Coastguard Worker # Loop, printing any data we receive 222*cfb92d14SAndroid Build Coastguard Worker while True: 223*cfb92d14SAndroid Build Coastguard Worker data, src = s.recvfrom(64) 224*cfb92d14SAndroid Build Coastguard Worker 225*cfb92d14SAndroid Build Coastguard Worker if data == b'BBR': 226*cfb92d14SAndroid Build Coastguard Worker logging.info('Received OpenThread simulation query, advertising') 227*cfb92d14SAndroid Build Coastguard Worker 228*cfb92d14SAndroid Build Coastguard Worker nodeid = 1 229*cfb92d14SAndroid Build Coastguard Worker for ven, devices in [('OpenThread_Sim', ot_devices), ('OpenThread_BR_Sim', otbr_devices)]: 230*cfb92d14SAndroid Build Coastguard Worker for tag, number in devices: 231*cfb92d14SAndroid Build Coastguard Worker advertise_devices(s, src, ven=ven, add=addr, nodeids=range(nodeid, nodeid + number), tag=tag) 232*cfb92d14SAndroid Build Coastguard Worker nodeid += number 233*cfb92d14SAndroid Build Coastguard Worker 234*cfb92d14SAndroid Build Coastguard Worker elif data == b'Sniffer': 235*cfb92d14SAndroid Build Coastguard Worker logging.info('Received sniffer simulation query, advertising') 236*cfb92d14SAndroid Build Coastguard Worker advertise_sniffers(s, 237*cfb92d14SAndroid Build Coastguard Worker src, 238*cfb92d14SAndroid Build Coastguard Worker add=addr, 239*cfb92d14SAndroid Build Coastguard Worker ports=range(sniffer_server_port_base, sniffer_server_port_base + sniffer_num)) 240*cfb92d14SAndroid Build Coastguard Worker 241*cfb92d14SAndroid Build Coastguard Worker else: 242*cfb92d14SAndroid Build Coastguard Worker logging.warning('Received %r, but ignored', data) 243*cfb92d14SAndroid Build Coastguard Worker 244*cfb92d14SAndroid Build Coastguard Worker 245*cfb92d14SAndroid Build Coastguard Workerif __name__ == '__main__': 246*cfb92d14SAndroid Build Coastguard Worker main() 247