mirror of
https://github.com/markqvist/Reticulum.git
synced 2024-11-22 21:50:18 +00:00
Implemented AutoInterface outbound traffic and multicast discovery listeners
This commit is contained in:
parent
3d49076602
commit
4241990690
211
RNS/Interfaces/AutoInterface.py
Normal file
211
RNS/Interfaces/AutoInterface.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user