diff --git a/RNS/Interfaces/AutoInterface.py b/RNS/Interfaces/AutoInterface.py new file mode 100644 index 0000000..27ee641 --- /dev/null +++ b/RNS/Interfaces/AutoInterface.py @@ -0,0 +1,211 @@ +from .Interface import Interface +import socketserver +import threading +import socket +import struct +import time +import sys +import RNS + + +class AutoInterface(Interface): + DEFAULT_DISCOVERY_PORT = 29716 + DEFAULT_DATA_PORT = 42671 + DEFAULT_GROUP_ID = "reticulum".encode("utf-8") + + SCOPE_LINK = "2" + SCOPE_ADMIN = "4" + SCOPE_SITE = "5" + SCOPE_ORGANISATION = "8" + SCOPE_GLOBAL = "E" + + @staticmethod + def get_address_for_if(name): + import importlib + if importlib.util.find_spec('netifaces') != None: + import netifaces + return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr'] + else: + RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL) + RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL) + RNS.panic() + + @staticmethod + def get_broadcast_for_if(name): + import importlib + if importlib.util.find_spec('netifaces') != None: + import netifaces + return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast'] + else: + RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL) + RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL) + RNS.panic() + + def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None): + import importlib + if importlib.util.find_spec('netifaces') != None: + import netifaces + else: + RNS.log("Using AutoInterface requires the netifaces module.", RNS.LOG_CRITICAL) + RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL) + RNS.panic() + + self.netifaces = netifaces + self.rxb = 0 + self.txb = 0 + self.IN = True + self.OUT = False + self.name = name + self.online = False + self.peers = {} + + self.outbound_udp_socket = None + + if group_id == None: + self.group_id = AutoInterface.DEFAULT_GROUP_ID + else: + self.group_id = group_id.encode("utf-8") + + self.group_hash = RNS.Identity.full_hash(self.group_id) + + if discovery_port == None: + self.discovery_port = AutoInterface.DEFAULT_DISCOVERY_PORT + else: + self.discovery_port = discovery_port + + if data_port == None: + self.data_port = AutoInterface.DEFAULT_DATA_PORT + else: + self.data_port = data_port + + if discovery_scope == None: + self.discovery_scope = AutoInterface.SCOPE_LINK + elif str(discovery_scope).lower() == "link": + self.discovery_scope = AutoInterface.SCOPE_LINK + elif str(discovery_scope).lower() == "admin": + self.discovery_scope = AutoInterface.SCOPE_ADMIN + elif str(discovery_scope).lower() == "site": + self.discovery_scope = AutoInterface.SCOPE_SITE + elif str(discovery_scope).lower() == "organisation": + self.discovery_scope = AutoInterface.SCOPE_ORGANISATION + elif str(discovery_scope).lower() == "global": + self.discovery_scope = AutoInterface.SCOPE_GLOBAL + + suitable_interfaces = 0 + for ifname in self.netifaces.interfaces(): + addresses = self.netifaces.ifaddresses(ifname) + if self.netifaces.AF_INET6 in addresses: + link_local_addr = None + for address in addresses[self.netifaces.AF_INET6]: + if "addr" in address: + if address["addr"].startswith("fe80:"): + link_local_addr = address["addr"] + RNS.log(str(self)+" Selecting link-local address "+str(link_local_addr)+" for interface "+str(ifname), RNS.LOG_EXTREME) + + if link_local_addr == None: + RNS.log(str(self)+" No link-local IPv6 address configured for "+str(ifname)+", skipping interface", RNS.LOG_EXTREME) + else: + g = self.group_hash + gt = "{:02x}".format(g[1]+(g[0]<<8))+":"+"{:02x}".format(g[3]+(g[2]<<8))+":"+"{:02x}".format(g[5]+(g[4]<<8))+":"+"{:02x}".format(g[7]+(g[6]<<8))+":"+"{:02x}".format(g[9]+(g[8]<<8))+":"+"{:02x}".format(g[11]+(g[10]<<8))+":"+"{:02x}".format(g[13]+(g[12]<<8)) + mcast_addr = "ff1"+self.discovery_scope+":"+gt + + RNS.log(str(self)+" Creating multicast discovery listener on "+str(ifname)+" with address "+str(mcast_addr), RNS.LOG_EXTREME) + + # Struct with interface index + if_struct = struct.pack("I", socket.if_nametoindex(ifname)) + + # Set up multicast socket + discovery_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, if_struct) + + # Join multicast group + discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, socket.inet_pton(socket.AF_INET6, mcast_addr) + if_struct) + + # Bind socket + addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM) + discovery_socket.bind(addr_info[0][4]) + + # Set up thread for discovery packets + def discovery_loop(): + self.discovery_handler(discovery_socket, ifname) + + thread = threading.Thread(target=discovery_loop) + thread.setDaemon(True) + thread.start() + + suitable_interfaces += 1 + + if suitable_interfaces == 0: + RNS.log(str(self)+" could not autoconfigure connectivity. You will need to manually configure this instance.", RNS.LOG_WARNING) + else: + self.receives = True + + def handlerFactory(callback): + def createHandler(*args, **keys): + return AutoInterfaceHandler(callback, *args, **keys) + return createHandler + + self.owner = owner + socketserver.UDPServer.address_family = socket.AF_INET6 + address = ("::", self.data_port) + self.server = socketserver.UDPServer(address, handlerFactory(self.processIncoming)) + + thread = threading.Thread(target=self.server.serve_forever) + thread.setDaemon(True) + thread.start() + + self.online = True + + + def discovery_handler(self, socket, ifname): + while True: + data, ipv6_src = socket.recvfrom(1024) + + # TODO: Add real peer discovery integrity check + if data.decode("utf-8") == "peer": + self.add_peer(ipv6_src[0], ifname) + + def add_peer(self, addr, ifname): + if not addr in self.peers: + self.peers[addr] = [ifname, time.time()] + RNS.log(str(self)+" added peer "+str(addr)+" on "+str(ifname), RNS.LOG_EXTREME) + else: + self.heard_peer(addr) + + def heard_peer(self, addr): + self.peers[addr][1] = time.time() + RNS.log(str(self)+" heard peer "+str(addr)+" on "+str(self.peers[addr][0]), RNS.LOG_EXTREME) + + def processIncoming(self, data): + self.rxb += len(data) + self.owner.inbound(data, self) + + def processOutgoing(self,data): + for peer in self.peers: + # TODO: Remove + # RNS.log("Send to "+str(peer)) + try: + if self.outbound_udp_socket == None: + self.outbound_udp_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + + self.outbound_udp_socket.sendto(data, (peer, self.data_port)) + except Exception as e: + RNS.log("Could not transmit on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR) + + + self.txb += len(data) + + + def __str__(self): + return "AutoInterface["+self.name+"]" + +class AutoInterfaceHandler(socketserver.BaseRequestHandler): + def __init__(self, callback, *args, **keys): + self.callback = callback + socketserver.BaseRequestHandler.__init__(self, *args, **keys) + + def handle(self): + data = self.request[0] + self.callback(data) \ No newline at end of file