211 lines
8.6 KiB
Python
211 lines
8.6 KiB
Python
|
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)
|