1#!/usr/bin/env python3 2# 3# Copyright (c) 2022, The OpenThread Authors. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 1. Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# 2. Redistributions in binary form must reproduce the above copyright 11# notice, this list of conditions and the following disclaimer in the 12# documentation and/or other materials provided with the distribution. 13# 3. Neither the name of the copyright holder nor the 14# names of its contributors may be used to endorse or promote products 15# derived from this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27# POSSIBILITY OF SUCH DAMAGE. 28# 29 30import os 31import socket 32 33 34class SnifferTransport(object): 35 """ Interface for transport that allows eavesdrop other nodes. """ 36 37 def open(self): 38 """ Open transport. 39 40 Raises: 41 RuntimeError: when transport is already opened or when transport opening failed. 42 """ 43 raise NotImplementedError 44 45 def close(self): 46 """ Close transport. 47 48 Raises: 49 RuntimeError: when transport is already closed. 50 """ 51 raise NotImplementedError 52 53 @property 54 def is_opened(self): 55 """ Check if transport is opened. 56 57 Returns: 58 bool: True if the transport is opened, False in otherwise 59 """ 60 raise NotImplementedError 61 62 def send(self, data, nodeid): 63 """ Send data to the node with nodeid. 64 65 Args: 66 data (bytearray): outcoming data. 67 nodeid (int): node id 68 69 Returns: 70 int: number of sent bytes 71 """ 72 raise NotImplementedError 73 74 def recv(self, bufsize, timeout): 75 """ Receive data sent by other node. 76 77 Args: 78 bufsize (int): size of buffer for incoming data. 79 timeout (float | None): socket timeout. 80 81 Returns: 82 A tuple contains data and node id. 83 84 For example: 85 (bytearray([0x00, 0x01...], 1) 86 87 Raises: 88 socket.timeout: when receiving the packets times out. 89 """ 90 raise NotImplementedError 91 92 93class SnifferSocketTransport(SnifferTransport): 94 """ Socket based implementation of sniffer transport. """ 95 96 BASE_PORT = 9000 97 98 MAX_NETWORK_SIZE = int(os.getenv('MAX_NETWORK_SIZE', '33')) 99 100 PORT_OFFSET = int(os.getenv('PORT_OFFSET', '0')) 101 102 RADIO_GROUP = '224.0.0.116' 103 104 def __init__(self): 105 self._socket = None 106 107 def _nodeid_to_port(self, nodeid: int): 108 return self.BASE_PORT + (self.PORT_OFFSET * (self.MAX_NETWORK_SIZE + 1)) + nodeid 109 110 def _port_to_nodeid(self, port): 111 return (port - self.BASE_PORT - (self.PORT_OFFSET * (self.MAX_NETWORK_SIZE + 1))) 112 113 def open(self): 114 if self.is_opened: 115 raise RuntimeError('Transport is already opened.') 116 117 self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 118 119 if not self.is_opened: 120 raise RuntimeError('Transport opening failed.') 121 122 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 123 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 124 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2 * 1024 * 1024) 125 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2 * 1024 * 1024) 126 self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, 127 socket.inet_aton(self.RADIO_GROUP) + socket.inet_aton('127.0.0.1')) 128 self._socket.bind((self.RADIO_GROUP, self._nodeid_to_port(0))) 129 130 def close(self): 131 if not self.is_opened: 132 raise RuntimeError('Transport is closed.') 133 134 self._socket.close() 135 self._socket = None 136 137 @property 138 def is_opened(self): 139 return bool(self._socket is not None) 140 141 def send(self, data, nodeid): 142 address = ('127.0.0.1', self._nodeid_to_port(nodeid)) 143 144 return self._socket.sendto(data, address) 145 146 def recv(self, bufsize, timeout): 147 self._socket.settimeout(timeout) 148 data, address = self._socket.recvfrom(bufsize) 149 150 nodeid = self._port_to_nodeid(address[1]) 151 152 return bytearray(data), nodeid 153 154 155class SnifferTransportFactory(object): 156 157 def create_transport(self): 158 return SnifferSocketTransport() 159