mirror of
https://github.com/markqvist/Reticulum.git
synced 2024-11-23 06:00:18 +00:00
Added auto interface
This commit is contained in:
parent
1d180a96f6
commit
c18ebed419
@ -17,31 +17,11 @@ class AutoInterface(Interface):
|
|||||||
SCOPE_ADMIN = "4"
|
SCOPE_ADMIN = "4"
|
||||||
SCOPE_SITE = "5"
|
SCOPE_SITE = "5"
|
||||||
SCOPE_ORGANISATION = "8"
|
SCOPE_ORGANISATION = "8"
|
||||||
SCOPE_GLOBAL = "E"
|
SCOPE_GLOBAL = "e"
|
||||||
|
|
||||||
@staticmethod
|
PEERING_TIMEOUT = 120
|
||||||
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 __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None):
|
||||||
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
|
import importlib
|
||||||
if importlib.util.find_spec('netifaces') != None:
|
if importlib.util.find_spec('netifaces') != None:
|
||||||
import netifaces
|
import netifaces
|
||||||
@ -58,16 +38,33 @@ class AutoInterface(Interface):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.online = False
|
self.online = False
|
||||||
self.peers = {}
|
self.peers = {}
|
||||||
|
self.link_local_addresses = []
|
||||||
|
self.adopted_interfaces = {}
|
||||||
|
|
||||||
self.outbound_udp_socket = None
|
self.outbound_udp_socket = None
|
||||||
|
|
||||||
|
self.announce_interval = AutoInterface.PEERING_TIMEOUT/3.0
|
||||||
|
self.peer_job_interval = AutoInterface.PEERING_TIMEOUT/2.0
|
||||||
|
self.peering_timeout = AutoInterface.PEERING_TIMEOUT
|
||||||
|
|
||||||
|
if allowed_interfaces == None:
|
||||||
|
self.allowed_interfaces = []
|
||||||
|
else:
|
||||||
|
self.allowed_interfaces = allowed_interfaces
|
||||||
|
|
||||||
|
if ignored_interfaces == None:
|
||||||
|
self.ignored_interfaces = []
|
||||||
|
else:
|
||||||
|
self.ignored_interfaces = ignored_interfaces
|
||||||
|
RNS.log("aifs_arg: "+str(allowed_interfaces))
|
||||||
|
RNS.log("iifs_arg: "+str(ignored_interfaces))
|
||||||
|
RNS.log("Ignored ifs: "+str(self.ignored_interfaces))
|
||||||
|
|
||||||
if group_id == None:
|
if group_id == None:
|
||||||
self.group_id = AutoInterface.DEFAULT_GROUP_ID
|
self.group_id = AutoInterface.DEFAULT_GROUP_ID
|
||||||
else:
|
else:
|
||||||
self.group_id = group_id.encode("utf-8")
|
self.group_id = group_id.encode("utf-8")
|
||||||
|
|
||||||
self.group_hash = RNS.Identity.full_hash(self.group_id)
|
|
||||||
|
|
||||||
if discovery_port == None:
|
if discovery_port == None:
|
||||||
self.discovery_port = AutoInterface.DEFAULT_DISCOVERY_PORT
|
self.discovery_port = AutoInterface.DEFAULT_DISCOVERY_PORT
|
||||||
else:
|
else:
|
||||||
@ -91,51 +88,68 @@ class AutoInterface(Interface):
|
|||||||
elif str(discovery_scope).lower() == "global":
|
elif str(discovery_scope).lower() == "global":
|
||||||
self.discovery_scope = AutoInterface.SCOPE_GLOBAL
|
self.discovery_scope = AutoInterface.SCOPE_GLOBAL
|
||||||
|
|
||||||
|
self.group_hash = RNS.Identity.full_hash(self.group_id)
|
||||||
|
g = self.group_hash
|
||||||
|
#gt = "{:02x}".format(g[1]+(g[0]<<8))
|
||||||
|
gt = "0"
|
||||||
|
gt += ":"+"{:02x}".format(g[3]+(g[2]<<8))
|
||||||
|
gt += ":"+"{:02x}".format(g[5]+(g[4]<<8))
|
||||||
|
gt += ":"+"{:02x}".format(g[7]+(g[6]<<8))
|
||||||
|
gt += ":"+"{:02x}".format(g[9]+(g[8]<<8))
|
||||||
|
gt += ":"+"{:02x}".format(g[11]+(g[10]<<8))
|
||||||
|
gt += ":"+"{:02x}".format(g[13]+(g[12]<<8))
|
||||||
|
self.mcast_discovery_address = "ff1"+self.discovery_scope+":"+gt
|
||||||
|
|
||||||
suitable_interfaces = 0
|
suitable_interfaces = 0
|
||||||
for ifname in self.netifaces.interfaces():
|
for ifname in self.netifaces.interfaces():
|
||||||
addresses = self.netifaces.ifaddresses(ifname)
|
if ifname in self.ignored_interfaces:
|
||||||
if self.netifaces.AF_INET6 in addresses:
|
RNS.log(str(self)+" ignoring disallowed interface "+str(ifname), RNS.LOG_EXTREME)
|
||||||
link_local_addr = None
|
else:
|
||||||
for address in addresses[self.netifaces.AF_INET6]:
|
if len(self.allowed_interfaces) > 0 and not ifname in self.allowed_interfaces:
|
||||||
if "addr" in address:
|
RNS.log(str(self)+" ignoring interface "+str(ifname)+" since it was not allowed", RNS.LOG_EXTREME)
|
||||||
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:
|
else:
|
||||||
g = self.group_hash
|
addresses = self.netifaces.ifaddresses(ifname)
|
||||||
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))
|
if self.netifaces.AF_INET6 in addresses:
|
||||||
mcast_addr = "ff1"+self.discovery_scope+":"+gt
|
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"]
|
||||||
|
self.link_local_addresses.append(link_local_addr.split("%")[0])
|
||||||
|
self.adopted_interfaces[ifname] = link_local_addr.split("%")[0]
|
||||||
|
RNS.log(str(self)+" Selecting link-local address "+str(link_local_addr)+" for interface "+str(ifname), RNS.LOG_EXTREME)
|
||||||
|
|
||||||
RNS.log(str(self)+" Creating multicast discovery listener on "+str(ifname)+" with address "+str(mcast_addr), 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:
|
||||||
|
mcast_addr = self.mcast_discovery_address
|
||||||
|
RNS.log(str(self)+" Creating multicast discovery listener on "+str(ifname)+" with address "+str(mcast_addr), RNS.LOG_EXTREME)
|
||||||
|
|
||||||
# Struct with interface index
|
# Struct with interface index
|
||||||
if_struct = struct.pack("I", socket.if_nametoindex(ifname))
|
if_struct = struct.pack("I", socket.if_nametoindex(ifname))
|
||||||
|
|
||||||
# Set up multicast socket
|
# Set up multicast socket
|
||||||
discovery_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
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_REUSEADDR, 1)
|
||||||
discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||||
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, if_struct)
|
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, if_struct)
|
||||||
|
|
||||||
# Join multicast group
|
# Join multicast group
|
||||||
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, socket.inet_pton(socket.AF_INET6, mcast_addr) + if_struct)
|
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, socket.inet_pton(socket.AF_INET6, mcast_addr) + if_struct)
|
||||||
|
|
||||||
# Bind socket
|
# Bind socket
|
||||||
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||||
discovery_socket.bind(addr_info[0][4])
|
discovery_socket.bind(addr_info[0][4])
|
||||||
|
|
||||||
# Set up thread for discovery packets
|
# Set up thread for discovery packets
|
||||||
def discovery_loop():
|
def discovery_loop():
|
||||||
self.discovery_handler(discovery_socket, ifname)
|
self.discovery_handler(discovery_socket, ifname)
|
||||||
|
|
||||||
thread = threading.Thread(target=discovery_loop)
|
thread = threading.Thread(target=discovery_loop)
|
||||||
thread.setDaemon(True)
|
thread.setDaemon(True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
suitable_interfaces += 1
|
suitable_interfaces += 1
|
||||||
|
|
||||||
if suitable_interfaces == 0:
|
if suitable_interfaces == 0:
|
||||||
RNS.log(str(self)+" could not autoconfigure connectivity. You will need to manually configure this instance.", RNS.LOG_WARNING)
|
RNS.log(str(self)+" could not autoconfigure connectivity. You will need to manually configure this instance.", RNS.LOG_WARNING)
|
||||||
@ -156,27 +170,71 @@ class AutoInterface(Interface):
|
|||||||
thread.setDaemon(True)
|
thread.setDaemon(True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
job_thread = threading.Thread(target=self.peer_jobs)
|
||||||
|
job_thread.setDaemon(True)
|
||||||
|
job_thread.start()
|
||||||
|
|
||||||
self.online = True
|
self.online = True
|
||||||
|
|
||||||
|
|
||||||
def discovery_handler(self, socket, ifname):
|
def discovery_handler(self, socket, ifname):
|
||||||
|
def announce_loop():
|
||||||
|
self.announce_handler(ifname)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=announce_loop)
|
||||||
|
thread.setDaemon(True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
data, ipv6_src = socket.recvfrom(1024)
|
data, ipv6_src = socket.recvfrom(1024)
|
||||||
|
expected_hash = RNS.Identity.full_hash(self.group_id+ipv6_src[0].encode("utf-8"))
|
||||||
# TODO: Add real peer discovery integrity check
|
if data == expected_hash:
|
||||||
if data.decode("utf-8") == "peer":
|
|
||||||
self.add_peer(ipv6_src[0], ifname)
|
self.add_peer(ipv6_src[0], ifname)
|
||||||
|
else:
|
||||||
|
RNS.log(str(self)+" received peering packet on "+str(ifname)+" from "+str(ipv6_src[0])+", but authentication hash was incorrect.", RNS.LOG_DEBUG)
|
||||||
|
|
||||||
|
def peer_jobs(self):
|
||||||
|
while True:
|
||||||
|
time.sleep(self.peer_job_interval)
|
||||||
|
now = time.time()
|
||||||
|
timed_out_peers = []
|
||||||
|
for peer_addr in self.peers:
|
||||||
|
peer = self.peers[peer_addr]
|
||||||
|
last_heard = peer[1]
|
||||||
|
if now > last_heard+self.peering_timeout:
|
||||||
|
timed_out_peers.append(peer_addr)
|
||||||
|
|
||||||
|
for peer_addr in timed_out_peers:
|
||||||
|
self.peers.pop(peer_addr)
|
||||||
|
RNS.log(str(self)+" removed peer "+str(peer_addr)+" due to timeout.", RNS.LOG_DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
def announce_handler(self, ifname):
|
||||||
|
while True:
|
||||||
|
self.peer_announce(ifname)
|
||||||
|
time.sleep(self.announce_interval)
|
||||||
|
|
||||||
|
def peer_announce(self, ifname):
|
||||||
|
link_local_address = self.adopted_interfaces[ifname]
|
||||||
|
discovery_token = RNS.Identity.full_hash(self.group_id+link_local_address.encode("utf-8"))
|
||||||
|
announce_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||||
|
addr_info = socket.getaddrinfo(self.mcast_discovery_address, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||||
|
|
||||||
|
ifis = struct.pack("I", socket.if_nametoindex(ifname))
|
||||||
|
announce_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, ifis)
|
||||||
|
announce_socket.sendto(discovery_token, addr_info[0][4])
|
||||||
|
|
||||||
def add_peer(self, addr, ifname):
|
def add_peer(self, addr, ifname):
|
||||||
if not addr in self.peers:
|
if not addr in self.link_local_addresses:
|
||||||
self.peers[addr] = [ifname, time.time()]
|
if not addr in self.peers:
|
||||||
RNS.log(str(self)+" added peer "+str(addr)+" on "+str(ifname), RNS.LOG_EXTREME)
|
self.peers[addr] = [ifname, time.time()]
|
||||||
else:
|
RNS.log(str(self)+" added peer "+str(addr)+" on "+str(ifname), RNS.LOG_DEBUG)
|
||||||
self.heard_peer(addr)
|
else:
|
||||||
|
self.refresh_peer(addr)
|
||||||
|
|
||||||
def heard_peer(self, addr):
|
def refresh_peer(self, addr):
|
||||||
self.peers[addr][1] = time.time()
|
self.peers[addr][1] = time.time()
|
||||||
RNS.log(str(self)+" heard peer "+str(addr)+" on "+str(self.peers[addr][0]), RNS.LOG_EXTREME)
|
RNS.log(str(self)+" refreshed peer "+str(addr)+" on "+str(self.peers[addr][0]), RNS.LOG_EXTREME)
|
||||||
|
|
||||||
def processIncoming(self, data):
|
def processIncoming(self, data):
|
||||||
self.rxb += len(data)
|
self.rxb += len(data)
|
||||||
@ -184,8 +242,6 @@ class AutoInterface(Interface):
|
|||||||
|
|
||||||
def processOutgoing(self,data):
|
def processOutgoing(self,data):
|
||||||
for peer in self.peers:
|
for peer in self.peers:
|
||||||
# TODO: Remove
|
|
||||||
# RNS.log("Send to "+str(peer))
|
|
||||||
try:
|
try:
|
||||||
if self.outbound_udp_socket == None:
|
if self.outbound_udp_socket == None:
|
||||||
self.outbound_udp_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
self.outbound_udp_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||||
|
@ -166,9 +166,9 @@ class Reticulum:
|
|||||||
else:
|
else:
|
||||||
RNS.log("Could not load config file, creating default configuration file...")
|
RNS.log("Could not load config file, creating default configuration file...")
|
||||||
self.__create_default_config()
|
self.__create_default_config()
|
||||||
RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and start Reticulum again.")
|
RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and restart Reticulum if needed.")
|
||||||
RNS.log("Exiting now!")
|
import time
|
||||||
exit(1)
|
time.sleep(1.5)
|
||||||
|
|
||||||
self.__apply_config()
|
self.__apply_config()
|
||||||
RNS.log("Configuration loaded from "+self.configpath, RNS.LOG_VERBOSE)
|
RNS.log("Configuration loaded from "+self.configpath, RNS.LOG_VERBOSE)
|
||||||
@ -275,6 +275,33 @@ class Reticulum:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if ("interface_enabled" in c) and c.as_bool("interface_enabled") == True:
|
if ("interface_enabled" in c) and c.as_bool("interface_enabled") == True:
|
||||||
|
if c["type"] == "AutoInterface":
|
||||||
|
group_id = c["group_id"] if "group_id" in c else None
|
||||||
|
discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None
|
||||||
|
discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None
|
||||||
|
data_port = int(c["data_port"]) if "data_port" in c else None
|
||||||
|
allowed_interfaces = c.as_list("interfaces") if "interfaces" in c else None
|
||||||
|
ignored_interfaces = c.as_list("ignored_interfaces") if "ignored_interfaces" in c else None
|
||||||
|
|
||||||
|
interface = AutoInterface.AutoInterface(
|
||||||
|
RNS.Transport,
|
||||||
|
name,
|
||||||
|
group_id,
|
||||||
|
discovery_scope,
|
||||||
|
discovery_port,
|
||||||
|
data_port,
|
||||||
|
allowed_interfaces,
|
||||||
|
ignored_interfaces
|
||||||
|
)
|
||||||
|
|
||||||
|
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||||
|
interface.OUT = True
|
||||||
|
else:
|
||||||
|
interface.OUT = False
|
||||||
|
|
||||||
|
RNS.Transport.interfaces.append(interface)
|
||||||
|
|
||||||
|
|
||||||
if c["type"] == "UDPInterface":
|
if c["type"] == "UDPInterface":
|
||||||
device = c["device"] if "device" in c else None
|
device = c["device"] if "device" in c else None
|
||||||
port = int(c["port"]) if "port" in c else None
|
port = int(c["port"]) if "port" in c else None
|
||||||
@ -517,7 +544,6 @@ class Reticulum:
|
|||||||
if not os.path.isdir(Reticulum.configdir):
|
if not os.path.isdir(Reticulum.configdir):
|
||||||
os.makedirs(Reticulum.configdir)
|
os.makedirs(Reticulum.configdir)
|
||||||
self.config.write()
|
self.config.write()
|
||||||
self.__apply_config()
|
|
||||||
|
|
||||||
def rpc_loop(self):
|
def rpc_loop(self):
|
||||||
while True:
|
while True:
|
||||||
@ -713,16 +739,26 @@ loglevel = 4
|
|||||||
[interfaces]
|
[interfaces]
|
||||||
|
|
||||||
# This interface enables communication with other
|
# This interface enables communication with other
|
||||||
# local Reticulum nodes over UDP. You can modify it
|
# link-local Reticulum nodes over UDP. It does not
|
||||||
# to suit your needs or turn it off completely.
|
# need any functional IP infrastructure like routers
|
||||||
# As a minimum, you should probably specify the
|
# or DHCP servers, but will require that at least link-
|
||||||
# network device you want to communicate on, such
|
# local IPv6 is enabled in your operating system, which
|
||||||
# as eth0 or wlan0.
|
# should be enabled by default in almost any OS. See
|
||||||
|
# the Reticulum Manual for more configuration options.
|
||||||
|
|
||||||
[[Default UDP Interface]]
|
[[Default Interface]]
|
||||||
type = UDPInterface
|
type = AutoInterface
|
||||||
interface_enabled = True
|
interface_enabled = True
|
||||||
outgoing = True
|
outgoing = True
|
||||||
|
|
||||||
|
|
||||||
|
# The following example enables communication with other
|
||||||
|
# local Reticulum peers using UDP broadcasts.
|
||||||
|
|
||||||
|
[[UDP Interface]]
|
||||||
|
type = UDPInterface
|
||||||
|
interface_enabled = False
|
||||||
|
outgoing = True
|
||||||
listen_ip = 0.0.0.0
|
listen_ip = 0.0.0.0
|
||||||
listen_port = 4242
|
listen_port = 4242
|
||||||
forward_ip = 255.255.255.255
|
forward_ip = 255.255.255.255
|
||||||
@ -730,9 +766,7 @@ loglevel = 4
|
|||||||
|
|
||||||
# The above configuration will allow communication
|
# The above configuration will allow communication
|
||||||
# within the local broadcast domains of all local
|
# within the local broadcast domains of all local
|
||||||
# IP interfaces. This is enabled by default as an
|
# IP interfaces.
|
||||||
# easy way to get started, but you might want to
|
|
||||||
# consider altering it to something more specific.
|
|
||||||
|
|
||||||
# Instead of specifying listen_ip, listen_port,
|
# Instead of specifying listen_ip, listen_port,
|
||||||
# forward_ip and forward_port, you can also bind
|
# forward_ip and forward_port, you can also bind
|
||||||
|
Loading…
Reference in New Issue
Block a user