1#!/usr/bin/env python3 2# 3# Copyright (c) 2016, 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 ctypes 31import os 32import socket 33 34 35class SnifferTransport(object): 36 """ Interface for transport that allows eavesdrop other nodes. """ 37 38 def open(self): 39 """ Open transport. 40 41 Raises: 42 RuntimeError: when transport is already opened or when transport opening failed. 43 """ 44 raise NotImplementedError 45 46 def close(self): 47 """ Close transport. 48 49 Raises: 50 RuntimeError: when transport is already closed. 51 """ 52 raise NotImplementedError 53 54 @property 55 def is_opened(self): 56 """ Check if transport is opened. 57 58 Returns: 59 bool: True if the transport is opened, False in otherwise 60 """ 61 raise NotImplementedError 62 63 def send(self, data, nodeid): 64 """ Send data to the node with nodeid. 65 66 Args: 67 data (bytearray): outcoming data. 68 nodeid (int): node id 69 70 Returns: 71 int: number of sent bytes 72 """ 73 raise NotImplementedError 74 75 def recv(self, bufsize): 76 """ Receive data sent by other node. 77 78 Args: 79 bufsize (int): size of buffer for incoming data. 80 81 Returns: 82 A tuple contains data and node id. 83 84 For example: 85 (bytearray([0x00, 0x01...], 1) 86 """ 87 raise NotImplementedError 88 89 90class SnifferSocketTransport(SnifferTransport): 91 """ Socket based implementation of sniffer transport. """ 92 93 BASE_PORT = 9000 94 95 MAX_NETWORK_SIZE = 33 96 97 PORT_OFFSET = int(os.getenv('PORT_OFFSET', "0")) 98 99 RADIO_GROUP = '224.0.0.116' 100 101 def __init__(self): 102 self._socket = None 103 104 def __del__(self): 105 if not self.is_opened: 106 return 107 108 self.close() 109 110 def _nodeid_to_address(self, nodeid, ip_address=''): 111 return ( 112 ip_address, 113 self.BASE_PORT + (self.PORT_OFFSET * (self.MAX_NETWORK_SIZE + 1)) + nodeid, 114 ) 115 116 def _address_to_nodeid(self, address): 117 _, port = address 118 return (port - self.BASE_PORT - (self.PORT_OFFSET * (self.MAX_NETWORK_SIZE + 1))) 119 120 def open(self): 121 if self.is_opened: 122 raise RuntimeError("Transport is already opened.") 123 124 self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 125 126 if not self.is_opened: 127 raise RuntimeError("Transport opening failed.") 128 129 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 130 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 131 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2 * 1024 * 1024) 132 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2 * 1024 * 1024) 133 self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, 134 socket.inet_aton(self.RADIO_GROUP) + socket.inet_aton('127.0.0.1')) 135 self._socket.bind(self._nodeid_to_address(0)) 136 137 def close(self): 138 if not self.is_opened: 139 raise RuntimeError("Transport is closed.") 140 141 self._socket.close() 142 self._socket = None 143 144 @property 145 def is_opened(self): 146 return bool(self._socket is not None) 147 148 def send(self, data, nodeid): 149 address = self._nodeid_to_address(nodeid) 150 151 return self._socket.sendto(data, address) 152 153 def recv(self, bufsize): 154 data, address = self._socket.recvfrom(bufsize) 155 156 nodeid = self._address_to_nodeid(address) 157 158 return bytearray(data), nodeid 159 160 161class MacFrame(ctypes.Structure): 162 _fields_ = [ 163 ("buffer", ctypes.c_ubyte * 128), 164 ("length", ctypes.c_ubyte), 165 ("nodeid", ctypes.c_uint), 166 ] 167 168 169class SnifferTransportFactory(object): 170 171 def create_transport(self): 172 return SnifferSocketTransport() 173