mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-22 20:12:37 -07:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95d3346da6 | |||
| d4aabc8b89 | |||
| d487609dcf | |||
| c96c82f1d1 | |||
| cb023cde40 | |||
| 17be289f37 | |||
| b8105e23ff | |||
| f378d09cbe | |||
| 4dfa62833c | |||
| 2ec6d3ba6c | |||
| 15d027e11e | |||
| 87a274d177 | |||
| f8272793b4 | |||
| 3a215be859 | |||
| 0e1279d012 | |||
| 8ec356a28e | |||
| 49d7808835 | |||
| 48184134e4 | |||
| 987ff0658b | |||
| 27dea7c524 | |||
| 9c6fd132d4 | |||
| 8d58bb62ab | |||
| c357f7a94e | |||
| 4b3ead3db2 | |||
| b62e9af5d4 | |||
| fa82989a2e | |||
| 07a65609b4 | |||
| 257bd95da8 | |||
| 1ccfa9079c | |||
| 57226201ff | |||
| d9419cd895 | |||
| aae10ede72 | |||
| 291b3056cd | |||
| 3f53c89d32 | |||
| 05288d7c97 | |||
| b403441074 | |||
| d3a23e3b00 | |||
| 329d83587e | |||
| 0a4dd64434 | |||
| b96cbf1014 | |||
| 485558cd6b | |||
| 8d93867a22 | |||
| 6b20a98adc | |||
| f3d04ba90f | |||
| 1d2564cedb | |||
| bec8473695 | |||
| 25620415a0 | |||
| b6df952995 | |||
| a72aaf12ca | |||
| b978a993b2 | |||
| 5ae00264e8 | |||
| 5396b80e80 | |||
| fdaa58a6fa |
@@ -1,6 +1,12 @@
|
||||
##########################################################
|
||||
# This RNS example demonstrates a simple speedtest #
|
||||
# program to measure link throughput. #
|
||||
# #
|
||||
# The current configuration is suited for testing fast #
|
||||
# links. If you want to measure slow links like LoRa or #
|
||||
# packet radio, you must significantly lower the #
|
||||
# data_cap variable, which defines how much data is sent #
|
||||
# for each test. #
|
||||
##########################################################
|
||||
|
||||
import os
|
||||
|
||||
@@ -9,6 +9,7 @@ Having no dependencies on traditional networking stacks free up overhead that ha
|
||||
|
||||
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
|
||||
|
||||
## Read The Manual
|
||||
The full documentation for Reticulum is available at [markqvist.github.io/Reticulum/manual/](https://markqvist.github.io/Reticulum/manual/).
|
||||
|
||||
You can also [download the Reticulum manual as a PDF](https://github.com/markqvist/Reticulum/raw/master/docs/Reticulum%20Manual.pdf)
|
||||
@@ -85,19 +86,19 @@ Reticulum implements a range of generalised interface types that covers most of
|
||||
|
||||
|
||||
|
||||
## Feature Roadmap
|
||||
- Stream mode for links
|
||||
- Globally routable multicast
|
||||
## Planned Features
|
||||
- More interface types for even broader compatibility
|
||||
- ESP32 devices (ESP-Now, Bluetooth, etc.)
|
||||
- More LoRa transceivers
|
||||
- AT-compatible modems
|
||||
- AWDL / OWL
|
||||
- HF Modems
|
||||
- CAN-bus
|
||||
- ZeroMQ
|
||||
- MQTT
|
||||
- SPI
|
||||
- i²c
|
||||
- Globally routable multicast
|
||||
|
||||
## Dependencies:
|
||||
- Python 3.6
|
||||
|
||||
@@ -19,9 +19,10 @@ class AutoInterface(Interface):
|
||||
SCOPE_ORGANISATION = "8"
|
||||
SCOPE_GLOBAL = "e"
|
||||
|
||||
PEERING_TIMEOUT = 6.0
|
||||
PEERING_TIMEOUT = 7.5
|
||||
|
||||
DARWIN_IGNORE_IFS = ["awdl0", "llw0", "lo0", "en5"]
|
||||
ANDROID_IGNORE_IFS = ["dummy0", "lo", "tun0"]
|
||||
|
||||
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None):
|
||||
import importlib
|
||||
@@ -42,12 +43,15 @@ class AutoInterface(Interface):
|
||||
self.peers = {}
|
||||
self.link_local_addresses = []
|
||||
self.adopted_interfaces = {}
|
||||
self.multicast_echoes = {}
|
||||
self.timed_out_interfaces = {}
|
||||
|
||||
self.outbound_udp_socket = None
|
||||
|
||||
self.announce_interval = AutoInterface.PEERING_TIMEOUT/4.0
|
||||
self.announce_interval = AutoInterface.PEERING_TIMEOUT/5.0
|
||||
self.peer_job_interval = AutoInterface.PEERING_TIMEOUT*1.1
|
||||
self.peering_timeout = AutoInterface.PEERING_TIMEOUT
|
||||
self.multicast_echo_timeout = AutoInterface.PEERING_TIMEOUT/2
|
||||
|
||||
if allowed_interfaces == None:
|
||||
self.allowed_interfaces = []
|
||||
@@ -101,10 +105,12 @@ class AutoInterface(Interface):
|
||||
|
||||
suitable_interfaces = 0
|
||||
for ifname in self.netifaces.interfaces():
|
||||
if RNS.vendor.platformutils.get_platform() == "darwin" and ifname in AutoInterface.DARWIN_IGNORE_IFS:
|
||||
if RNS.vendor.platformutils.is_darwin() and ifname in AutoInterface.DARWIN_IGNORE_IFS and not ifname in self.allowed_interfaces:
|
||||
RNS.log(str(self)+" skipping Darwin AWDL or tethering interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif RNS.vendor.platformutils.get_platform() == "darwin" and ifname == "lo0":
|
||||
elif RNS.vendor.platformutils.is_darwin() and ifname == "lo0":
|
||||
RNS.log(str(self)+" skipping Darwin loopback interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif RNS.vendor.platformutils.is_android() and ifname in AutoInterface.ANDROID_IGNORE_IFS and not ifname in self.allowed_interfaces:
|
||||
RNS.log(str(self)+" skipping Android system interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif ifname in self.ignored_interfaces:
|
||||
RNS.log(str(self)+" ignoring disallowed interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
else:
|
||||
@@ -120,6 +126,7 @@ class AutoInterface(Interface):
|
||||
link_local_addr = address["addr"]
|
||||
self.link_local_addresses.append(link_local_addr.split("%")[0])
|
||||
self.adopted_interfaces[ifname] = link_local_addr.split("%")[0]
|
||||
self.multicast_echoes[ifname] = time.time()
|
||||
RNS.log(str(self)+" Selecting link-local address "+str(link_local_addr)+" for interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
|
||||
if link_local_addr == None:
|
||||
@@ -212,15 +219,32 @@ class AutoInterface(Interface):
|
||||
time.sleep(self.peer_job_interval)
|
||||
now = time.time()
|
||||
timed_out_peers = []
|
||||
|
||||
# Check for 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)
|
||||
|
||||
# Remove any timed out peers
|
||||
for peer_addr in timed_out_peers:
|
||||
removed_peer = self.peers.pop(peer_addr)
|
||||
RNS.log(str(self)+" removed peer "+str(peer_addr)+" on "+str(removed_peer[0]), RNS.LOG_DEBUG)
|
||||
|
||||
for ifname in self.adopted_interfaces:
|
||||
last_multicast_echo = 0
|
||||
if ifname in self.multicast_echoes:
|
||||
last_multicast_echo = self.multicast_echoes[ifname]
|
||||
|
||||
if now - last_multicast_echo > self.multicast_echo_timeout:
|
||||
if ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == False:
|
||||
RNS.log("Multicast echo timeout for "+str(ifname)+". Carrier lost.", RNS.LOG_WARNING)
|
||||
self.timed_out_interfaces[ifname] = True
|
||||
else:
|
||||
if ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == True:
|
||||
RNS.log(str(self)+" Carrier recovered on "+str(ifname), RNS.LOG_WARNING)
|
||||
self.timed_out_interfaces[ifname] = False
|
||||
|
||||
|
||||
def announce_handler(self, ifname):
|
||||
@@ -229,17 +253,34 @@ class AutoInterface(Interface):
|
||||
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)
|
||||
try:
|
||||
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])
|
||||
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])
|
||||
except Exception as e:
|
||||
if (ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == False) or not ifname in self.timed_out_interfaces:
|
||||
RNS.log(str(self)+" Detected possible carrier loss on "+str(ifname)+": "+str(e), RNS.LOG_WARNING)
|
||||
else:
|
||||
pass
|
||||
|
||||
def add_peer(self, addr, ifname):
|
||||
if not addr in self.link_local_addresses:
|
||||
if addr in self.link_local_addresses:
|
||||
ifname = None
|
||||
for interface_name in self.adopted_interfaces:
|
||||
if self.adopted_interfaces[interface_name] == addr:
|
||||
ifname = interface_name
|
||||
|
||||
if ifname != None:
|
||||
self.multicast_echoes[ifname] = time.time()
|
||||
else:
|
||||
RNS.log(str(self)+" received multicast echo on unexpected interface "+str(ifname), RNS.LOG_WARNING)
|
||||
|
||||
else:
|
||||
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_DEBUG)
|
||||
|
||||
@@ -0,0 +1,593 @@
|
||||
from .Interface import Interface
|
||||
import socketserver
|
||||
import threading
|
||||
import platform
|
||||
import socket
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import RNS
|
||||
import asyncio
|
||||
|
||||
class HDLC():
|
||||
FLAG = 0x7E
|
||||
ESC = 0x7D
|
||||
ESC_MASK = 0x20
|
||||
|
||||
@staticmethod
|
||||
def escape(data):
|
||||
data = data.replace(bytes([HDLC.ESC]), bytes([HDLC.ESC, HDLC.ESC^HDLC.ESC_MASK]))
|
||||
data = data.replace(bytes([HDLC.FLAG]), bytes([HDLC.ESC, HDLC.FLAG^HDLC.ESC_MASK]))
|
||||
return data
|
||||
|
||||
class KISS():
|
||||
FEND = 0xC0
|
||||
FESC = 0xDB
|
||||
TFEND = 0xDC
|
||||
TFESC = 0xDD
|
||||
CMD_DATA = 0x00
|
||||
CMD_UNKNOWN = 0xFE
|
||||
|
||||
@staticmethod
|
||||
def escape(data):
|
||||
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
|
||||
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
|
||||
return data
|
||||
|
||||
# TODO: Neater shutdown of the event loop and
|
||||
# better error handling is needed. Sometimes
|
||||
# errors occur in I2P that leave tunnel setup
|
||||
# hanging indefinitely, and right now we have
|
||||
# no way of catching it. Sometimes the server
|
||||
# and client tasks are also not cancelled on
|
||||
# shutdown, which leads to errors dumped to
|
||||
# the console. This should also be remedied.
|
||||
|
||||
class I2PController:
|
||||
def __init__(self, rns_storagepath):
|
||||
import RNS.vendor.i2plib as i2plib
|
||||
import RNS.vendor.i2plib.utils
|
||||
|
||||
self.client_tunnels = {}
|
||||
self.server_tunnels = {}
|
||||
self.loop = None
|
||||
self.i2plib = i2plib
|
||||
self.utils = i2plib.utils
|
||||
self.sam_address = i2plib.get_sam_address()
|
||||
|
||||
self.storagepath = rns_storagepath+"/i2p"
|
||||
if not os.path.isdir(self.storagepath):
|
||||
os.makedirs(self.storagepath)
|
||||
|
||||
|
||||
def start(self):
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
self.loop = asyncio.get_event_loop()
|
||||
try:
|
||||
self.loop.run_forever()
|
||||
except Exception as e:
|
||||
RNS.log("Exception on event loop for "+str(self)+": "+str(e), RNS.LOG_ERROR)
|
||||
finally:
|
||||
self.loop.close()
|
||||
|
||||
|
||||
def stop(self):
|
||||
for task in asyncio.Task.all_tasks(loop=self.loop):
|
||||
task.cancel()
|
||||
|
||||
self.loop.stop()
|
||||
|
||||
|
||||
def get_free_port(self):
|
||||
return self.i2plib.utils.get_free_port()
|
||||
|
||||
|
||||
def client_tunnel(self, owner, i2p_destination):
|
||||
self.client_tunnels[i2p_destination] = False
|
||||
|
||||
while True:
|
||||
if not self.client_tunnels[i2p_destination]:
|
||||
try:
|
||||
async def tunnel_up():
|
||||
RNS.log("Bringing up I2P tunnel to "+str(owner)+", this may take a while...", RNS.LOG_INFO)
|
||||
tunnel = self.i2plib.ClientTunnel(i2p_destination, owner.local_addr, sam_address=self.sam_address, loop=self.loop)
|
||||
await tunnel.run()
|
||||
owner.awaiting_i2p_tunnel = False
|
||||
RNS.log(str(owner)+ " tunnel setup complete", RNS.LOG_VERBOSE)
|
||||
|
||||
try:
|
||||
self.loop.ext_owner = self
|
||||
future = asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
|
||||
self.client_tunnels[i2p_destination] = True
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while setting up I2P tunnel: "+str(e))
|
||||
raise e
|
||||
|
||||
|
||||
except Exception as e:
|
||||
raise IOError("Could not connect to I2P SAM API while configuring to "+str(owner)+". Check that I2P is running and SAM is enabled.")
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def server_tunnel(self, owner):
|
||||
i2p_dest_hash = RNS.Identity.full_hash(RNS.Identity.full_hash(owner.name.encode("utf-8")))
|
||||
i2p_keyfile = self.storagepath+"/"+RNS.hexrep(i2p_dest_hash, delimit=False)+".i2p"
|
||||
|
||||
i2p_dest = None
|
||||
if not os.path.isfile(i2p_keyfile):
|
||||
coro = self.i2plib.new_destination(sam_address=self.sam_address, loop=self.loop)
|
||||
i2p_dest = asyncio.run_coroutine_threadsafe(coro, self.loop).result()
|
||||
key_file = open(i2p_keyfile, "w")
|
||||
key_file.write(i2p_dest.private_key.base64)
|
||||
key_file.close()
|
||||
else:
|
||||
key_file = open(i2p_keyfile, "r")
|
||||
prvd = key_file.read()
|
||||
key_file.close()
|
||||
i2p_dest = self.i2plib.Destination(data=prvd, has_private_key=True)
|
||||
|
||||
i2p_b32 = i2p_dest.base32
|
||||
owner.b32 = i2p_b32
|
||||
|
||||
self.server_tunnels[i2p_b32] = False
|
||||
|
||||
while self.server_tunnels[i2p_b32] == False:
|
||||
try:
|
||||
async def tunnel_up():
|
||||
RNS.log(str(owner)+" Bringing up I2P endpoint, this may take a while...", RNS.LOG_INFO)
|
||||
tunnel = self.i2plib.ServerTunnel((owner.bind_ip, owner.bind_port), loop=self.loop, destination=i2p_dest, sam_address=self.sam_address)
|
||||
await tunnel.run()
|
||||
RNS.log(str(owner)+ " endpoint setup complete. Now reachable at: "+str(i2p_dest.base32)+".b32.i2p", RNS.LOG_VERBOSE)
|
||||
|
||||
asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
|
||||
self.server_tunnels[i2p_b32] = True
|
||||
|
||||
except Exception as e:
|
||||
raise IOError("Could not connect to I2P SAM API while configuring "+str(self)+". Check that I2P is running and SAM is enabled.")
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
def get_loop(self):
|
||||
return asyncio.get_event_loop()
|
||||
|
||||
|
||||
class ThreadingI2PServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
|
||||
class I2PInterfacePeer(Interface):
|
||||
RECONNECT_WAIT = 15
|
||||
RECONNECT_MAX_TRIES = None
|
||||
|
||||
# TCP socket options
|
||||
I2P_USER_TIMEOUT = 40
|
||||
I2P_PROBE_AFTER = 10
|
||||
I2P_PROBE_INTERVAL = 5
|
||||
I2P_PROBES = 6
|
||||
|
||||
def __init__(self, parent_interface, owner, name, target_i2p_dest=None, connected_socket=None, max_reconnect_tries=None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
self.socket = None
|
||||
self.parent_interface = parent_interface
|
||||
self.parent_count = True
|
||||
self.name = name
|
||||
self.initiator = False
|
||||
self.reconnecting = False
|
||||
self.never_connected = True
|
||||
self.owner = owner
|
||||
self.writing = False
|
||||
self.online = False
|
||||
self.detached = False
|
||||
self.kiss_framing = False
|
||||
self.i2p_tunneled = True
|
||||
self.i2p_dest = None
|
||||
self.i2p_tunnel_ready = False
|
||||
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
|
||||
|
||||
if max_reconnect_tries == None:
|
||||
self.max_reconnect_tries = I2PInterfacePeer.RECONNECT_MAX_TRIES
|
||||
else:
|
||||
self.max_reconnect_tries = max_reconnect_tries
|
||||
|
||||
if connected_socket != None:
|
||||
self.receives = True
|
||||
self.target_ip = None
|
||||
self.target_port = None
|
||||
self.socket = connected_socket
|
||||
|
||||
if platform.system() == "Linux":
|
||||
self.set_timeouts_linux()
|
||||
elif platform.system() == "Darwin":
|
||||
self.set_timeouts_osx()
|
||||
|
||||
elif target_i2p_dest != None:
|
||||
self.receives = True
|
||||
self.initiator = True
|
||||
|
||||
self.bind_ip = "127.0.0.1"
|
||||
self.bind_port = self.parent_interface.i2p.get_free_port()
|
||||
self.local_addr = (self.bind_ip, self.bind_port)
|
||||
self.target_ip = self.bind_ip
|
||||
self.target_port = self.bind_port
|
||||
|
||||
self.awaiting_i2p_tunnel = True
|
||||
|
||||
def tunnel_job():
|
||||
self.parent_interface.i2p.client_tunnel(self, target_i2p_dest)
|
||||
|
||||
thread = threading.Thread(target=tunnel_job)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
def wait_job():
|
||||
while self.awaiting_i2p_tunnel:
|
||||
time.sleep(0.25)
|
||||
|
||||
if not self.kiss_framing:
|
||||
self.wants_tunnel = True
|
||||
|
||||
if not self.connect(initial=True):
|
||||
thread = threading.Thread(target=self.reconnect)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
else:
|
||||
thread = threading.Thread(target=self.read_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
thread = threading.Thread(target=wait_job)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
|
||||
def set_timeouts_linux(self):
|
||||
if not self.i2p_tunneled:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(I2PInterfacePeer.TCP_USER_TIMEOUT * 1000))
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(I2PInterfacePeer.TCP_PROBE_AFTER))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(I2PInterfacePeer.TCP_PROBE_INTERVAL))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(I2PInterfacePeer.TCP_PROBES))
|
||||
else:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(I2PInterfacePeer.I2P_USER_TIMEOUT * 1000))
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(I2PInterfacePeer.I2P_PROBE_INTERVAL))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(I2PInterfacePeer.I2P_PROBES))
|
||||
|
||||
def set_timeouts_osx(self):
|
||||
if hasattr(socket, "TCP_KEEPALIVE"):
|
||||
TCP_KEEPIDLE = socket.TCP_KEEPALIVE
|
||||
else:
|
||||
TCP_KEEPIDLE = 0x10
|
||||
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
|
||||
if not self.i2p_tunneled:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.TCP_PROBE_AFTER))
|
||||
else:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
|
||||
|
||||
def detach(self):
|
||||
if self.socket != None:
|
||||
if hasattr(self.socket, "close"):
|
||||
if callable(self.socket.close):
|
||||
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
|
||||
self.detached = True
|
||||
|
||||
try:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
except Exception as e:
|
||||
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
|
||||
|
||||
try:
|
||||
self.socket.close()
|
||||
except Exception as e:
|
||||
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
|
||||
|
||||
self.socket = None
|
||||
|
||||
def connect(self, initial=False):
|
||||
try:
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.connect((self.target_ip, self.target_port))
|
||||
self.online = True
|
||||
|
||||
except Exception as e:
|
||||
if initial:
|
||||
if not self.awaiting_i2p_tunnel:
|
||||
RNS.log("Initial connection for "+str(self)+" could not be established: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Leaving unconnected and retrying connection in "+str(I2PInterfacePeer.RECONNECT_WAIT)+" seconds.", RNS.LOG_ERROR)
|
||||
|
||||
return False
|
||||
|
||||
else:
|
||||
raise e
|
||||
|
||||
if platform.system() == "Linux":
|
||||
self.set_timeouts_linux()
|
||||
elif platform.system() == "Darwin":
|
||||
self.set_timeouts_osx()
|
||||
|
||||
self.online = True
|
||||
self.writing = False
|
||||
self.never_connected = False
|
||||
|
||||
if not self.kiss_framing and self.wants_tunnel:
|
||||
RNS.Transport.synthesize_tunnel(self)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def reconnect(self):
|
||||
if self.initiator:
|
||||
if not self.reconnecting:
|
||||
self.reconnecting = True
|
||||
attempts = 0
|
||||
while not self.online:
|
||||
time.sleep(I2PInterfacePeer.RECONNECT_WAIT)
|
||||
attempts += 1
|
||||
|
||||
if self.max_reconnect_tries != None and attempts > self.max_reconnect_tries:
|
||||
RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_ERROR)
|
||||
self.teardown()
|
||||
break
|
||||
|
||||
try:
|
||||
self.connect()
|
||||
|
||||
except Exception as e:
|
||||
if not self.awaiting_i2p_tunnel:
|
||||
RNS.log("Connection attempt for "+str(self)+" failed: "+str(e), RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log(str(self)+" still waiting for I2P tunnel to appear", RNS.LOG_VERBOSE)
|
||||
|
||||
if not self.never_connected:
|
||||
RNS.log(str(self)+" Re-established connection via I2P tunnel", RNS.LOG_INFO)
|
||||
|
||||
self.reconnecting = False
|
||||
thread = threading.Thread(target=self.read_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
if not self.kiss_framing:
|
||||
RNS.Transport.synthesize_tunnel(self)
|
||||
|
||||
else:
|
||||
RNS.log("Attempt to reconnect on a non-initiator I2P interface. This should not happen.", RNS.LOG_ERROR)
|
||||
raise IOError("Attempt to reconnect on a non-initiator I2P interface")
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count:
|
||||
self.parent_interface.rxb += len(data)
|
||||
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
def processOutgoing(self, data):
|
||||
if self.online:
|
||||
while self.writing:
|
||||
time.sleep(0.01)
|
||||
|
||||
try:
|
||||
self.writing = True
|
||||
|
||||
if self.kiss_framing:
|
||||
data = bytes([KISS.FEND])+bytes([KISS.CMD_DATA])+KISS.escape(data)+bytes([KISS.FEND])
|
||||
else:
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
|
||||
self.socket.sendall(data)
|
||||
self.writing = False
|
||||
self.txb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count:
|
||||
self.parent_interface.txb += len(data)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Exception occurred while transmitting via "+str(self)+", tearing down interface", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
self.teardown()
|
||||
|
||||
|
||||
def read_loop(self):
|
||||
try:
|
||||
in_frame = False
|
||||
escape = False
|
||||
data_buffer = b""
|
||||
command = KISS.CMD_UNKNOWN
|
||||
|
||||
while True:
|
||||
data_in = self.socket.recv(4096)
|
||||
if len(data_in) > 0:
|
||||
pointer = 0
|
||||
while pointer < len(data_in):
|
||||
byte = data_in[pointer]
|
||||
pointer += 1
|
||||
|
||||
if self.kiss_framing:
|
||||
# Read loop for KISS framing
|
||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
elif (byte == KISS.FEND):
|
||||
in_frame = True
|
||||
command = KISS.CMD_UNKNOWN
|
||||
data_buffer = b""
|
||||
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
|
||||
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
|
||||
# We only support one HDLC port for now, so
|
||||
# strip off the port nibble
|
||||
byte = byte & 0x0F
|
||||
command = byte
|
||||
elif (command == KISS.CMD_DATA):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
|
||||
else:
|
||||
# Read loop for HDLC framing
|
||||
if (in_frame and byte == HDLC.FLAG):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
elif (byte == HDLC.FLAG):
|
||||
in_frame = True
|
||||
data_buffer = b""
|
||||
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
|
||||
if (byte == HDLC.ESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.FLAG
|
||||
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.ESC
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
else:
|
||||
self.online = False
|
||||
if self.initiator and not self.detached:
|
||||
RNS.log("Socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
|
||||
self.reconnect()
|
||||
else:
|
||||
RNS.log("Socket for remote client "+str(self)+" was closed.", RNS.LOG_VERBOSE)
|
||||
self.teardown()
|
||||
|
||||
break
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("An interface error occurred for "+str(self)+", the contained exception was: "+str(e), RNS.LOG_WARNING)
|
||||
|
||||
if self.initiator:
|
||||
RNS.log("Attempting to reconnect...", RNS.LOG_WARNING)
|
||||
self.reconnect()
|
||||
else:
|
||||
self.teardown()
|
||||
|
||||
def teardown(self):
|
||||
if self.initiator and not self.detached:
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
else:
|
||||
RNS.log("The interface "+str(self)+" is being torn down.", RNS.LOG_VERBOSE)
|
||||
|
||||
self.online = False
|
||||
self.OUT = False
|
||||
self.IN = False
|
||||
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.clients -= 1
|
||||
|
||||
if self in RNS.Transport.interfaces:
|
||||
if not self.initiator:
|
||||
RNS.Transport.interfaces.remove(self)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "I2PInterfacePeer["+str(self.name)+"]"
|
||||
|
||||
|
||||
class I2PInterface(Interface):
|
||||
|
||||
def __init__(self, owner, name, rns_storagepath, peers, connectable = True):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
self.online = False
|
||||
self.clients = 0
|
||||
self.owner = owner
|
||||
self.connectable = connectable
|
||||
self.i2p_tunneled = True
|
||||
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
|
||||
|
||||
self.b32 = None
|
||||
self.i2p = I2PController(rns_storagepath)
|
||||
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
self.name = name
|
||||
|
||||
|
||||
self.receives = True
|
||||
self.bind_ip = "127.0.0.1"
|
||||
self.bind_port = self.i2p.get_free_port()
|
||||
self.address = (self.bind_ip, self.bind_port)
|
||||
|
||||
i2p_thread = threading.Thread(target=self.i2p.start)
|
||||
i2p_thread.setDaemon(True)
|
||||
i2p_thread.start()
|
||||
|
||||
def handlerFactory(callback):
|
||||
def createHandler(*args, **keys):
|
||||
return I2PInterfaceHandler(callback, *args, **keys)
|
||||
return createHandler
|
||||
|
||||
ThreadingI2PServer.allow_reuse_address = True
|
||||
self.server = ThreadingI2PServer(self.address, handlerFactory(self.incoming_connection))
|
||||
|
||||
thread = threading.Thread(target=self.server.serve_forever)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
if self.connectable:
|
||||
def tunnel_job():
|
||||
self.i2p.server_tunnel(self)
|
||||
|
||||
thread = threading.Thread(target=tunnel_job)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
if peers != None:
|
||||
for peer_addr in peers:
|
||||
interface_name = peer_addr
|
||||
peer_interface = I2PInterfacePeer(self, self.owner, interface_name, peer_addr)
|
||||
peer_interface.OUT = True
|
||||
peer_interface.IN = True
|
||||
peer_interface.parent_interface = self
|
||||
peer_interface.parent_count = False
|
||||
RNS.Transport.interfaces.append(peer_interface)
|
||||
|
||||
self.online = True
|
||||
|
||||
|
||||
def incoming_connection(self, handler):
|
||||
RNS.log("Accepting incoming I2P connection", RNS.LOG_VERBOSE)
|
||||
interface_name = "Connected peer on "+self.name
|
||||
spawned_interface = I2PInterfacePeer(self, self.owner, interface_name, connected_socket=handler.request)
|
||||
spawned_interface.OUT = True
|
||||
spawned_interface.IN = True
|
||||
spawned_interface.parent_interface = self
|
||||
spawned_interface.online = True
|
||||
RNS.log("Spawned new I2PInterface Peer: "+str(spawned_interface), RNS.LOG_VERBOSE)
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
self.clients += 1
|
||||
spawned_interface.read_loop()
|
||||
|
||||
def processOutgoing(self, data):
|
||||
pass
|
||||
|
||||
def detach(self):
|
||||
self.i2p.stop()
|
||||
|
||||
def __str__(self):
|
||||
return "I2PInterface["+self.name+"]"
|
||||
|
||||
class I2PInterfaceHandler(socketserver.BaseRequestHandler):
|
||||
def __init__(self, callback, *args, **keys):
|
||||
self.callback = callback
|
||||
socketserver.BaseRequestHandler.__init__(self, *args, **keys)
|
||||
|
||||
def handle(self):
|
||||
self.callback(handler=self)
|
||||
@@ -7,6 +7,10 @@ class Interface:
|
||||
RPT = False
|
||||
name = None
|
||||
|
||||
MODE_FULL = 0x01
|
||||
MODE_POINT_TO_POINT = 0x02
|
||||
MODE_ACCESS_POINT = 0x03
|
||||
|
||||
def __init__(self):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
@@ -37,6 +37,7 @@ class LocalClientInterface(Interface):
|
||||
self.never_connected = True
|
||||
self.detached = False
|
||||
self.name = name
|
||||
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
|
||||
|
||||
if connected_socket != None:
|
||||
self.receives = True
|
||||
@@ -237,6 +238,7 @@ class LocalServerInterface(Interface):
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
self.name = "Reticulum"
|
||||
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
|
||||
|
||||
if (bindport != None):
|
||||
self.receives = True
|
||||
|
||||
@@ -30,8 +30,11 @@ class KISS():
|
||||
CMD_STAT_SNR = 0x24
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_PLATFORM = 0x48
|
||||
CMD_MCU = 0x49
|
||||
CMD_FW_VERSION = 0x50
|
||||
CMD_ROM_READ = 0x51
|
||||
CMD_RESET = 0x55
|
||||
|
||||
DETECT_REQ = 0x73
|
||||
DETECT_RESP = 0x46
|
||||
@@ -45,6 +48,9 @@ class KISS():
|
||||
ERROR_TXFAILED = 0x02
|
||||
ERROR_EEPROM_LOCKED = 0x03
|
||||
|
||||
PLATFORM_AVR = 0x90
|
||||
PLATFORM_ESP32 = 0x80
|
||||
|
||||
@staticmethod
|
||||
def escape(data):
|
||||
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
|
||||
@@ -70,6 +76,9 @@ class RNodeInterface(Interface):
|
||||
|
||||
CALLSIGN_MAX_LEN = 32
|
||||
|
||||
REQUIRED_FW_VER_MAJ = 1
|
||||
REQUIRED_FW_VER_MIN = 26
|
||||
|
||||
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False, id_interval = None, id_callsign = None):
|
||||
import importlib
|
||||
if importlib.util.find_spec('serial') != None:
|
||||
@@ -101,6 +110,12 @@ class RNodeInterface(Interface):
|
||||
self.cr = cr
|
||||
self.state = KISS.RADIO_STATE_OFF
|
||||
self.bitrate = 0
|
||||
self.platform = None
|
||||
self.mcu = None
|
||||
self.detected = False
|
||||
self.firmware_ok = False
|
||||
self.maj_version = 0
|
||||
self.min_version = 0
|
||||
|
||||
self.last_id = 0
|
||||
self.first_tx = None
|
||||
@@ -190,6 +205,17 @@ class RNodeInterface(Interface):
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
self.detect()
|
||||
sleep(0.1)
|
||||
|
||||
if not self.detected:
|
||||
raise IOError("Could not detect device")
|
||||
else:
|
||||
if self.platform == KISS.PLATFORM_ESP32:
|
||||
RNS.log("Resetting ESP32-based device before configuration...", RNS.LOG_VERBOSE)
|
||||
self.hard_reset()
|
||||
|
||||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
|
||||
@@ -203,7 +229,7 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
|
||||
RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
|
||||
self.serial.close()
|
||||
raise IOError("RNode interface did not pass validation")
|
||||
raise IOError("RNode interface did not pass configuration validation")
|
||||
|
||||
|
||||
def initRadio(self):
|
||||
@@ -214,6 +240,19 @@ class RNodeInterface(Interface):
|
||||
self.setCodingRate()
|
||||
self.setRadioState(KISS.RADIO_STATE_ON)
|
||||
|
||||
def detect(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while detecting hardware for "+self(str))
|
||||
|
||||
def hard_reset(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while restarting device")
|
||||
sleep(2.25);
|
||||
|
||||
def setFrequency(self):
|
||||
c1 = self.frequency >> 24
|
||||
c2 = self.frequency >> 16 & 0xFF
|
||||
@@ -260,13 +299,28 @@ class RNodeInterface(Interface):
|
||||
raise IOError("An IO error occurred while configuring coding rate for "+self(str))
|
||||
|
||||
def setRadioState(self, state):
|
||||
self.state = state
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while configuring radio state for "+self(str))
|
||||
|
||||
def validate_firmware(self):
|
||||
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
|
||||
if (self.min_version >= RNodeInterface.REQUIRED_FW_VER_MIN):
|
||||
self.firmware_ok = True
|
||||
|
||||
if self.firmware_ok:
|
||||
return
|
||||
|
||||
RNS.log("The firmware version of the connected RNode is "+str(self.maj_version)+"."+str(self.min_version), RNS.LOG_ERROR)
|
||||
RNS.log("This version of Reticulum requires at least version "+str(RNodeInterface.REQUIRED_FW_VER_MAJ)+"."+str(RNodeInterface.REQUIRED_FW_VER_MIN), RNS.LOG_ERROR)
|
||||
RNS.log("Please update your RNode firmware with rnodeconf (https://github.com/markqvist/rnodeconfigutil/)")
|
||||
RNS.panic()
|
||||
|
||||
|
||||
def validateRadioState(self):
|
||||
RNS.log("Validating radio configuration for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
RNS.log("Wating for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
sleep(0.25);
|
||||
if (self.frequency != self.r_frequency):
|
||||
RNS.log("Frequency mismatch", RNS.LOG_ERROR)
|
||||
@@ -280,6 +334,9 @@ class RNodeInterface(Interface):
|
||||
if (self.sf != self.r_sf):
|
||||
RNS.log("Spreading factor mismatch", RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
if (self.state != self.r_state):
|
||||
RNS.log("Radio state mismatch", RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.validcfg):
|
||||
return True
|
||||
@@ -420,8 +477,30 @@ class RNodeInterface(Interface):
|
||||
self.updateBitrate()
|
||||
elif (command == KISS.CMD_RADIO_STATE):
|
||||
self.r_state = byte
|
||||
if self.r_state:
|
||||
pass
|
||||
#RNS.log(str(self)+" Radio reporting state is online", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log(str(self)+" Radio reporting state is offline", RNS.LOG_DEBUG)
|
||||
|
||||
elif (command == KISS.CMD_RADIO_LOCK):
|
||||
self.r_lock = byte
|
||||
elif (command == KISS.CMD_FW_VERSION):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 2):
|
||||
self.maj_version = int(command_buffer[0])
|
||||
self.min_version = int(command_buffer[1])
|
||||
self.validate_firmware()
|
||||
|
||||
elif (command == KISS.CMD_STAT_RX):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
@@ -456,15 +535,33 @@ class RNodeInterface(Interface):
|
||||
self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25
|
||||
elif (command == KISS.CMD_RANDOM):
|
||||
self.r_random = byte
|
||||
elif (command == KISS.CMD_PLATFORM):
|
||||
self.platform = byte
|
||||
elif (command == KISS.CMD_MCU):
|
||||
self.mcu = byte
|
||||
elif (command == KISS.CMD_ERROR):
|
||||
if (byte == KISS.ERROR_INITRADIO):
|
||||
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Radio initialisation failure")
|
||||
elif (byte == KISS.ERROR_INITRADIO):
|
||||
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Hardware transmit failure")
|
||||
else:
|
||||
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Unknown hardware failure")
|
||||
elif (command == KISS.CMD_RESET):
|
||||
if (byte == 0xF8):
|
||||
if self.platform == KISS.PLATFORM_ESP32:
|
||||
if self.online:
|
||||
RNS.log("Detected reset while device was online, reinitialising device...", RNS.LOG_ERROR)
|
||||
raise IOError("ESP32 reset")
|
||||
elif (command == KISS.CMD_READY):
|
||||
self.process_queue()
|
||||
elif (command == KISS.CMD_DETECT):
|
||||
if byte == KISS.DETECT_RESP:
|
||||
self.detected = True
|
||||
else:
|
||||
self.detected = False
|
||||
|
||||
else:
|
||||
time_since_last = int(time.time()*1000) - last_read_ms
|
||||
@@ -487,7 +584,7 @@ class RNodeInterface(Interface):
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
@@ -500,7 +597,7 @@ class RNodeInterface(Interface):
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
try:
|
||||
time.sleep(5)
|
||||
time.sleep(3.5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
self.open_port()
|
||||
if self.serial.is_open:
|
||||
@@ -511,5 +608,5 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def __str__(self):
|
||||
return "RNodeInterface["+self.name+"]"
|
||||
return "RNodeInterface["+str(self.name)+"]"
|
||||
|
||||
|
||||
@@ -46,7 +46,12 @@ class TCPClientInterface(Interface):
|
||||
TCP_PROBE_INTERVAL = 3
|
||||
TCP_PROBES = 5
|
||||
|
||||
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None, max_reconnect_tries=None, kiss_framing=False):
|
||||
I2P_USER_TIMEOUT = 40
|
||||
I2P_PROBE_AFTER = 10
|
||||
I2P_PROBE_INTERVAL = 5
|
||||
I2P_PROBES = 6
|
||||
|
||||
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None, max_reconnect_tries=None, kiss_framing=False, i2p_tunneled = False):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
@@ -63,7 +68,9 @@ class TCPClientInterface(Interface):
|
||||
self.online = False
|
||||
self.detached = False
|
||||
self.kiss_framing = kiss_framing
|
||||
|
||||
self.i2p_tunneled = i2p_tunneled
|
||||
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
|
||||
|
||||
if max_reconnect_tries == None:
|
||||
self.max_reconnect_tries = TCPClientInterface.RECONNECT_MAX_TRIES
|
||||
else:
|
||||
@@ -99,12 +106,18 @@ class TCPClientInterface(Interface):
|
||||
|
||||
|
||||
def set_timeouts_linux(self):
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.TCP_USER_TIMEOUT * 1000))
|
||||
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.TCP_PROBE_INTERVAL))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.TCP_PROBES))
|
||||
if not self.i2p_tunneled:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.TCP_USER_TIMEOUT * 1000))
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.TCP_PROBE_INTERVAL))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.TCP_PROBES))
|
||||
else:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.I2P_USER_TIMEOUT * 1000))
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.I2P_PROBE_INTERVAL))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.I2P_PROBES))
|
||||
|
||||
def set_timeouts_osx(self):
|
||||
if hasattr(socket, "TCP_KEEPALIVE"):
|
||||
@@ -112,9 +125,13 @@ class TCPClientInterface(Interface):
|
||||
else:
|
||||
TCP_KEEPIDLE = 0x10
|
||||
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
sock.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
|
||||
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
|
||||
if not self.i2p_tunneled:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
|
||||
else:
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
|
||||
|
||||
def detach(self):
|
||||
if self.socket != None:
|
||||
if hasattr(self.socket, "close"):
|
||||
@@ -358,7 +375,7 @@ class TCPServerInterface(Interface):
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None):
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
self.online = False
|
||||
@@ -368,6 +385,9 @@ class TCPServerInterface(Interface):
|
||||
self.OUT = False
|
||||
self.name = name
|
||||
|
||||
self.i2p_tunneled = i2p_tunneled
|
||||
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
|
||||
|
||||
if device != None:
|
||||
bindip = TCPServerInterface.get_address_for_if(device)
|
||||
|
||||
@@ -397,7 +417,7 @@ class TCPServerInterface(Interface):
|
||||
def incoming_connection(self, handler):
|
||||
RNS.log("Accepting incoming TCP connection", RNS.LOG_VERBOSE)
|
||||
interface_name = "Client on "+self.name
|
||||
spawned_interface = TCPClientInterface(self.owner, interface_name, target_ip=None, target_port=None, connected_socket=handler.request)
|
||||
spawned_interface = TCPClientInterface(self.owner, interface_name, target_ip=None, target_port=None, connected_socket=handler.request, i2p_tunneled=self.i2p_tunneled)
|
||||
spawned_interface.OUT = self.OUT
|
||||
spawned_interface.IN = self.IN
|
||||
spawned_interface.target_ip = handler.client_address[0]
|
||||
@@ -421,4 +441,4 @@ class TCPInterfaceHandler(socketserver.BaseRequestHandler):
|
||||
socketserver.BaseRequestHandler.__init__(self, *args, **keys)
|
||||
|
||||
def handle(self):
|
||||
self.callback(handler=self)
|
||||
self.callback(handler=self)
|
||||
|
||||
+3
-2
@@ -551,7 +551,8 @@ class Link:
|
||||
break
|
||||
|
||||
if remove != None:
|
||||
self.pending_requests.remove(remove)
|
||||
if remove in self.pending_requests:
|
||||
self.pending_requests.remove(remove)
|
||||
|
||||
def request_resource_concluded(self, resource):
|
||||
if resource.status == RNS.Resource.COMPLETE:
|
||||
@@ -765,7 +766,7 @@ class Link:
|
||||
return plaintext
|
||||
except Exception as e:
|
||||
RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log(traceback.format_exc(), RNS.LOG_ERROR)
|
||||
# RNS.log(traceback.format_exc(), RNS.LOG_ERROR)
|
||||
# TODO: Think long about implications here
|
||||
# self.teardown()
|
||||
|
||||
|
||||
+24
-18
@@ -185,27 +185,33 @@ class Packet:
|
||||
|
||||
|
||||
def unpack(self):
|
||||
self.flags = self.raw[0]
|
||||
self.hops = self.raw[1]
|
||||
try:
|
||||
self.flags = self.raw[0]
|
||||
self.hops = self.raw[1]
|
||||
|
||||
self.header_type = (self.flags & 0b11000000) >> 6
|
||||
self.transport_type = (self.flags & 0b00110000) >> 4
|
||||
self.destination_type = (self.flags & 0b00001100) >> 2
|
||||
self.packet_type = (self.flags & 0b00000011)
|
||||
self.header_type = (self.flags & 0b11000000) >> 6
|
||||
self.transport_type = (self.flags & 0b00110000) >> 4
|
||||
self.destination_type = (self.flags & 0b00001100) >> 2
|
||||
self.packet_type = (self.flags & 0b00000011)
|
||||
|
||||
if self.header_type == Packet.HEADER_2:
|
||||
self.transport_id = self.raw[2:12]
|
||||
self.destination_hash = self.raw[12:22]
|
||||
self.context = ord(self.raw[22:23])
|
||||
self.data = self.raw[23:]
|
||||
else:
|
||||
self.transport_id = None
|
||||
self.destination_hash = self.raw[2:12]
|
||||
self.context = ord(self.raw[12:13])
|
||||
self.data = self.raw[13:]
|
||||
if self.header_type == Packet.HEADER_2:
|
||||
self.transport_id = self.raw[2:12]
|
||||
self.destination_hash = self.raw[12:22]
|
||||
self.context = ord(self.raw[22:23])
|
||||
self.data = self.raw[23:]
|
||||
else:
|
||||
self.transport_id = None
|
||||
self.destination_hash = self.raw[2:12]
|
||||
self.context = ord(self.raw[12:13])
|
||||
self.data = self.raw[13:]
|
||||
|
||||
self.packed = False
|
||||
self.update_hash()
|
||||
self.packed = False
|
||||
self.update_hash()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Received malformed packet, dropping it. The contained exception was: "+str(e), RNS.LOG_EXTREME)
|
||||
return False
|
||||
|
||||
def send(self):
|
||||
"""
|
||||
|
||||
+4
-1
@@ -143,6 +143,8 @@ class Resource:
|
||||
def __init__(self, data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout = None, segment_index = 1, original_hash = None, request_id = None, is_response = False):
|
||||
data_size = None
|
||||
resource_data = None
|
||||
self.assembly_lock = False
|
||||
|
||||
if hasattr(data, "read"):
|
||||
data_size = os.stat(data.name).st_size
|
||||
self.total_size = data_size
|
||||
@@ -601,7 +603,8 @@ class Resource:
|
||||
|
||||
self.receiving_part = False
|
||||
|
||||
if self.received_count == self.total_parts:
|
||||
if self.received_count == self.total_parts and not self.assembly_lock:
|
||||
self.assembly_lock = True
|
||||
self.assemble()
|
||||
elif self.outstanding_parts == 0:
|
||||
# TODO: Figure out if there is a mathematically
|
||||
|
||||
+139
-306
@@ -1,8 +1,12 @@
|
||||
from .vendor.platformutils import get_platform
|
||||
|
||||
if get_platform() == "android":
|
||||
# TODO: Selectively import Android-relevant interfaces
|
||||
pass
|
||||
from .Interfaces import Interface
|
||||
from .Interfaces import LocalInterface
|
||||
from .Interfaces import AutoInterface
|
||||
from .Interfaces import TCPInterface
|
||||
from .Interfaces import UDPInterface
|
||||
from .Interfaces import I2PInterface
|
||||
else:
|
||||
from .Interfaces import *
|
||||
|
||||
@@ -116,6 +120,8 @@ class Reticulum:
|
||||
:param configdir: Full path to a Reticulum configuration directory.
|
||||
"""
|
||||
|
||||
RNS.vendor.platformutils.platform_checks()
|
||||
|
||||
if configdir != None:
|
||||
Reticulum.configdir = configdir
|
||||
|
||||
@@ -268,38 +274,56 @@ class Reticulum:
|
||||
self.__start_local_interface()
|
||||
|
||||
if self.is_shared_instance or self.is_standalone_instance:
|
||||
RNS.log("Bringing up system interfaces...", RNS.LOG_DEBUG)
|
||||
interface_names = []
|
||||
for name in self.config["interfaces"]:
|
||||
if not name in interface_names:
|
||||
c = self.config["interfaces"][name]
|
||||
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
if "mode" in c:
|
||||
if c["mode"] == "full":
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
elif c["mode"] == "accesspoint" or c["mode"] == "ap":
|
||||
interface_mode = Interface.Interface.MODE_ACCESS_POINT
|
||||
elif c["mode"] == "pointtopoint" or c["mode"] == "ptp":
|
||||
interface_mode = Interface.Interface.MODE_POINT_TO_POINT
|
||||
|
||||
try:
|
||||
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("devices") if "devices" in c else None
|
||||
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None
|
||||
if not RNS.vendor.platformutils.is_windows():
|
||||
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("devices") if "devices" in c else None
|
||||
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None
|
||||
|
||||
interface = AutoInterface.AutoInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
group_id,
|
||||
discovery_scope,
|
||||
discovery_port,
|
||||
data_port,
|
||||
allowed_interfaces,
|
||||
ignored_interfaces
|
||||
)
|
||||
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
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
else:
|
||||
interface.OUT = False
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
RNS.log("AutoInterface is not currently supported on Windows, disabling interface.", RNS.LOG_ERROR);
|
||||
RNS.log("Please remove this AutoInterface instance from your configuration file.", RNS.LOG_ERROR);
|
||||
RNS.log("You will have to manually configure other interfaces for connectivity.", RNS.LOG_ERROR);
|
||||
|
||||
|
||||
if c["type"] == "UDPInterface":
|
||||
@@ -326,10 +350,12 @@ class Reticulum:
|
||||
forward_port
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
@@ -339,6 +365,7 @@ class Reticulum:
|
||||
port = int(c["port"]) if "port" in c else None
|
||||
listen_ip = c["listen_ip"] if "listen_ip" in c else None
|
||||
listen_port = int(c["listen_port"]) if "listen_port" in c else None
|
||||
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
|
||||
|
||||
if port != None:
|
||||
listen_port = port
|
||||
@@ -348,13 +375,20 @@ class Reticulum:
|
||||
name,
|
||||
device,
|
||||
listen_ip,
|
||||
listen_port
|
||||
listen_port,
|
||||
i2p_tunneled
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
if interface_mode != Interface.Interface.MODE_FULL:
|
||||
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
@@ -363,19 +397,54 @@ class Reticulum:
|
||||
kiss_framing = False
|
||||
if "kiss_framing" in c and c.as_bool("kiss_framing") == True:
|
||||
kiss_framing = True
|
||||
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
|
||||
|
||||
|
||||
interface = TCPInterface.TCPClientInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
c["target_host"],
|
||||
int(c["target_port"]),
|
||||
kiss_framing = kiss_framing
|
||||
kiss_framing = kiss_framing,
|
||||
i2p_tunneled = i2p_tunneled
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
if interface_mode != Interface.Interface.MODE_FULL:
|
||||
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
|
||||
if c["type"] == "I2PInterface":
|
||||
i2p_peers = c.as_list("peers") if "peers" in c else None
|
||||
connectable = c.as_bool("connectable") if "connectable" in c else False
|
||||
|
||||
interface = I2PInterface.I2PInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
Reticulum.storagepath,
|
||||
i2p_peers,
|
||||
connectable = connectable,
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
if interface_mode != Interface.Interface.MODE_FULL:
|
||||
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
@@ -400,10 +469,12 @@ class Reticulum:
|
||||
stopbits
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
@@ -441,10 +512,12 @@ class Reticulum:
|
||||
beacon_data
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
@@ -483,10 +556,12 @@ class Reticulum:
|
||||
flow_control
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
@@ -519,10 +594,12 @@ class Reticulum:
|
||||
id_callsign = id_callsign
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
else:
|
||||
@@ -535,6 +612,9 @@ class Reticulum:
|
||||
else:
|
||||
RNS.log("The interface name \""+name+"\" was already used. Check your configuration file for errors!", RNS.LOG_ERROR)
|
||||
RNS.panic()
|
||||
|
||||
RNS.log("System interfaces are ready", RNS.LOG_DEBUG)
|
||||
|
||||
|
||||
|
||||
def __create_default_config(self):
|
||||
@@ -589,6 +669,12 @@ class Reticulum:
|
||||
else:
|
||||
ifstats["clients"] = None
|
||||
|
||||
if hasattr(interface, "b32"):
|
||||
if interface.b32 != None:
|
||||
ifstats["i2p_b32"] = interface.b32+".b32.i2p"
|
||||
else:
|
||||
ifstats["i2p_b32"] = None
|
||||
|
||||
ifstats["name"] = str(interface)
|
||||
ifstats["rxb"] = interface.rxb
|
||||
ifstats["txb"] = interface.txb
|
||||
@@ -674,6 +760,12 @@ __default_rns_config__ = '''# This is the default Reticulum config file.
|
||||
# You should probably edit it to include any additional,
|
||||
# interfaces and settings you might need.
|
||||
|
||||
# Only the most basic options are included in this default
|
||||
# configuration. To see a more verbose, and much longer,
|
||||
# configuration example, you can run the command:
|
||||
# rnsd --exampleconfig
|
||||
|
||||
|
||||
[reticulum]
|
||||
|
||||
# If you enable Transport, your system will route traffic
|
||||
@@ -707,6 +799,7 @@ share_instance = Yes
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
|
||||
|
||||
# You can configure Reticulum to panic and forcibly close
|
||||
# if an unrecoverable interface error occurs, such as the
|
||||
# hardware device for an interface disappearing. This is
|
||||
@@ -749,265 +842,5 @@ loglevel = 4
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = 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_port = 4242
|
||||
forward_ip = 255.255.255.255
|
||||
forward_port = 4242
|
||||
|
||||
# The above configuration will allow communication
|
||||
# within the local broadcast domains of all local
|
||||
# IP interfaces.
|
||||
|
||||
# Instead of specifying listen_ip, listen_port,
|
||||
# forward_ip and forward_port, you can also bind
|
||||
# to a specific network device like below.
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
# Assuming the eth0 device has the address
|
||||
# 10.55.0.72/24, the above configuration would
|
||||
# be equivalent to the following manual setup.
|
||||
# Note that we are both listening and forwarding to
|
||||
# the broadcast address of the network segments.
|
||||
|
||||
# listen_ip = 10.55.0.255
|
||||
# listen_port = 4242
|
||||
# forward_ip = 10.55.0.255
|
||||
# forward_port = 4242
|
||||
|
||||
# You can of course also communicate only with
|
||||
# a single IP address
|
||||
|
||||
# listen_ip = 10.55.0.15
|
||||
# listen_port = 4242
|
||||
# forward_ip = 10.55.0.16
|
||||
# forward_port = 4242
|
||||
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = False
|
||||
outgoing = True
|
||||
|
||||
# This configuration will listen on all IP
|
||||
# interfaces on port 4242
|
||||
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
|
||||
# Alternatively you can bind to a specific IP
|
||||
|
||||
# listen_ip = 10.0.0.88
|
||||
# listen_port = 4242
|
||||
|
||||
# Or a specific network device
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
|
||||
# To connect to a TCP server interface, you would
|
||||
# naturally use the TCP client interface. Here's
|
||||
# an example. The target_host can either be an IP
|
||||
# address or a hostname
|
||||
|
||||
[[TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = False
|
||||
outgoing = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
|
||||
# Here's an example of how to add a LoRa interface
|
||||
# using the RNode LoRa transceiver.
|
||||
|
||||
[[RNode LoRa Interface]]
|
||||
type = RNodeInterface
|
||||
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = False
|
||||
|
||||
# Allow transmit on interface. Setting
|
||||
# this to false will create a listen-
|
||||
# only interface.
|
||||
outgoing = true
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
|
||||
# Set frequency to 867.2 MHz
|
||||
frequency = 867200000
|
||||
|
||||
# Set LoRa bandwidth to 125 KHz
|
||||
bandwidth = 125000
|
||||
|
||||
# Set TX power to 7 dBm (5 mW)
|
||||
txpower = 7
|
||||
|
||||
# Select spreading factor 8. Valid
|
||||
# range is 7 through 12, with 7
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 8
|
||||
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
|
||||
# You can configure the RNode to send
|
||||
# out identification on the channel with
|
||||
# a set interval by configuring the
|
||||
# following two parameters. The trans-
|
||||
# ceiver will only ID if the set
|
||||
# interval has elapsed since it's last
|
||||
# actual transmission. The interval is
|
||||
# configured in seconds.
|
||||
# This option is commented out and not
|
||||
# used by default.
|
||||
# id_callsign = MYCALL-0
|
||||
# id_interval = 600
|
||||
|
||||
# For certain homebrew RNode interfaces
|
||||
# with low amounts of RAM, using packet
|
||||
# flow control can be useful. By default
|
||||
# it is disabled.
|
||||
flow_control = False
|
||||
|
||||
|
||||
# An example KISS modem interface. Useful for running
|
||||
# Reticulum over packet radio hardware.
|
||||
|
||||
[[Packet Radio KISS Interface]]
|
||||
type = KISSInterface
|
||||
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = False
|
||||
|
||||
# Allow transmit on interface.
|
||||
outgoing = true
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB1
|
||||
|
||||
# Set the serial baud-rate and other
|
||||
# configuration parameters.
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
|
||||
# Set the modem preamble. A 150ms
|
||||
# preamble should be a reasonable
|
||||
# default, but may need to be
|
||||
# increased for radios with slow-
|
||||
# opening squelch and long TX/RX
|
||||
# turnaround
|
||||
preamble = 150
|
||||
|
||||
# Set the modem TX tail. In most
|
||||
# cases this should be kept as low
|
||||
# as possible to not waste airtime.
|
||||
txtail = 10
|
||||
|
||||
# Configure CDMA parameters. These
|
||||
# settings are reasonable defaults.
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
|
||||
# You can configure the interface to send
|
||||
# out identification on the channel with
|
||||
# a set interval by configuring the
|
||||
# following two parameters. The KISS
|
||||
# interface will only ID if the set
|
||||
# interval has elapsed since it's last
|
||||
# actual transmission. The interval is
|
||||
# configured in seconds.
|
||||
# This option is commented out and not
|
||||
# used by default.
|
||||
# id_callsign = MYCALL-0
|
||||
# id_interval = 600
|
||||
|
||||
# Whether to use KISS flow-control.
|
||||
# This is useful for modems that have
|
||||
# a small internal packet buffer, but
|
||||
# support packet flow control instead.
|
||||
flow_control = false
|
||||
|
||||
|
||||
# If you're using Reticulum on amateur radio spectrum,
|
||||
# you might want to use the AX.25 KISS interface. This
|
||||
# way, Reticulum will automatically encapsulate it's
|
||||
# traffic in AX.25 and also identify your stations
|
||||
# transmissions with your callsign and SSID.
|
||||
#
|
||||
# Only do this if you really need to! Reticulum doesn't
|
||||
# need the AX.25 layer for anything, and it incurs extra
|
||||
# overhead on every packet to encapsulate in AX.25.
|
||||
#
|
||||
# A more efficient way is to use the plain KISS interface
|
||||
# with the beaconing functionality described above.
|
||||
|
||||
[[Packet Radio AX.25 KISS Interface]]
|
||||
type = AX25KISSInterface
|
||||
|
||||
# Set the station callsign and SSID
|
||||
callsign = NO1CLL
|
||||
ssid = 0
|
||||
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = False
|
||||
|
||||
# Allow transmit on interface.
|
||||
outgoing = true
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB2
|
||||
|
||||
# Set the serial baud-rate and other
|
||||
# configuration parameters.
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
|
||||
# Whether to use KISS flow-control.
|
||||
# This is useful for modems with a
|
||||
# small internal packet buffer.
|
||||
flow_control = false
|
||||
|
||||
# Set the modem preamble. A 150ms
|
||||
# preamble should be a reasonable
|
||||
# default, but may need to be
|
||||
# increased for radios with slow-
|
||||
# opening squelch and long TX/RX
|
||||
# turnaround
|
||||
preamble = 150
|
||||
|
||||
# Set the modem TX tail. In most
|
||||
# cases this should be kept as low
|
||||
# as possible to not waste airtime.
|
||||
txtail = 10
|
||||
|
||||
# Configure CDMA parameters. These
|
||||
# settings are reasonable defaults.
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
|
||||
'''.splitlines()
|
||||
|
||||
+15
-2
@@ -35,6 +35,7 @@ class Transport:
|
||||
PATHFINDER_T = 10 # Retry grace period
|
||||
PATHFINDER_RW = 10 # Random window for announce rebroadcast
|
||||
PATHFINDER_E = 60*60*24*7 # Path expiration in seconds
|
||||
AP_PATH_TIME = 60*60*24 # Expiration for Access Point paths
|
||||
|
||||
# TODO: Calculate an optimal number for this in
|
||||
# various situations
|
||||
@@ -511,13 +512,19 @@ class Transport:
|
||||
for interface in Transport.interfaces:
|
||||
if interface.OUT:
|
||||
should_transmit = True
|
||||
|
||||
if packet.destination.type == RNS.Destination.LINK:
|
||||
if packet.destination.status == RNS.Link.CLOSED:
|
||||
should_transmit = False
|
||||
if interface != packet.destination.attached_interface:
|
||||
should_transmit = False
|
||||
|
||||
if packet.attached_interface != None and interface != packet.attached_interface:
|
||||
should_transmit = False
|
||||
|
||||
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
||||
if packet.attached_interface == None and interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
|
||||
should_transmit = False
|
||||
|
||||
if should_transmit:
|
||||
if not stored_hash:
|
||||
@@ -585,7 +592,9 @@ class Transport:
|
||||
Transport.jobs_locked = True
|
||||
|
||||
packet = RNS.Packet(None, raw)
|
||||
packet.unpack()
|
||||
if not packet.unpack():
|
||||
return
|
||||
|
||||
packet.receiving_interface = interface
|
||||
packet.hops += 1
|
||||
|
||||
@@ -860,12 +869,16 @@ class Transport:
|
||||
if should_add:
|
||||
now = time.time()
|
||||
retries = 0
|
||||
expires = now + Transport.PATHFINDER_E
|
||||
announce_hops = packet.hops
|
||||
local_rebroadcasts = 0
|
||||
block_rebroadcasts = False
|
||||
attached_interface = None
|
||||
retransmit_timeout = now + math.pow(Transport.PATHFINDER_C, packet.hops) + (RNS.rand() * Transport.PATHFINDER_RW)
|
||||
|
||||
if packet.receiving_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
|
||||
expires = now + Transport.AP_PATH_TIME
|
||||
else:
|
||||
expires = now + Transport.PATHFINDER_E
|
||||
|
||||
random_blobs.append(random_blob)
|
||||
|
||||
|
||||
+346
-1
@@ -27,10 +27,15 @@ def main():
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0)
|
||||
parser.add_argument('-q', '--quiet', action='count', default=0)
|
||||
parser.add_argument('-s', '--service', action='store_true', default=False, help="rnsd is running as a service and should log to file")
|
||||
parser.add_argument("--exampleconfig", action='store_true', default=False, help="print verbose configuration example to stdout and exit")
|
||||
parser.add_argument("--version", action="version", version="rnsd {version}".format(version=__version__))
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.exampleconfig:
|
||||
print(__example_rns_config__)
|
||||
exit()
|
||||
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
else:
|
||||
@@ -42,5 +47,345 @@ def main():
|
||||
print("")
|
||||
exit()
|
||||
|
||||
__example_rns_config__ = '''# This is an example Reticulum config file.
|
||||
# You should probably edit it to include any additional,
|
||||
# interfaces and settings you might need.
|
||||
|
||||
[reticulum]
|
||||
|
||||
# If you enable Transport, your system will route traffic
|
||||
# for other peers, pass announces and serve path requests.
|
||||
# This should be done for systems that are suited to act
|
||||
# as transport nodes, ie. if they are stationary and
|
||||
# always-on. This directive is optional and can be removed
|
||||
# for brevity.
|
||||
|
||||
enable_transport = False
|
||||
|
||||
|
||||
# By default, the first program to launch the Reticulum
|
||||
# Network Stack will create a shared instance, that other
|
||||
# programs can communicate with. Only the shared instance
|
||||
# opens all the configured interfaces directly, and other
|
||||
# local programs communicate with the shared instance over
|
||||
# a local socket. This is completely transparent to the
|
||||
# user, and should generally be turned on. This directive
|
||||
# is optional and can be removed for brevity.
|
||||
|
||||
share_instance = Yes
|
||||
|
||||
|
||||
# If you want to run multiple *different* shared instances
|
||||
# on the same system, you will need to specify different
|
||||
# shared instance ports for each. The defaults are given
|
||||
# below, and again, these options can be left out if you
|
||||
# don't need them.
|
||||
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
|
||||
# You can configure Reticulum to panic and forcibly close
|
||||
# if an unrecoverable interface error occurs, such as the
|
||||
# hardware device for an interface disappearing. This is
|
||||
# an optional directive, and can be left out for brevity.
|
||||
# This behaviour is disabled by default.
|
||||
|
||||
panic_on_interface_error = No
|
||||
|
||||
|
||||
[logging]
|
||||
# Valid log levels are 0 through 7:
|
||||
# 0: Log only critical information
|
||||
# 1: Log errors and lower log levels
|
||||
# 2: Log warnings and lower log levels
|
||||
# 3: Log notices and lower log levels
|
||||
# 4: Log info and lower (this is the default)
|
||||
# 5: Verbose logging
|
||||
# 6: Debug logging
|
||||
# 7: Extreme logging
|
||||
|
||||
loglevel = 4
|
||||
|
||||
|
||||
# The interfaces section defines the physical and virtual
|
||||
# interfaces Reticulum will use to communicate on. This
|
||||
# section will contain examples for a variety of interface
|
||||
# types. You can modify these or use them as a basis for
|
||||
# your own config, or simply remove the unused ones.
|
||||
|
||||
[interfaces]
|
||||
|
||||
# This interface enables communication with other
|
||||
# link-local Reticulum nodes over UDP. It does not
|
||||
# need any functional IP infrastructure like routers
|
||||
# or DHCP servers, but will require that at least link-
|
||||
# local IPv6 is enabled in your operating system, which
|
||||
# should be enabled by default in almost any OS. See
|
||||
# the Reticulum Manual for more configuration options.
|
||||
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
|
||||
|
||||
# The following example enables communication with other
|
||||
# local Reticulum peers using UDP broadcasts.
|
||||
|
||||
[[UDP Interface]]
|
||||
type = UDPInterface
|
||||
interface_enabled = False
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
forward_ip = 255.255.255.255
|
||||
forward_port = 4242
|
||||
|
||||
# The above configuration will allow communication
|
||||
# within the local broadcast domains of all local
|
||||
# IP interfaces.
|
||||
|
||||
# Instead of specifying listen_ip, listen_port,
|
||||
# forward_ip and forward_port, you can also bind
|
||||
# to a specific network device like below.
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
# Assuming the eth0 device has the address
|
||||
# 10.55.0.72/24, the above configuration would
|
||||
# be equivalent to the following manual setup.
|
||||
# Note that we are both listening and forwarding to
|
||||
# the broadcast address of the network segments.
|
||||
|
||||
# listen_ip = 10.55.0.255
|
||||
# listen_port = 4242
|
||||
# forward_ip = 10.55.0.255
|
||||
# forward_port = 4242
|
||||
|
||||
# You can of course also communicate only with
|
||||
# a single IP address
|
||||
|
||||
# listen_ip = 10.55.0.15
|
||||
# listen_port = 4242
|
||||
# forward_ip = 10.55.0.16
|
||||
# forward_port = 4242
|
||||
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = False
|
||||
|
||||
# This configuration will listen on all IP
|
||||
# interfaces on port 4242
|
||||
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
|
||||
# Alternatively you can bind to a specific IP
|
||||
|
||||
# listen_ip = 10.0.0.88
|
||||
# listen_port = 4242
|
||||
|
||||
# Or a specific network device
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
|
||||
# To connect to a TCP server interface, you would
|
||||
# naturally use the TCP client interface. Here's
|
||||
# an example. The target_host can either be an IP
|
||||
# address or a hostname
|
||||
|
||||
[[TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = False
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
|
||||
# This example shows how to make your Reticulum
|
||||
# instance available over I2P, and connect to
|
||||
# another I2P peer. Please be aware that you
|
||||
# must have an I2P router running on your system
|
||||
# with the SAMv3 API enabled for this to work.
|
||||
|
||||
[[I2P]]
|
||||
type = I2PInterface
|
||||
interface_enabled = yes
|
||||
connectable = yes
|
||||
peers = 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p
|
||||
|
||||
|
||||
# Here's an example of how to add a LoRa interface
|
||||
# using the RNode LoRa transceiver.
|
||||
|
||||
[[RNode LoRa Interface]]
|
||||
type = RNodeInterface
|
||||
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = False
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
|
||||
# Set frequency to 867.2 MHz
|
||||
frequency = 867200000
|
||||
|
||||
# Set LoRa bandwidth to 125 KHz
|
||||
bandwidth = 125000
|
||||
|
||||
# Set TX power to 7 dBm (5 mW)
|
||||
txpower = 7
|
||||
|
||||
# Select spreading factor 8. Valid
|
||||
# range is 7 through 12, with 7
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 8
|
||||
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
|
||||
# You can configure the RNode to send
|
||||
# out identification on the channel with
|
||||
# a set interval by configuring the
|
||||
# following two parameters. The trans-
|
||||
# ceiver will only ID if the set
|
||||
# interval has elapsed since it's last
|
||||
# actual transmission. The interval is
|
||||
# configured in seconds.
|
||||
# This option is commented out and not
|
||||
# used by default.
|
||||
# id_callsign = MYCALL-0
|
||||
# id_interval = 600
|
||||
|
||||
# For certain homebrew RNode interfaces
|
||||
# with low amounts of RAM, using packet
|
||||
# flow control can be useful. By default
|
||||
# it is disabled.
|
||||
flow_control = False
|
||||
|
||||
|
||||
# An example KISS modem interface. Useful for running
|
||||
# Reticulum over packet radio hardware.
|
||||
|
||||
[[Packet Radio KISS Interface]]
|
||||
type = KISSInterface
|
||||
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = False
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB1
|
||||
|
||||
# Set the serial baud-rate and other
|
||||
# configuration parameters.
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
|
||||
# Set the modem preamble. A 150ms
|
||||
# preamble should be a reasonable
|
||||
# default, but may need to be
|
||||
# increased for radios with slow-
|
||||
# opening squelch and long TX/RX
|
||||
# turnaround
|
||||
preamble = 150
|
||||
|
||||
# Set the modem TX tail. In most
|
||||
# cases this should be kept as low
|
||||
# as possible to not waste airtime.
|
||||
txtail = 10
|
||||
|
||||
# Configure CDMA parameters. These
|
||||
# settings are reasonable defaults.
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
|
||||
# You can configure the interface to send
|
||||
# out identification on the channel with
|
||||
# a set interval by configuring the
|
||||
# following two parameters. The KISS
|
||||
# interface will only ID if the set
|
||||
# interval has elapsed since it's last
|
||||
# actual transmission. The interval is
|
||||
# configured in seconds.
|
||||
# This option is commented out and not
|
||||
# used by default.
|
||||
# id_callsign = MYCALL-0
|
||||
# id_interval = 600
|
||||
|
||||
# Whether to use KISS flow-control.
|
||||
# This is useful for modems that have
|
||||
# a small internal packet buffer, but
|
||||
# support packet flow control instead.
|
||||
flow_control = false
|
||||
|
||||
|
||||
# If you're using Reticulum on amateur radio spectrum,
|
||||
# you might want to use the AX.25 KISS interface. This
|
||||
# way, Reticulum will automatically encapsulate it's
|
||||
# traffic in AX.25 and also identify your stations
|
||||
# transmissions with your callsign and SSID.
|
||||
#
|
||||
# Only do this if you really need to! Reticulum doesn't
|
||||
# need the AX.25 layer for anything, and it incurs extra
|
||||
# overhead on every packet to encapsulate in AX.25.
|
||||
#
|
||||
# A more efficient way is to use the plain KISS interface
|
||||
# with the beaconing functionality described above.
|
||||
|
||||
[[Packet Radio AX.25 KISS Interface]]
|
||||
type = AX25KISSInterface
|
||||
|
||||
# Set the station callsign and SSID
|
||||
callsign = NO1CLL
|
||||
ssid = 0
|
||||
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = False
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB2
|
||||
|
||||
# Set the serial baud-rate and other
|
||||
# configuration parameters.
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
|
||||
# Whether to use KISS flow-control.
|
||||
# This is useful for modems with a
|
||||
# small internal packet buffer.
|
||||
flow_control = false
|
||||
|
||||
# Set the modem preamble. A 150ms
|
||||
# preamble should be a reasonable
|
||||
# default, but may need to be
|
||||
# increased for radios with slow-
|
||||
# opening squelch and long TX/RX
|
||||
# turnaround
|
||||
preamble = 150
|
||||
|
||||
# Set the modem TX tail. In most
|
||||
# cases this should be kept as low
|
||||
# as possible to not waste airtime.
|
||||
txtail = 10
|
||||
|
||||
# Configure CDMA parameters. These
|
||||
# settings are reasonable defaults.
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
|
||||
'''
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -51,6 +51,10 @@ def program_setup(configdir, dispall=False, verbosity = 0):
|
||||
|
||||
print(" {n}".format(n=ifstat["name"]))
|
||||
print("\tStatus: {ss}".format(ss=ss))
|
||||
|
||||
if "i2p_b32" in ifstat:
|
||||
print("\tI2P B32: {ep}".format(ep=str(ifstat["i2p_b32"])))
|
||||
|
||||
if clients != None:
|
||||
print("\t"+clients_string)
|
||||
print("\tRX: {rxb}\n\tTX: {txb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
|
||||
|
||||
@@ -109,6 +109,11 @@ def rand():
|
||||
return result
|
||||
|
||||
def hexrep(data, delimit=True):
|
||||
try:
|
||||
iter(data)
|
||||
except TypeError:
|
||||
data = [data]
|
||||
|
||||
delimiter = ":"
|
||||
if not delimit:
|
||||
delimiter = ""
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "0.3.0"
|
||||
__version__ = "0.3.3"
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2018 Viktor Villainov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
A modern asynchronous library for building I2P applications.
|
||||
"""
|
||||
|
||||
from .__version__ import (
|
||||
__title__, __description__, __url__, __version__,
|
||||
__author__, __author_email__, __license__, __copyright__
|
||||
)
|
||||
|
||||
from .sam import Destination, PrivateKey
|
||||
|
||||
from .aiosam import (
|
||||
get_sam_socket, dest_lookup, new_destination,
|
||||
create_session, stream_connect, stream_accept,
|
||||
Session, StreamConnection, StreamAcceptor
|
||||
)
|
||||
|
||||
from .tunnel import ClientTunnel, ServerTunnel
|
||||
|
||||
from .utils import get_sam_address
|
||||
|
||||
from .exceptions import (
|
||||
CantReachPeer, DuplicatedDest, DuplicatedId, I2PError,
|
||||
InvalidId, InvalidKey, KeyNotFound, PeerNotFound, Timeout,
|
||||
)
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
__title__ = 'i2plib'
|
||||
__description__ = 'A modern asynchronous library for building I2P applications.'
|
||||
__url__ = 'https://github.com/l-n-s/i2plib'
|
||||
__version__ = '0.0.14'
|
||||
__author__ = 'Viktor Villainov'
|
||||
__author_email__ = 'supervillain@riseup.net'
|
||||
__license__ = 'MIT'
|
||||
__copyright__ = 'Copyright 2018 Viktor Villainov'
|
||||
Vendored
+258
@@ -0,0 +1,258 @@
|
||||
import asyncio
|
||||
|
||||
from . import sam
|
||||
from . import exceptions
|
||||
from . import utils
|
||||
from .log import logger
|
||||
|
||||
def parse_reply(data):
|
||||
if not data:
|
||||
raise ConnectionAbortedError("Empty response: SAM API went offline")
|
||||
|
||||
try:
|
||||
msg = sam.Message(data.decode().strip())
|
||||
logger.debug("SAM reply: "+str(msg))
|
||||
except:
|
||||
raise ConnectionAbortedError("Invalid SAM response")
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
async def get_sam_socket(sam_address=sam.DEFAULT_ADDRESS, loop=None):
|
||||
"""A couroutine used to create a new SAM socket.
|
||||
|
||||
:param sam_address: (optional) SAM API address
|
||||
:param loop: (optional) event loop instance
|
||||
:return: A (reader, writer) pair
|
||||
"""
|
||||
reader, writer = await asyncio.open_connection(*sam_address, loop=loop)
|
||||
writer.write(sam.hello("3.1", "3.1"))
|
||||
reply = parse_reply(await reader.readline())
|
||||
if reply.ok:
|
||||
return (reader, writer)
|
||||
else:
|
||||
writer.close()
|
||||
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
|
||||
|
||||
async def dest_lookup(domain, sam_address=sam.DEFAULT_ADDRESS,
|
||||
loop=None):
|
||||
"""A coroutine used to lookup a full I2P destination by .i2p domain or
|
||||
.b32.i2p address.
|
||||
|
||||
:param domain: Address to be resolved, can be a .i2p domain or a .b32.i2p
|
||||
address.
|
||||
:param sam_address: (optional) SAM API address
|
||||
:param loop: (optional) Event loop instance
|
||||
:return: An instance of :class:`Destination`
|
||||
"""
|
||||
reader, writer = await get_sam_socket(sam_address, loop)
|
||||
writer.write(sam.naming_lookup(domain))
|
||||
reply = parse_reply(await reader.readline())
|
||||
writer.close()
|
||||
if reply.ok:
|
||||
return sam.Destination(reply["VALUE"])
|
||||
else:
|
||||
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
|
||||
|
||||
async def new_destination(sam_address=sam.DEFAULT_ADDRESS, loop=None,
|
||||
sig_type=sam.Destination.default_sig_type):
|
||||
"""A coroutine used to generate a new destination with a private key of a
|
||||
chosen signature type.
|
||||
|
||||
:param sam_address: (optional) SAM API address
|
||||
:param loop: (optional) Event loop instance
|
||||
:param sig_type: (optional) Signature type
|
||||
:return: An instance of :class:`Destination`
|
||||
"""
|
||||
reader, writer = await get_sam_socket(sam_address, loop)
|
||||
writer.write(sam.dest_generate(sig_type))
|
||||
reply = parse_reply(await reader.readline())
|
||||
writer.close()
|
||||
return sam.Destination(reply["PRIV"], has_private_key=True)
|
||||
|
||||
async def create_session(session_name, sam_address=sam.DEFAULT_ADDRESS,
|
||||
loop=None, style="STREAM",
|
||||
signature_type=sam.Destination.default_sig_type,
|
||||
destination=None, options={}):
|
||||
"""A coroutine used to create a new SAM session.
|
||||
|
||||
:param session_name: Session nick name
|
||||
:param sam_address: (optional) SAM API address
|
||||
:param loop: (optional) Event loop instance
|
||||
:param style: (optional) Session style, can be STREAM, DATAGRAM, RAW
|
||||
:param signature_type: (optional) If the destination is TRANSIENT, this
|
||||
signature type is used
|
||||
:param destination: (optional) Destination to use in this session. Can be
|
||||
a base64 encoded string, :class:`Destination`
|
||||
instance or None. TRANSIENT destination is used when it
|
||||
is None.
|
||||
:param options: (optional) A dict object with i2cp options
|
||||
:return: A (reader, writer) pair
|
||||
"""
|
||||
logger.debug("Creating session {}".format(session_name))
|
||||
if destination:
|
||||
if type(destination) == sam.Destination:
|
||||
destination = destination
|
||||
else:
|
||||
destination = sam.Destination(
|
||||
destination, has_private_key=True)
|
||||
|
||||
dest_string = destination.private_key.base64
|
||||
else:
|
||||
dest_string = sam.TRANSIENT_DESTINATION
|
||||
|
||||
options = " ".join(["{}={}".format(k, v) for k, v in options.items()])
|
||||
|
||||
reader, writer = await get_sam_socket(sam_address, loop)
|
||||
writer.write(sam.session_create(
|
||||
style, session_name, dest_string, options))
|
||||
|
||||
reply = parse_reply(await reader.readline())
|
||||
if reply.ok:
|
||||
if not destination:
|
||||
destination = sam.Destination(
|
||||
reply["DESTINATION"], has_private_key=True)
|
||||
logger.debug(destination.base32)
|
||||
logger.debug("Session created {}".format(session_name))
|
||||
return (reader, writer)
|
||||
else:
|
||||
writer.close()
|
||||
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
|
||||
|
||||
async def stream_connect(session_name, destination,
|
||||
sam_address=sam.DEFAULT_ADDRESS, loop=None):
|
||||
"""A coroutine used to connect to a remote I2P destination.
|
||||
|
||||
:param session_name: Session nick name
|
||||
:param destination: I2P destination to connect to
|
||||
:param sam_address: (optional) SAM API address
|
||||
:param loop: (optional) Event loop instance
|
||||
:return: A (reader, writer) pair
|
||||
"""
|
||||
logger.debug("Connecting stream {}".format(session_name))
|
||||
if isinstance(destination, str) and not destination.endswith(".i2p"):
|
||||
destination = sam.Destination(destination)
|
||||
elif isinstance(destination, str):
|
||||
destination = await dest_lookup(destination, sam_address, loop)
|
||||
|
||||
reader, writer = await get_sam_socket(sam_address, loop)
|
||||
writer.write(sam.stream_connect(session_name, destination.base64,
|
||||
silent="false"))
|
||||
reply = parse_reply(await reader.readline())
|
||||
if reply.ok:
|
||||
logger.debug("Stream connected {}".format(session_name))
|
||||
return (reader, writer)
|
||||
else:
|
||||
writer.close()
|
||||
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
|
||||
|
||||
async def stream_accept(session_name, sam_address=sam.DEFAULT_ADDRESS,
|
||||
loop=None):
|
||||
"""A coroutine used to accept a connection from the I2P network.
|
||||
|
||||
:param session_name: Session nick name
|
||||
:param sam_address: (optional) SAM API address
|
||||
:param loop: (optional) Event loop instance
|
||||
:return: A (reader, writer) pair
|
||||
"""
|
||||
reader, writer = await get_sam_socket(sam_address, loop)
|
||||
writer.write(sam.stream_accept(session_name, silent="false"))
|
||||
reply = parse_reply(await reader.readline())
|
||||
if reply.ok:
|
||||
return (reader, writer)
|
||||
else:
|
||||
writer.close()
|
||||
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
|
||||
|
||||
### Context managers
|
||||
|
||||
class Session:
|
||||
"""Async SAM session context manager.
|
||||
|
||||
:param session_name: Session nick name
|
||||
:param sam_address: (optional) SAM API address
|
||||
:param loop: (optional) Event loop instance
|
||||
:param style: (optional) Session style, can be STREAM, DATAGRAM, RAW
|
||||
:param signature_type: (optional) If the destination is TRANSIENT, this
|
||||
signature type is used
|
||||
:param destination: (optional) Destination to use in this session. Can be
|
||||
a base64 encoded string, :class:`Destination`
|
||||
instance or None. TRANSIENT destination is used when it
|
||||
is None.
|
||||
:param options: (optional) A dict object with i2cp options
|
||||
:return: :class:`Session` object
|
||||
"""
|
||||
def __init__(self, session_name, sam_address=sam.DEFAULT_ADDRESS,
|
||||
loop=None, style="STREAM",
|
||||
signature_type=sam.Destination.default_sig_type,
|
||||
destination=None, options={}):
|
||||
self.session_name = session_name
|
||||
self.sam_address = sam_address
|
||||
self.loop = loop
|
||||
self.style = style
|
||||
self.signature_type = signature_type
|
||||
self.destination = destination
|
||||
self.options = options
|
||||
|
||||
async def __aenter__(self):
|
||||
self.reader, self.writer = await create_session(self.session_name,
|
||||
sam_address=self.sam_address, loop=self.loop, style=self.style,
|
||||
signature_type=self.signature_type,
|
||||
destination=self.destination, options=self.options)
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
### TODO handle exceptions
|
||||
self.writer.close()
|
||||
|
||||
class StreamConnection:
|
||||
"""Async stream connection context manager.
|
||||
|
||||
:param session_name: Session nick name
|
||||
:param destination: I2P destination to connect to
|
||||
:param sam_address: (optional) SAM API address
|
||||
:param loop: (optional) Event loop instance
|
||||
:return: :class:`StreamConnection` object
|
||||
"""
|
||||
def __init__(self, session_name, destination,
|
||||
sam_address=sam.DEFAULT_ADDRESS, loop=None):
|
||||
self.session_name = session_name
|
||||
self.sam_address = sam_address
|
||||
self.loop = loop
|
||||
self.destination = destination
|
||||
|
||||
async def __aenter__(self):
|
||||
self.reader, self.writer = await stream_connect(self.session_name,
|
||||
self.destination, sam_address=self.sam_address, loop=self.loop)
|
||||
self.read = self.reader.read
|
||||
self.write = self.writer.write
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
### TODO handle exceptions
|
||||
self.writer.close()
|
||||
|
||||
class StreamAcceptor:
|
||||
"""Async stream acceptor context manager.
|
||||
|
||||
:param session_name: Session nick name
|
||||
:param sam_address: (optional) SAM API address
|
||||
:param loop: (optional) Event loop instance
|
||||
:return: :class:`StreamAcceptor` object
|
||||
"""
|
||||
def __init__(self, session_name, sam_address=sam.DEFAULT_ADDRESS,
|
||||
loop=None):
|
||||
self.session_name = session_name
|
||||
self.sam_address = sam_address
|
||||
self.loop = loop
|
||||
|
||||
async def __aenter__(self):
|
||||
self.reader, self.writer = await stream_accept(self.session_name,
|
||||
sam_address=self.sam_address, loop=self.loop)
|
||||
self.read = self.reader.read
|
||||
self.write = self.writer.write
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
### TODO handle exceptions
|
||||
self.writer.close()
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
# SAM exceptions
|
||||
|
||||
class SAMException(IOError):
|
||||
"""Base class for SAM exceptions"""
|
||||
|
||||
class CantReachPeer(SAMException):
|
||||
"""The peer exists, but cannot be reached"""
|
||||
|
||||
class DuplicatedDest(SAMException):
|
||||
"""The specified Destination is already in use"""
|
||||
|
||||
class DuplicatedId(SAMException):
|
||||
"""The nickname is already associated with a session"""
|
||||
|
||||
class I2PError(SAMException):
|
||||
"""A generic I2P error"""
|
||||
|
||||
class InvalidId(SAMException):
|
||||
"""STREAM SESSION ID doesn't exist"""
|
||||
|
||||
class InvalidKey(SAMException):
|
||||
"""The specified key is not valid (bad format, etc.)"""
|
||||
|
||||
class KeyNotFound(SAMException):
|
||||
"""The naming system can't resolve the given name"""
|
||||
|
||||
class PeerNotFound(SAMException):
|
||||
"""The peer cannot be found on the network"""
|
||||
|
||||
class Timeout(SAMException):
|
||||
"""The peer cannot be found on the network"""
|
||||
|
||||
SAM_EXCEPTIONS = {
|
||||
"CANT_REACH_PEER": CantReachPeer,
|
||||
"DUPLICATED_DEST": DuplicatedDest,
|
||||
"DUPLICATED_ID": DuplicatedId,
|
||||
"I2P_ERROR": I2PError,
|
||||
"INVALID_ID": InvalidId,
|
||||
"INVALID_KEY": InvalidKey,
|
||||
"KEY_NOT_FOUND": KeyNotFound,
|
||||
"PEER_NOT_FOUND": PeerNotFound,
|
||||
"TIMEOUT": Timeout,
|
||||
}
|
||||
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
"""Logging configuration."""
|
||||
import logging
|
||||
|
||||
# Name the logger after the package.
|
||||
logger = logging.getLogger(__package__)
|
||||
Vendored
+147
@@ -0,0 +1,147 @@
|
||||
from base64 import b64decode, b64encode, b32encode
|
||||
from hashlib import sha256
|
||||
import struct
|
||||
import re
|
||||
|
||||
|
||||
I2P_B64_CHARS = "-~"
|
||||
|
||||
def i2p_b64encode(x):
|
||||
"""Encode I2P destination"""
|
||||
return b64encode(x, altchars=I2P_B64_CHARS.encode()).decode()
|
||||
|
||||
def i2p_b64decode(x):
|
||||
"""Decode I2P destination"""
|
||||
return b64decode(x, altchars=I2P_B64_CHARS, validate=True)
|
||||
|
||||
SAM_BUFSIZE = 4096
|
||||
DEFAULT_ADDRESS = ("127.0.0.1", 7656)
|
||||
DEFAULT_MIN_VER = "3.1"
|
||||
DEFAULT_MAX_VER = "3.1"
|
||||
TRANSIENT_DESTINATION = "TRANSIENT"
|
||||
|
||||
VALID_BASE32_ADDRESS = re.compile(r"^([a-zA-Z0-9]{52}).b32.i2p$")
|
||||
VALID_BASE64_ADDRESS = re.compile(r"^([a-zA-Z0-9-~=]{516,528})$")
|
||||
|
||||
class Message(object):
|
||||
"""Parse SAM message to an object"""
|
||||
def __init__(self, s):
|
||||
self.opts = {}
|
||||
if type(s) != str:
|
||||
self._reply_string = s.decode().strip()
|
||||
else:
|
||||
self._reply_string = s
|
||||
|
||||
self.cmd, self.action, opts = self._reply_string.split(" ", 2)
|
||||
for v in opts.split(" "):
|
||||
data = v.split("=", 1) if "=" in v else (v, True)
|
||||
self.opts[data[0]] = data[1]
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.opts[key]
|
||||
|
||||
@property
|
||||
def ok(self):
|
||||
return self["RESULT"] == "OK"
|
||||
|
||||
def __repr__(self):
|
||||
return self._reply_string
|
||||
|
||||
|
||||
# SAM request messages
|
||||
|
||||
def hello(min_version, max_version):
|
||||
return "HELLO VERSION MIN={} MAX={}\n".format(min_version,
|
||||
max_version).encode()
|
||||
|
||||
def session_create(style, session_id, destination, options=""):
|
||||
return "SESSION CREATE STYLE={} ID={} DESTINATION={} {}\n".format(
|
||||
style, session_id, destination, options).encode()
|
||||
|
||||
|
||||
def stream_connect(session_id, destination, silent="false"):
|
||||
return "STREAM CONNECT ID={} DESTINATION={} SILENT={}\n".format(
|
||||
session_id, destination, silent).encode()
|
||||
|
||||
def stream_accept(session_id, silent="false"):
|
||||
return "STREAM ACCEPT ID={} SILENT={}\n".format(session_id, silent).encode()
|
||||
|
||||
def stream_forward(session_id, port, options=""):
|
||||
return "STREAM FORWARD ID={} PORT={} {}\n".format(
|
||||
session_id, port, options).encode()
|
||||
|
||||
|
||||
|
||||
def naming_lookup(name):
|
||||
return "NAMING LOOKUP NAME={}\n".format(name).encode()
|
||||
|
||||
def dest_generate(signature_type):
|
||||
return "DEST GENERATE SIGNATURE_TYPE={}\n".format(signature_type).encode()
|
||||
|
||||
class Destination(object):
|
||||
"""I2P destination
|
||||
|
||||
https://geti2p.net/spec/common-structures#destination
|
||||
|
||||
:param data: (optional) Base64 encoded data or binary data
|
||||
:param path: (optional) A path to a file with binary data
|
||||
:param has_private_key: (optional) Does data have a private key?
|
||||
"""
|
||||
|
||||
ECDSA_SHA256_P256 = 1
|
||||
ECDSA_SHA384_P384 = 2
|
||||
ECDSA_SHA512_P521 = 3
|
||||
EdDSA_SHA512_Ed25519 = 7
|
||||
|
||||
default_sig_type = EdDSA_SHA512_Ed25519
|
||||
|
||||
_pubkey_size = 256
|
||||
_signkey_size = 128
|
||||
_min_cert_size = 3
|
||||
|
||||
def __init__(self, data=None, path=None, has_private_key=False):
|
||||
#: Binary destination
|
||||
self.data = bytes()
|
||||
#: Base64 encoded destination
|
||||
self.base64 = ""
|
||||
#: :class:`RNS.vendor.i2plib.PrivateKey` instance or None
|
||||
self.private_key = None
|
||||
|
||||
if path:
|
||||
with open(path, "rb") as f: data = f.read()
|
||||
|
||||
if data and has_private_key:
|
||||
self.private_key = PrivateKey(data)
|
||||
|
||||
cert_len = struct.unpack("!H", self.private_key.data[385:387])[0]
|
||||
data = self.private_key.data[:387+cert_len]
|
||||
|
||||
if not data:
|
||||
raise Exception("Can't create a destination with no data")
|
||||
|
||||
self.data = data if type(data) == bytes else i2p_b64decode(data)
|
||||
self.base64 = data if type(data) == str else i2p_b64encode(data)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Destination: {}>".format(self.base32)
|
||||
|
||||
@property
|
||||
def base32(self):
|
||||
"""Base32 destination hash of this destination"""
|
||||
desthash = sha256(self.data).digest()
|
||||
return b32encode(desthash).decode()[:52].lower()
|
||||
|
||||
class PrivateKey(object):
|
||||
"""I2P private key
|
||||
|
||||
https://geti2p.net/spec/common-structures#keysandcert
|
||||
|
||||
:param data: Base64 encoded data or binary data
|
||||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
#: Binary private key
|
||||
self.data = data if type(data) == bytes else i2p_b64decode(data)
|
||||
#: Base64 encoded private key
|
||||
self.base64 = data if type(data) == str else i2p_b64encode(data)
|
||||
|
||||
Vendored
+202
@@ -0,0 +1,202 @@
|
||||
import logging
|
||||
import asyncio
|
||||
import argparse
|
||||
|
||||
from . import sam
|
||||
from . import aiosam
|
||||
from . import utils
|
||||
from .log import logger
|
||||
|
||||
BUFFER_SIZE = 65536
|
||||
|
||||
async def proxy_data(reader, writer):
|
||||
"""Proxy data from reader to writer"""
|
||||
try:
|
||||
while True:
|
||||
data = await reader.read(BUFFER_SIZE)
|
||||
if not data:
|
||||
break
|
||||
writer.write(data)
|
||||
except Exception as e:
|
||||
logger.debug('proxy_data_task exception {}'.format(e))
|
||||
finally:
|
||||
try:
|
||||
writer.close()
|
||||
except RuntimeError:
|
||||
pass
|
||||
logger.debug('close connection')
|
||||
|
||||
class I2PTunnel(object):
|
||||
"""Base I2P Tunnel object, not to be used directly
|
||||
|
||||
:param local_address: A local address to use for a tunnel.
|
||||
E.g. ("127.0.0.1", 6668)
|
||||
:param destination: (optional) Destination to use for this tunnel. Can be
|
||||
a base64 encoded string, :class:`Destination`
|
||||
instance or None. A new destination is created when it
|
||||
is None.
|
||||
:param session_name: (optional) Session nick name. A new session nickname is
|
||||
generated if not specified.
|
||||
:param options: (optional) A dict object with i2cp options
|
||||
:param loop: (optional) Event loop instance
|
||||
:param sam_address: (optional) SAM API address
|
||||
"""
|
||||
|
||||
def __init__(self, local_address, destination=None, session_name=None,
|
||||
options={}, loop=None, sam_address=sam.DEFAULT_ADDRESS):
|
||||
self.local_address = local_address
|
||||
self.destination = destination
|
||||
self.session_name = session_name or utils.generate_session_id()
|
||||
self.options = options
|
||||
self.loop = loop
|
||||
self.sam_address = sam_address
|
||||
|
||||
async def _pre_run(self):
|
||||
if not self.destination:
|
||||
self.destination = await aiosam.new_destination(
|
||||
sam_address=self.sam_address, loop=self.loop)
|
||||
_, self.session_writer = await aiosam.create_session(
|
||||
self.session_name, style=self.style, options=self.options,
|
||||
sam_address=self.sam_address,
|
||||
loop=self.loop, destination=self.destination)
|
||||
|
||||
def stop(self):
|
||||
"""Stop the tunnel"""
|
||||
self.session_writer.close()
|
||||
|
||||
class ClientTunnel(I2PTunnel):
|
||||
"""Client tunnel, a subclass of tunnel.I2PTunnel
|
||||
|
||||
If you run a client tunnel with a local address ("127.0.0.1", 6668) and
|
||||
a remote destination "irc.echelon.i2p", all connections to 127.0.0.1:6668
|
||||
will be proxied to irc.echelon.i2p.
|
||||
|
||||
:param remote_destination: Remote I2P destination, can be either .i2p
|
||||
domain, .b32.i2p address, base64 destination or
|
||||
:class:`Destination` instance
|
||||
"""
|
||||
|
||||
def __init__(self, remote_destination, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.style = "STREAM"
|
||||
self.remote_destination = remote_destination
|
||||
|
||||
async def run(self):
|
||||
"""A coroutine used to run the tunnel"""
|
||||
await self._pre_run()
|
||||
|
||||
async def handle_client(client_reader, client_writer):
|
||||
"""Handle local client connection"""
|
||||
remote_reader, remote_writer = await aiosam.stream_connect(
|
||||
self.session_name, self.remote_destination,
|
||||
sam_address=self.sam_address, loop=self.loop)
|
||||
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
|
||||
loop=self.loop)
|
||||
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
|
||||
loop=self.loop)
|
||||
|
||||
self.server = await asyncio.start_server(handle_client, *self.local_address, loop=self.loop)
|
||||
|
||||
def stop(self):
|
||||
super().stop()
|
||||
self.server.close()
|
||||
|
||||
class ServerTunnel(I2PTunnel):
|
||||
"""Server tunnel, a subclass of tunnel.I2PTunnel
|
||||
|
||||
If you want to expose a local service 127.0.0.1:80 to the I2P network, run
|
||||
a server tunnel with a local address ("127.0.0.1", 80). If you don't
|
||||
provide a private key or a session name, it will use a TRANSIENT
|
||||
destination.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.style = "STREAM"
|
||||
|
||||
async def run(self):
|
||||
"""A coroutine used to run the tunnel"""
|
||||
await self._pre_run()
|
||||
|
||||
async def handle_client(incoming, client_reader, client_writer):
|
||||
# data and dest may come in one chunk
|
||||
dest, data = incoming.split(b"\n", 1)
|
||||
remote_destination = sam.Destination(dest.decode())
|
||||
logger.debug("{} client connected: {}.b32.i2p".format(
|
||||
self.session_name, remote_destination.base32))
|
||||
|
||||
try:
|
||||
remote_reader, remote_writer = await asyncio.wait_for(
|
||||
asyncio.open_connection(
|
||||
host=self.local_address[0],
|
||||
port=self.local_address[1], loop=self.loop),
|
||||
timeout=5, loop=self.loop)
|
||||
if data: remote_writer.write(data)
|
||||
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
|
||||
loop=self.loop)
|
||||
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
|
||||
loop=self.loop)
|
||||
except ConnectionRefusedError:
|
||||
client_writer.close()
|
||||
|
||||
async def server_loop():
|
||||
try:
|
||||
while True:
|
||||
client_reader, client_writer = await aiosam.stream_accept(
|
||||
self.session_name, sam_address=self.sam_address,
|
||||
loop=self.loop)
|
||||
incoming = await client_reader.read(BUFFER_SIZE)
|
||||
asyncio.ensure_future(handle_client(
|
||||
incoming, client_reader, client_writer), loop=self.loop)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
self.server_loop = asyncio.ensure_future(server_loop(), loop=self.loop)
|
||||
|
||||
def stop(self):
|
||||
super().stop()
|
||||
self.server_loop.cancel()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('type', metavar="TYPE", choices=('server', 'client'),
|
||||
help="Tunnel type (server or client)")
|
||||
parser.add_argument('address', metavar="ADDRESS",
|
||||
help="Local address (e.g. 127.0.0.1:8000)")
|
||||
parser.add_argument('--debug', '-d', action='store_true',
|
||||
help='Debugging')
|
||||
parser.add_argument('--key', '-k', default='', metavar='PRIVATE_KEY',
|
||||
help='Path to private key file')
|
||||
parser.add_argument('--destination', '-D', default='',
|
||||
metavar='DESTINATION', help='Remote destination')
|
||||
args = parser.parse_args()
|
||||
|
||||
SAM_ADDRESS = utils.get_sam_address()
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.set_debug(args.debug)
|
||||
|
||||
if args.key:
|
||||
destination = sam.Destination(path=args.key, has_private_key=True)
|
||||
else:
|
||||
destination = None
|
||||
|
||||
local_address = utils.address_from_string(args.address)
|
||||
|
||||
if args.type == "client":
|
||||
tunnel = ClientTunnel(args.destination, local_address, loop=loop,
|
||||
destination=destination, sam_address=SAM_ADDRESS)
|
||||
elif args.type == "server":
|
||||
tunnel = ServerTunnel(local_address, loop=loop, destination=destination,
|
||||
sam_address=SAM_ADDRESS)
|
||||
|
||||
asyncio.ensure_future(tunnel.run(), loop=loop)
|
||||
|
||||
try:
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
tunnel.stop()
|
||||
finally:
|
||||
loop.stop()
|
||||
loop.close()
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
import socket
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
|
||||
from . import sam
|
||||
|
||||
def get_free_port():
|
||||
"""Get a free port on your local host"""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind(('', 0))
|
||||
free_port = s.getsockname()[1]
|
||||
s.close()
|
||||
return free_port
|
||||
|
||||
def is_address_accessible(address):
|
||||
"""Check if address is accessible or down"""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
is_accessible = s.connect_ex(address) == 0
|
||||
s.close()
|
||||
return is_accessible
|
||||
|
||||
def address_from_string(address_string):
|
||||
"""Address tuple from host:port string"""
|
||||
address = address_string.split(":")
|
||||
return (address[0], int(address[1]))
|
||||
|
||||
def get_sam_address():
|
||||
"""
|
||||
Get SAM address from environment variable I2P_SAM_ADDRESS, or use a default
|
||||
value
|
||||
"""
|
||||
value = os.getenv("I2P_SAM_ADDRESS")
|
||||
return address_from_string(value) if value else sam.DEFAULT_ADDRESS
|
||||
|
||||
def generate_session_id(length=6):
|
||||
"""Generate random session id"""
|
||||
rand = random.SystemRandom()
|
||||
sid = [rand.choice(string.ascii_letters) for _ in range(length)]
|
||||
return "reticulum-" + "".join(sid)
|
||||
|
||||
Vendored
+34
-3
@@ -1,7 +1,38 @@
|
||||
def get_platform():
|
||||
from os import environ
|
||||
if 'ANDROID_ARGUMENT' in environ:
|
||||
return 'android'
|
||||
if "ANDROID_ARGUMENT" in environ:
|
||||
return "android"
|
||||
elif "ANDROID_ROOT" in environ:
|
||||
return "android"
|
||||
else:
|
||||
import sys
|
||||
return sys.platform
|
||||
return sys.platform
|
||||
|
||||
def is_darwin():
|
||||
if get_platform() == "darwin":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_android():
|
||||
if get_platform() == "android":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_windows():
|
||||
if str(get_platform()).startswith("win"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def platform_checks():
|
||||
if is_windows():
|
||||
import sys
|
||||
if sys.version_info.major >= 3 and sys.version_info.minor >= 8:
|
||||
pass
|
||||
else:
|
||||
import RNS
|
||||
RNS.log("On Windows, Reticulum requires Python 3.8 or higher.", RNS.LOG_ERROR)
|
||||
RNS.log("Please update Python to run Reticulum.", RNS.LOG_ERROR)
|
||||
RNS.panic()
|
||||
|
||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: 373c2d4526d24456ccd5dac65661415a
|
||||
config: 8cd01657672a2b3a4d1c8ecc92b32a11
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -6,6 +6,7 @@ The best way to get started with the Reticulum Network Stack depends on what
|
||||
you want to do. This guide will outline sensible starting paths for different
|
||||
scenarios.
|
||||
|
||||
|
||||
Try Using a Reticulum-based Program
|
||||
=============================================
|
||||
If you simply want to try using a program built with Reticulum, you can take
|
||||
@@ -29,6 +30,10 @@ You can install Nomad Network via pip:
|
||||
# ... and run
|
||||
nomadnet
|
||||
|
||||
**Please Note**: If this is the very first time you use pip to install a program
|
||||
on your system, you might need to reboot your system for your program to become
|
||||
available. If you get a "command not found" error or similar when running the
|
||||
program, reboot your system and try again.
|
||||
|
||||
|
||||
Using the Included Utilities
|
||||
@@ -44,6 +49,7 @@ network status and connectivity.
|
||||
To learn more about these utility programs, have a look at the
|
||||
:ref:`Using Reticulum on Your System<using-main>` chapter of this manual.
|
||||
|
||||
|
||||
Creating a Network With Reticulum
|
||||
=============================================
|
||||
To create a network, you will need to specify one or more *interfaces* for
|
||||
|
||||
@@ -39,7 +39,6 @@ system, which should be enabled by default in almost all OSes.
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# You can create multiple isolated Reticulum
|
||||
# networks on the same physical LAN by
|
||||
@@ -69,7 +68,6 @@ the discovery scope by setting it to one of ``link``, ``admin``, ``site``,
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# Configure global discovery
|
||||
|
||||
@@ -81,8 +79,175 @@ the discovery scope by setting it to one of ``link``, ``admin``, ``site``,
|
||||
discovery_port = 48555
|
||||
data_port = 49555
|
||||
|
||||
*Please Note!* If you use the Auto Interface, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
.. _interfaces-i2p:
|
||||
|
||||
I2P Interface
|
||||
=============
|
||||
|
||||
The I2P interface lets you connect Reticulum instances over the
|
||||
`Invisible Internet Protocol <https://i2pd.website>`_. This can be
|
||||
especially useful in cases where you want to host a globally reachable
|
||||
Reticulum instance, but do not have access to any public IP addresses,
|
||||
have a frequently changing IP address, or have firewalls blocking
|
||||
inbound traffic.
|
||||
|
||||
Using the I2P interface, you will get a globally reachable, portable
|
||||
and persistent I2P address that your Reticulum instance can be reached
|
||||
at.
|
||||
|
||||
To use the I2P interface, you must have an I2P router running
|
||||
on your system. The easiest way to acheive this is to download and
|
||||
install the `latest release <https://github.com/PurpleI2P/i2pd/releases/latest>`_
|
||||
of the ``ì2pd`` package. For more details about I2P, see the
|
||||
`geti2p.net website <https://geti2p.net/en/about/intro>`_.`
|
||||
|
||||
When an I2P router is running on your system, you can simply add
|
||||
an I2P interface to reticulum:
|
||||
|
||||
.. code::
|
||||
|
||||
[[I2P]]
|
||||
type = I2PInterface
|
||||
interface_enabled = yes
|
||||
connectable = yes
|
||||
|
||||
On the first start, Reticulum will generate a new I2P address for the
|
||||
interface and start listening for inbound traffic on it. This can take
|
||||
a while the first time, especially if your I2P router was also just
|
||||
started, and is not yet well-connected to the I2P network. When ready,
|
||||
you should see I2P base32 address printed to your log file. You can
|
||||
also inspect the status of the interface using the ``rnstatus`` utility.
|
||||
|
||||
To connect to other Reticulum instances over I2P, just add a comma-separated
|
||||
list of I2P base32 addresses to the ``peers`` option of the interface:
|
||||
|
||||
.. code::
|
||||
|
||||
[[I2P]]
|
||||
type = I2PInterface
|
||||
interface_enabled = yes
|
||||
connectable = yes
|
||||
peers = 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p
|
||||
|
||||
It can take anywhere from a few seconds to a few minutes to establish
|
||||
I2P connections to the desired peers, so Reticulum handles the process
|
||||
in the background, and will output relevant events to the log.
|
||||
|
||||
**Please Note!** While the I2P interface is the simplest way to use
|
||||
Reticulum over I2P, it is also possible to tunnel the TCP server and
|
||||
client interfaces over I2P manually. This can be useful in situations
|
||||
where more control is needed, but requires manual tunnel setup through
|
||||
the I2P daemon configuration.
|
||||
|
||||
It is important to note that the two methods are *interchangably compatible*.
|
||||
You can use the I2PInterface to connect to a TCPServerInterface that
|
||||
was manually tunneled over I2P, for example. This offers a high degree
|
||||
of flexibility in network setup, while retaining ease of use in simpler
|
||||
use-cases.
|
||||
|
||||
|
||||
.. _interfaces-tcps:
|
||||
|
||||
TCP Server Interface
|
||||
====================
|
||||
|
||||
The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
|
||||
.. code::
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = True
|
||||
|
||||
# This configuration will listen on all IP
|
||||
# interfaces on port 4242
|
||||
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
|
||||
# Alternatively you can bind to a specific IP
|
||||
|
||||
# listen_ip = 10.0.0.88
|
||||
# listen_port = 4242
|
||||
|
||||
# Or a specific network device
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
|
||||
.. code::
|
||||
|
||||
[[TCP Server on I2P]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = yes
|
||||
listen_ip = 127.0.0.1
|
||||
listen_port = 5001
|
||||
i2p_tunneled = yes
|
||||
|
||||
.. _interfaces-tcpc:
|
||||
|
||||
TCP Client Interface
|
||||
====================
|
||||
|
||||
To connect to a TCP server interface, you would naturally use the TCP client
|
||||
interface. Many TCP Client interfaces from different peers can connect to the
|
||||
same TCP Server interface at the same time.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface. The
|
||||
# target_host can either be an IP address or a hostname.
|
||||
|
||||
[[TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the ``kiss_framing`` option:
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface that connects
|
||||
# to a software TNC soundmodem on a KISS over TCP port.
|
||||
|
||||
[[TCP KISS Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
kiss_framing = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 8001
|
||||
|
||||
**Caution!** Only use the KISS framing option when connecting to external devices
|
||||
and programs like soundmodems and similar over TCP. When using the
|
||||
``TCPClientInterface`` in conjunction with the ``TCPServerInterface`` you should
|
||||
never enable ``kiss_framing``, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.
|
||||
|
||||
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
|
||||
.. code::
|
||||
|
||||
[[TCP Client over I2P]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = yes
|
||||
target_host = 127.0.0.1
|
||||
target_port = 5001
|
||||
i2p_tunneled = yes
|
||||
|
||||
|
||||
.. _interfaces-udp:
|
||||
@@ -113,7 +278,7 @@ pre-existing LAN.
|
||||
[[Default UDP Interface]]
|
||||
type = UDPInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
forward_ip = 255.255.255.255
|
||||
@@ -149,93 +314,6 @@ pre-existing LAN.
|
||||
# forward_ip = 10.55.0.16
|
||||
# forward_port = 4242
|
||||
|
||||
*Please Note!* If you use the ``device`` option, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
.. _interfaces-tcps:
|
||||
|
||||
TCP Server Interface
|
||||
====================
|
||||
|
||||
The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
|
||||
.. code::
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# This configuration will listen on all IP
|
||||
# interfaces on port 4242
|
||||
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
|
||||
# Alternatively you can bind to a specific IP
|
||||
|
||||
# listen_ip = 10.0.0.88
|
||||
# listen_port = 4242
|
||||
|
||||
# Or a specific network device
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
*Please Note!* If you use the ``device`` option, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
.. _interfaces-tcpc:
|
||||
|
||||
TCP Client Interface
|
||||
====================
|
||||
|
||||
To connect to a TCP server interface, you would naturally use the TCP client
|
||||
interface. Many TCP Client interfaces from different peers can connect to the
|
||||
same TCP Server interface at the same time.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface. The
|
||||
# target_host can either be an IP address or a hostname.
|
||||
|
||||
[[TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the ``kiss_framing`` option:
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface that connects
|
||||
# to a software TNC soundmodem on a KISS over TCP port.
|
||||
|
||||
[[TCP KISS Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
kiss_framing = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 8001
|
||||
|
||||
**Caution!** Only use the KISS framing option when connecting to external devices
|
||||
and programs like soundmodems and similar over TCP. When using the
|
||||
``TCPClientInterface`` in conjunction with the ``TCPServerInterface`` you should
|
||||
never enable ``kiss_framing``, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.
|
||||
|
||||
|
||||
.. _interfaces-rnode:
|
||||
|
||||
@@ -256,11 +334,6 @@ can be used, and offers full control over LoRa parameters.
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = True
|
||||
|
||||
# Allow transmit on interface. Setting
|
||||
# this to false will create a listen-
|
||||
# only interface.
|
||||
outgoing = true
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
|
||||
@@ -311,7 +384,6 @@ directly over a wire-pair, or for using devices such as data radios and lasers.
|
||||
[[Serial Interface]]
|
||||
type = SerialInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
@@ -338,7 +410,6 @@ for station identification purposes.
|
||||
[[Packet Radio KISS Interface]]
|
||||
type = KISSInterface
|
||||
interface_enabled = True
|
||||
outgoing = true
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB1
|
||||
@@ -409,9 +480,6 @@ beaconing functionality described above.
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = True
|
||||
|
||||
# Allow transmit on interface.
|
||||
outgoing = True
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB2
|
||||
|
||||
@@ -443,4 +511,39 @@ beaconing functionality described above.
|
||||
# Whether to use KISS flow-control.
|
||||
# This is useful for modems with a
|
||||
# small internal packet buffer.
|
||||
flow_control = false
|
||||
flow_control = false
|
||||
|
||||
|
||||
.. _interfaces-options:
|
||||
|
||||
Common Interface Options
|
||||
========================
|
||||
|
||||
A number of general options can be used to control various
|
||||
aspects of interface behaviour.
|
||||
|
||||
The ``interface_enabled`` option tells Reticulum whether or not
|
||||
to bring up the interface. Defaults to ``False``. For any
|
||||
interface to be brought up, the ``interface_enabled`` option
|
||||
must be set to ``True`` or ``Yes``.
|
||||
|
||||
The ``outgoing`` option sets whether an interface is allowed
|
||||
to transmit. Defaults to ``True``. If set to ``False`` the
|
||||
interface will only receive data, and never transmit.
|
||||
|
||||
The ``interface_mode`` option allows selecting the high-level
|
||||
behaviour of the interface from a number of options.
|
||||
|
||||
- The default value is ``full``. In this mode, all discovery,
|
||||
meshing and transpor functionality is available.
|
||||
|
||||
- In the ``access_point`` (or shorthand ``ap``) mode, the
|
||||
interface will operate as a network access point. In this
|
||||
mode, announces will not be automatically broadcasted on
|
||||
the interface, and paths to destinations on the interface
|
||||
will have a much shorter expiry time. This mode is useful
|
||||
for creating interfaces that are mostly quiet, unless when
|
||||
someone is actually using them. An example of this could
|
||||
be a radio interface serving a wide area, where users are
|
||||
expected to connect momentarily, use the network, and then
|
||||
disappear again.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.3.0 beta',
|
||||
VERSION: '0.3.3 beta',
|
||||
LANGUAGE: 'None',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Code Examples — Reticulum Network Stack 0.3.0 beta documentation</title>
|
||||
<title>Code Examples — Reticulum Network Stack 0.3.3 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<li class="right" >
|
||||
<a href="reference.html" title="API Reference"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -2366,7 +2366,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
<li class="right" >
|
||||
<a href="reference.html" title="API Reference"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Index — Reticulum Network Stack 0.3.0 beta documentation</title>
|
||||
<title>Index — Reticulum Network Stack 0.3.3 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="#" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Index</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -416,7 +416,7 @@
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="#" title="General Index"
|
||||
>index</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Index</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Getting Started Fast — Reticulum Network Stack 0.3.0 beta documentation</title>
|
||||
<title>Getting Started Fast — Reticulum Network Stack 0.3.3 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<li class="right" >
|
||||
<a href="whatis.html" title="What is Reticulum?"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -63,6 +63,10 @@ for the messaging and information-sharing protocol
|
||||
<span class="n">nomadnet</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Please Note</strong>: If this is the very first time you use pip to install a program
|
||||
on your system, you might need to reboot your system for your program to become
|
||||
available. If you get a “command not found” error or similar when running the
|
||||
program, reboot your system and try again.</p>
|
||||
</div>
|
||||
<div class="section" id="using-the-included-utilities">
|
||||
<h2>Using the Included Utilities<a class="headerlink" href="#using-the-included-utilities" title="Permalink to this headline">¶</a></h2>
|
||||
@@ -264,7 +268,7 @@ here at a later point.</p>
|
||||
<li class="right" >
|
||||
<a href="whatis.html" title="What is Reticulum?"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Reticulum Network Stack Manual — Reticulum Network Stack 0.3.0 beta documentation</title>
|
||||
<title>Reticulum Network Stack Manual — Reticulum Network Stack 0.3.3 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<li class="right" >
|
||||
<a href="whatis.html" title="What is Reticulum?"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -89,13 +89,15 @@ to participate in the development of Reticulum itself.</p>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#auto-interface">Auto Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#udp-interface">UDP Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#i2p-interface">I2P Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-server-interface">TCP Server Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-client-interface">TCP Client Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#udp-interface">UDP Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#rnode-lora-interface">RNode LoRa Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#serial-interface">Serial Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#kiss-interface">KISS Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#ax-25-kiss-interface">AX.25 KISS Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#common-interface-options">Common Interface Options</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a><ul>
|
||||
@@ -208,7 +210,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="right" >
|
||||
<a href="whatis.html" title="What is Reticulum?"
|
||||
>next</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
+177
-93
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Supported Interfaces — Reticulum Network Stack 0.3.0 beta documentation</title>
|
||||
<title>Supported Interfaces — Reticulum Network Stack 0.3.3 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<li class="right" >
|
||||
<a href="networks.html" title="Building Networks"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -68,7 +68,6 @@ system, which should be enabled by default in almost all OSes.</p>
|
||||
<span class="p">[[</span><span class="n">Default</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">AutoInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># You can create multiple isolated Reticulum</span>
|
||||
<span class="c1"># networks on the same physical LAN by</span>
|
||||
@@ -95,7 +94,6 @@ the discovery scope by setting it to one of <code class="docutils literal notran
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Default</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">AutoInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># Configure global discovery</span>
|
||||
|
||||
@@ -108,8 +106,146 @@ the discovery scope by setting it to one of <code class="docutils literal notran
|
||||
<span class="n">data_port</span> <span class="o">=</span> <span class="mi">49555</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><em>Please Note!</em> If you use the Auto Interface, you will need the Python module
|
||||
<code class="docutils literal notranslate"><span class="pre">netifaces</span></code> installed on your system. You can install it with <code class="docutils literal notranslate"><span class="pre">pip3</span> <span class="pre">install</span> <span class="pre">netifaces</span></code>.</p>
|
||||
</div>
|
||||
<div class="section" id="i2p-interface">
|
||||
<span id="interfaces-i2p"></span><h2>I2P Interface<a class="headerlink" href="#i2p-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>The I2P interface lets you connect Reticulum instances over the
|
||||
<a class="reference external" href="https://i2pd.website">Invisible Internet Protocol</a>. This can be
|
||||
especially useful in cases where you want to host a globally reachable
|
||||
Reticulum instance, but do not have access to any public IP addresses,
|
||||
have a frequently changing IP address, or have firewalls blocking
|
||||
inbound traffic.</p>
|
||||
<p>Using the I2P interface, you will get a globally reachable, portable
|
||||
and persistent I2P address that your Reticulum instance can be reached
|
||||
at.</p>
|
||||
<p>To use the I2P interface, you must have an I2P router running
|
||||
on your system. The easiest way to acheive this is to download and
|
||||
install the <a class="reference external" href="https://github.com/PurpleI2P/i2pd/releases/latest">latest release</a>
|
||||
of the <code class="docutils literal notranslate"><span class="pre">ì2pd</span></code> package. For more details about I2P, see the
|
||||
<a class="reference external" href="https://geti2p.net/en/about/intro">geti2p.net website</a>.`</p>
|
||||
<p>When an I2P router is running on your system, you can simply add
|
||||
an I2P interface to reticulum:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">I2P</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">I2PInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">connectable</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>On the first start, Reticulum will generate a new I2P address for the
|
||||
interface and start listening for inbound traffic on it. This can take
|
||||
a while the first time, especially if your I2P router was also just
|
||||
started, and is not yet well-connected to the I2P network. When ready,
|
||||
you should see I2P base32 address printed to your log file. You can
|
||||
also inspect the status of the interface using the <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code> utility.</p>
|
||||
<p>To connect to other Reticulum instances over I2P, just add a comma-separated
|
||||
list of I2P base32 addresses to the <code class="docutils literal notranslate"><span class="pre">peers</span></code> option of the interface:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">I2P</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">I2PInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">connectable</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">peers</span> <span class="o">=</span> <span class="mi">5</span><span class="n">urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq</span><span class="o">.</span><span class="n">b32</span><span class="o">.</span><span class="n">i2p</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>It can take anywhere from a few seconds to a few minutes to establish
|
||||
I2P connections to the desired peers, so Reticulum handles the process
|
||||
in the background, and will output relevant events to the log.</p>
|
||||
<p><strong>Please Note!</strong> While the I2P interface is the simplest way to use
|
||||
Reticulum over I2P, it is also possible to tunnel the TCP server and
|
||||
client interfaces over I2P manually. This can be useful in situations
|
||||
where more control is needed, but requires manual tunnel setup through
|
||||
the I2P daemon configuration.</p>
|
||||
<p>It is important to note that the two methods are <em>interchangably compatible</em>.
|
||||
You can use the I2PInterface to connect to a TCPServerInterface that
|
||||
was manually tunneled over I2P, for example. This offers a high degree
|
||||
of flexibility in network setup, while retaining ease of use in simpler
|
||||
use-cases.</p>
|
||||
</div>
|
||||
<div class="section" id="tcp-server-interface">
|
||||
<span id="interfaces-tcps"></span><h2>TCP Server Interface<a class="headerlink" href="#tcp-server-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
|
||||
<span class="c1"># It will listen for incoming connections on the</span>
|
||||
<span class="c1"># specified IP address and port number.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># This configuration will listen on all IP</span>
|
||||
<span class="c1"># interfaces on port 4242</span>
|
||||
|
||||
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">.</span><span class="mf">0.0</span>
|
||||
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
|
||||
<span class="c1"># Alternatively you can bind to a specific IP</span>
|
||||
|
||||
<span class="c1"># listen_ip = 10.0.0.88</span>
|
||||
<span class="c1"># listen_port = 4242</span>
|
||||
|
||||
<span class="c1"># Or a specific network device</span>
|
||||
|
||||
<span class="c1"># device = eth0</span>
|
||||
<span class="c1"># port = 4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Please Note!</strong> The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">on</span> <span class="n">I2P</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
|
||||
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">5001</span>
|
||||
<span class="n">i2p_tunneled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="tcp-client-interface">
|
||||
<span id="interfaces-tcpc"></span><h2>TCP Client Interface<a class="headerlink" href="#tcp-client-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>To connect to a TCP server interface, you would naturally use the TCP client
|
||||
interface. Many TCP Client interfaces from different peers can connect to the
|
||||
same TCP Server interface at the same time.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here's an example of a TCP Client interface. The</span>
|
||||
<span class="c1"># target_host can either be an IP address or a hostname.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code> option:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here's an example of a TCP Client interface that connects</span>
|
||||
<span class="c1"># to a software TNC soundmodem on a KISS over TCP port.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">TCP</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">kiss_framing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">8001</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Caution!</strong> Only use the KISS framing option when connecting to external devices
|
||||
and programs like soundmodems and similar over TCP. When using the
|
||||
<code class="docutils literal notranslate"><span class="pre">TCPClientInterface</span></code> in conjunction with the <code class="docutils literal notranslate"><span class="pre">TCPServerInterface</span></code> you should
|
||||
never enable <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code>, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.</p>
|
||||
<p><strong>Please Note!</strong> The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">over</span> <span class="n">I2P</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">5001</span>
|
||||
<span class="n">i2p_tunneled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="udp-interface">
|
||||
<span id="interfaces-udp"></span><h2>UDP Interface<a class="headerlink" href="#udp-interface" title="Permalink to this headline">¶</a></h2>
|
||||
@@ -131,7 +267,7 @@ pre-existing LAN.</p>
|
||||
<span class="p">[[</span><span class="n">Default</span> <span class="n">UDP</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">UDPInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">.</span><span class="mf">0.0</span>
|
||||
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
<span class="n">forward_ip</span> <span class="o">=</span> <span class="mf">255.255</span><span class="o">.</span><span class="mf">255.255</span>
|
||||
@@ -168,80 +304,6 @@ pre-existing LAN.</p>
|
||||
<span class="c1"># forward_port = 4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><em>Please Note!</em> If you use the <code class="docutils literal notranslate"><span class="pre">device</span></code> option, you will need the Python module
|
||||
<code class="docutils literal notranslate"><span class="pre">netifaces</span></code> installed on your system. You can install it with <code class="docutils literal notranslate"><span class="pre">pip3</span> <span class="pre">install</span> <span class="pre">netifaces</span></code>.</p>
|
||||
</div>
|
||||
<div class="section" id="tcp-server-interface">
|
||||
<span id="interfaces-tcps"></span><h2>TCP Server Interface<a class="headerlink" href="#tcp-server-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
|
||||
<span class="c1"># It will listen for incoming connections on the</span>
|
||||
<span class="c1"># specified IP address and port number.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># This configuration will listen on all IP</span>
|
||||
<span class="c1"># interfaces on port 4242</span>
|
||||
|
||||
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">.</span><span class="mf">0.0</span>
|
||||
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
|
||||
<span class="c1"># Alternatively you can bind to a specific IP</span>
|
||||
|
||||
<span class="c1"># listen_ip = 10.0.0.88</span>
|
||||
<span class="c1"># listen_port = 4242</span>
|
||||
|
||||
<span class="c1"># Or a specific network device</span>
|
||||
|
||||
<span class="c1"># device = eth0</span>
|
||||
<span class="c1"># port = 4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><em>Please Note!</em> If you use the <code class="docutils literal notranslate"><span class="pre">device</span></code> option, you will need the Python module
|
||||
<code class="docutils literal notranslate"><span class="pre">netifaces</span></code> installed on your system. You can install it with <code class="docutils literal notranslate"><span class="pre">pip3</span> <span class="pre">install</span> <span class="pre">netifaces</span></code>.</p>
|
||||
</div>
|
||||
<div class="section" id="tcp-client-interface">
|
||||
<span id="interfaces-tcpc"></span><h2>TCP Client Interface<a class="headerlink" href="#tcp-client-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>To connect to a TCP server interface, you would naturally use the TCP client
|
||||
interface. Many TCP Client interfaces from different peers can connect to the
|
||||
same TCP Server interface at the same time.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here's an example of a TCP Client interface. The</span>
|
||||
<span class="c1"># target_host can either be an IP address or a hostname.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code> option:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here's an example of a TCP Client interface that connects</span>
|
||||
<span class="c1"># to a software TNC soundmodem on a KISS over TCP port.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">TCP</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">kiss_framing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">8001</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Caution!</strong> Only use the KISS framing option when connecting to external devices
|
||||
and programs like soundmodems and similar over TCP. When using the
|
||||
<code class="docutils literal notranslate"><span class="pre">TCPClientInterface</span></code> in conjunction with the <code class="docutils literal notranslate"><span class="pre">TCPServerInterface</span></code> you should
|
||||
never enable <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code>, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.</p>
|
||||
</div>
|
||||
<div class="section" id="rnode-lora-interface">
|
||||
<span id="interfaces-rnode"></span><h2>RNode LoRa Interface<a class="headerlink" href="#rnode-lora-interface" title="Permalink to this headline">¶</a></h2>
|
||||
@@ -256,11 +318,6 @@ can be used, and offers full control over LoRa parameters.</p>
|
||||
<span class="c1"># Enable interface if you want use it!</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># Allow transmit on interface. Setting</span>
|
||||
<span class="c1"># this to false will create a listen-</span>
|
||||
<span class="c1"># only interface.</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="n">true</span>
|
||||
|
||||
<span class="c1"># Serial port for the device</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
|
||||
|
||||
@@ -307,7 +364,6 @@ directly over a wire-pair, or for using devices such as data radios and lasers.<
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Serial</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">SerialInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># Serial port for the device</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
|
||||
@@ -330,7 +386,6 @@ for station identification purposes.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Packet</span> <span class="n">Radio</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">KISSInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="n">true</span>
|
||||
|
||||
<span class="c1"># Serial port for the device</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB1</span>
|
||||
@@ -395,9 +450,6 @@ beaconing functionality described above.</p>
|
||||
<span class="c1"># Enable interface if you want use it!</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># Allow transmit on interface.</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># Serial port for the device</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB2</span>
|
||||
|
||||
@@ -433,6 +485,36 @@ beaconing functionality described above.</p>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="common-interface-options">
|
||||
<span id="interfaces-options"></span><h2>Common Interface Options<a class="headerlink" href="#common-interface-options" title="Permalink to this headline">¶</a></h2>
|
||||
<p>A number of general options can be used to control various
|
||||
aspects of interface behaviour.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">interface_enabled</span></code> option tells Reticulum whether or not
|
||||
to bring up the interface. Defaults to <code class="docutils literal notranslate"><span class="pre">False</span></code>. For any
|
||||
interface to be brought up, the <code class="docutils literal notranslate"><span class="pre">interface_enabled</span></code> option
|
||||
must be set to <code class="docutils literal notranslate"><span class="pre">True</span></code> or <code class="docutils literal notranslate"><span class="pre">Yes</span></code>.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">outgoing</span></code> option sets whether an interface is allowed
|
||||
to transmit. Defaults to <code class="docutils literal notranslate"><span class="pre">True</span></code>. If set to <code class="docutils literal notranslate"><span class="pre">False</span></code> the
|
||||
interface will only receive data, and never transmit.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">interface_mode</span></code> option allows selecting the high-level
|
||||
behaviour of the interface from a number of options.</p>
|
||||
<blockquote>
|
||||
<div><ul class="simple">
|
||||
<li><p>The default value is <code class="docutils literal notranslate"><span class="pre">full</span></code>. In this mode, all discovery,
|
||||
meshing and transpor functionality is available.</p></li>
|
||||
<li><p>In the <code class="docutils literal notranslate"><span class="pre">access_point</span></code> (or shorthand <code class="docutils literal notranslate"><span class="pre">ap</span></code>) mode, the
|
||||
interface will operate as a network access point. In this
|
||||
mode, announces will not be automatically broadcasted on
|
||||
the interface, and paths to destinations on the interface
|
||||
will have a much shorter expiry time. This mode is useful
|
||||
for creating interfaces that are mostly quiet, unless when
|
||||
someone is actually using them. An example of this could
|
||||
be a radio interface serving a wide area, where users are
|
||||
expected to connect momentarily, use the network, and then
|
||||
disappear again.</p></li>
|
||||
</ul>
|
||||
</div></blockquote>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -446,13 +528,15 @@ beaconing functionality described above.</p>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Supported Interfaces</a><ul>
|
||||
<li><a class="reference internal" href="#auto-interface">Auto Interface</a></li>
|
||||
<li><a class="reference internal" href="#udp-interface">UDP Interface</a></li>
|
||||
<li><a class="reference internal" href="#i2p-interface">I2P Interface</a></li>
|
||||
<li><a class="reference internal" href="#tcp-server-interface">TCP Server Interface</a></li>
|
||||
<li><a class="reference internal" href="#tcp-client-interface">TCP Client Interface</a></li>
|
||||
<li><a class="reference internal" href="#udp-interface">UDP Interface</a></li>
|
||||
<li><a class="reference internal" href="#rnode-lora-interface">RNode LoRa Interface</a></li>
|
||||
<li><a class="reference internal" href="#serial-interface">Serial Interface</a></li>
|
||||
<li><a class="reference internal" href="#kiss-interface">KISS Interface</a></li>
|
||||
<li><a class="reference internal" href="#ax-25-kiss-interface">AX.25 KISS Interface</a></li>
|
||||
<li><a class="reference internal" href="#common-interface-options">Common Interface Options</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -496,7 +580,7 @@ beaconing functionality described above.</p>
|
||||
<li class="right" >
|
||||
<a href="networks.html" title="Building Networks"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Building Networks — Reticulum Network Stack 0.3.0 beta documentation</title>
|
||||
<title>Building Networks — Reticulum Network Stack 0.3.3 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<li class="right" >
|
||||
<a href="using.html" title="Using Reticulum on Your System"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -247,7 +247,7 @@ connected outliers are now an integral part of the network.</p>
|
||||
<li class="right" >
|
||||
<a href="using.html" title="Using Reticulum on Your System"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>API Reference — Reticulum Network Stack 0.3.0 beta documentation</title>
|
||||
<title>API Reference — Reticulum Network Stack 0.3.3 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<li class="right" >
|
||||
<a href="understanding.html" title="Understanding Reticulum"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1238,7 +1238,7 @@ will announce it.</p>
|
||||
<li class="right" >
|
||||
<a href="understanding.html" title="Understanding Reticulum"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Search — Reticulum Network Stack 0.3.0 beta documentation</title>
|
||||
<title>Search — Reticulum Network Stack 0.3.3 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Search</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -85,7 +85,7 @@
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
>index</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Search</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Understanding Reticulum — Reticulum Network Stack 0.3.0 beta documentation</title>
|
||||
<title>Understanding Reticulum — Reticulum Network Stack 0.3.3 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<li class="right" >
|
||||
<a href="interfaces.html" title="Supported Interfaces"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -856,7 +856,7 @@ proof 11
|
||||
<li class="right" >
|
||||
<a href="interfaces.html" title="Supported Interfaces"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Using Reticulum on Your System — Reticulum Network Stack 0.3.0 beta documentation</title>
|
||||
<title>Using Reticulum on Your System — Reticulum Network Stack 0.3.3 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<li class="right" >
|
||||
<a href="gettingstartedfast.html" title="Getting Started Fast"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -330,7 +330,7 @@ WantedBy=multi-user.target
|
||||
<li class="right" >
|
||||
<a href="gettingstartedfast.html" title="Getting Started Fast"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>What is Reticulum? — Reticulum Network Stack 0.3.0 beta documentation</title>
|
||||
<title>What is Reticulum? — Reticulum Network Stack 0.3.3 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<li class="right" >
|
||||
<a href="index.html" title="Reticulum Network Stack Manual"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -184,7 +184,7 @@ network, and vice versa.</p>
|
||||
<li class="right" >
|
||||
<a href="index.html" title="Reticulum Network Stack Manual"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ copyright = '2021, Mark Qvist'
|
||||
author = 'Mark Qvist'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.3.0 beta'
|
||||
release = '0.3.3 beta'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
@@ -65,4 +65,4 @@ html_static_path = ['_static']
|
||||
# return False
|
||||
|
||||
# def setup(app):
|
||||
# app.connect('autodoc-skip-member', check_skip_member)
|
||||
# app.connect('autodoc-skip-member', check_skip_member)
|
||||
|
||||
@@ -6,6 +6,7 @@ The best way to get started with the Reticulum Network Stack depends on what
|
||||
you want to do. This guide will outline sensible starting paths for different
|
||||
scenarios.
|
||||
|
||||
|
||||
Try Using a Reticulum-based Program
|
||||
=============================================
|
||||
If you simply want to try using a program built with Reticulum, you can take
|
||||
@@ -29,6 +30,10 @@ You can install Nomad Network via pip:
|
||||
# ... and run
|
||||
nomadnet
|
||||
|
||||
**Please Note**: If this is the very first time you use pip to install a program
|
||||
on your system, you might need to reboot your system for your program to become
|
||||
available. If you get a "command not found" error or similar when running the
|
||||
program, reboot your system and try again.
|
||||
|
||||
|
||||
Using the Included Utilities
|
||||
@@ -44,6 +49,7 @@ network status and connectivity.
|
||||
To learn more about these utility programs, have a look at the
|
||||
:ref:`Using Reticulum on Your System<using-main>` chapter of this manual.
|
||||
|
||||
|
||||
Creating a Network With Reticulum
|
||||
=============================================
|
||||
To create a network, you will need to specify one or more *interfaces* for
|
||||
|
||||
+206
-103
@@ -39,7 +39,6 @@ system, which should be enabled by default in almost all OSes.
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# You can create multiple isolated Reticulum
|
||||
# networks on the same physical LAN by
|
||||
@@ -69,7 +68,6 @@ the discovery scope by setting it to one of ``link``, ``admin``, ``site``,
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# Configure global discovery
|
||||
|
||||
@@ -81,8 +79,175 @@ the discovery scope by setting it to one of ``link``, ``admin``, ``site``,
|
||||
discovery_port = 48555
|
||||
data_port = 49555
|
||||
|
||||
*Please Note!* If you use the Auto Interface, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
.. _interfaces-i2p:
|
||||
|
||||
I2P Interface
|
||||
=============
|
||||
|
||||
The I2P interface lets you connect Reticulum instances over the
|
||||
`Invisible Internet Protocol <https://i2pd.website>`_. This can be
|
||||
especially useful in cases where you want to host a globally reachable
|
||||
Reticulum instance, but do not have access to any public IP addresses,
|
||||
have a frequently changing IP address, or have firewalls blocking
|
||||
inbound traffic.
|
||||
|
||||
Using the I2P interface, you will get a globally reachable, portable
|
||||
and persistent I2P address that your Reticulum instance can be reached
|
||||
at.
|
||||
|
||||
To use the I2P interface, you must have an I2P router running
|
||||
on your system. The easiest way to acheive this is to download and
|
||||
install the `latest release <https://github.com/PurpleI2P/i2pd/releases/latest>`_
|
||||
of the ``ì2pd`` package. For more details about I2P, see the
|
||||
`geti2p.net website <https://geti2p.net/en/about/intro>`_.`
|
||||
|
||||
When an I2P router is running on your system, you can simply add
|
||||
an I2P interface to reticulum:
|
||||
|
||||
.. code::
|
||||
|
||||
[[I2P]]
|
||||
type = I2PInterface
|
||||
interface_enabled = yes
|
||||
connectable = yes
|
||||
|
||||
On the first start, Reticulum will generate a new I2P address for the
|
||||
interface and start listening for inbound traffic on it. This can take
|
||||
a while the first time, especially if your I2P router was also just
|
||||
started, and is not yet well-connected to the I2P network. When ready,
|
||||
you should see I2P base32 address printed to your log file. You can
|
||||
also inspect the status of the interface using the ``rnstatus`` utility.
|
||||
|
||||
To connect to other Reticulum instances over I2P, just add a comma-separated
|
||||
list of I2P base32 addresses to the ``peers`` option of the interface:
|
||||
|
||||
.. code::
|
||||
|
||||
[[I2P]]
|
||||
type = I2PInterface
|
||||
interface_enabled = yes
|
||||
connectable = yes
|
||||
peers = 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p
|
||||
|
||||
It can take anywhere from a few seconds to a few minutes to establish
|
||||
I2P connections to the desired peers, so Reticulum handles the process
|
||||
in the background, and will output relevant events to the log.
|
||||
|
||||
**Please Note!** While the I2P interface is the simplest way to use
|
||||
Reticulum over I2P, it is also possible to tunnel the TCP server and
|
||||
client interfaces over I2P manually. This can be useful in situations
|
||||
where more control is needed, but requires manual tunnel setup through
|
||||
the I2P daemon configuration.
|
||||
|
||||
It is important to note that the two methods are *interchangably compatible*.
|
||||
You can use the I2PInterface to connect to a TCPServerInterface that
|
||||
was manually tunneled over I2P, for example. This offers a high degree
|
||||
of flexibility in network setup, while retaining ease of use in simpler
|
||||
use-cases.
|
||||
|
||||
|
||||
.. _interfaces-tcps:
|
||||
|
||||
TCP Server Interface
|
||||
====================
|
||||
|
||||
The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
|
||||
.. code::
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = True
|
||||
|
||||
# This configuration will listen on all IP
|
||||
# interfaces on port 4242
|
||||
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
|
||||
# Alternatively you can bind to a specific IP
|
||||
|
||||
# listen_ip = 10.0.0.88
|
||||
# listen_port = 4242
|
||||
|
||||
# Or a specific network device
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
|
||||
.. code::
|
||||
|
||||
[[TCP Server on I2P]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = yes
|
||||
listen_ip = 127.0.0.1
|
||||
listen_port = 5001
|
||||
i2p_tunneled = yes
|
||||
|
||||
.. _interfaces-tcpc:
|
||||
|
||||
TCP Client Interface
|
||||
====================
|
||||
|
||||
To connect to a TCP server interface, you would naturally use the TCP client
|
||||
interface. Many TCP Client interfaces from different peers can connect to the
|
||||
same TCP Server interface at the same time.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface. The
|
||||
# target_host can either be an IP address or a hostname.
|
||||
|
||||
[[TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the ``kiss_framing`` option:
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface that connects
|
||||
# to a software TNC soundmodem on a KISS over TCP port.
|
||||
|
||||
[[TCP KISS Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
kiss_framing = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 8001
|
||||
|
||||
**Caution!** Only use the KISS framing option when connecting to external devices
|
||||
and programs like soundmodems and similar over TCP. When using the
|
||||
``TCPClientInterface`` in conjunction with the ``TCPServerInterface`` you should
|
||||
never enable ``kiss_framing``, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.
|
||||
|
||||
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
|
||||
.. code::
|
||||
|
||||
[[TCP Client over I2P]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = yes
|
||||
target_host = 127.0.0.1
|
||||
target_port = 5001
|
||||
i2p_tunneled = yes
|
||||
|
||||
|
||||
.. _interfaces-udp:
|
||||
@@ -113,7 +278,7 @@ pre-existing LAN.
|
||||
[[Default UDP Interface]]
|
||||
type = UDPInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
forward_ip = 255.255.255.255
|
||||
@@ -149,93 +314,6 @@ pre-existing LAN.
|
||||
# forward_ip = 10.55.0.16
|
||||
# forward_port = 4242
|
||||
|
||||
*Please Note!* If you use the ``device`` option, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
.. _interfaces-tcps:
|
||||
|
||||
TCP Server Interface
|
||||
====================
|
||||
|
||||
The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
|
||||
.. code::
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# This configuration will listen on all IP
|
||||
# interfaces on port 4242
|
||||
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
|
||||
# Alternatively you can bind to a specific IP
|
||||
|
||||
# listen_ip = 10.0.0.88
|
||||
# listen_port = 4242
|
||||
|
||||
# Or a specific network device
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
*Please Note!* If you use the ``device`` option, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
.. _interfaces-tcpc:
|
||||
|
||||
TCP Client Interface
|
||||
====================
|
||||
|
||||
To connect to a TCP server interface, you would naturally use the TCP client
|
||||
interface. Many TCP Client interfaces from different peers can connect to the
|
||||
same TCP Server interface at the same time.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface. The
|
||||
# target_host can either be an IP address or a hostname.
|
||||
|
||||
[[TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the ``kiss_framing`` option:
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface that connects
|
||||
# to a software TNC soundmodem on a KISS over TCP port.
|
||||
|
||||
[[TCP KISS Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
kiss_framing = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 8001
|
||||
|
||||
**Caution!** Only use the KISS framing option when connecting to external devices
|
||||
and programs like soundmodems and similar over TCP. When using the
|
||||
``TCPClientInterface`` in conjunction with the ``TCPServerInterface`` you should
|
||||
never enable ``kiss_framing``, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.
|
||||
|
||||
|
||||
.. _interfaces-rnode:
|
||||
|
||||
@@ -256,11 +334,6 @@ can be used, and offers full control over LoRa parameters.
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = True
|
||||
|
||||
# Allow transmit on interface. Setting
|
||||
# this to false will create a listen-
|
||||
# only interface.
|
||||
outgoing = true
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
|
||||
@@ -311,7 +384,6 @@ directly over a wire-pair, or for using devices such as data radios and lasers.
|
||||
[[Serial Interface]]
|
||||
type = SerialInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
@@ -338,7 +410,6 @@ for station identification purposes.
|
||||
[[Packet Radio KISS Interface]]
|
||||
type = KISSInterface
|
||||
interface_enabled = True
|
||||
outgoing = true
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB1
|
||||
@@ -409,9 +480,6 @@ beaconing functionality described above.
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = True
|
||||
|
||||
# Allow transmit on interface.
|
||||
outgoing = True
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB2
|
||||
|
||||
@@ -443,4 +511,39 @@ beaconing functionality described above.
|
||||
# Whether to use KISS flow-control.
|
||||
# This is useful for modems with a
|
||||
# small internal packet buffer.
|
||||
flow_control = false
|
||||
flow_control = false
|
||||
|
||||
|
||||
.. _interfaces-options:
|
||||
|
||||
Common Interface Options
|
||||
========================
|
||||
|
||||
A number of general options can be used to control various
|
||||
aspects of interface behaviour.
|
||||
|
||||
The ``interface_enabled`` option tells Reticulum whether or not
|
||||
to bring up the interface. Defaults to ``False``. For any
|
||||
interface to be brought up, the ``interface_enabled`` option
|
||||
must be set to ``True`` or ``Yes``.
|
||||
|
||||
The ``outgoing`` option sets whether an interface is allowed
|
||||
to transmit. Defaults to ``True``. If set to ``False`` the
|
||||
interface will only receive data, and never transmit.
|
||||
|
||||
The ``interface_mode`` option allows selecting the high-level
|
||||
behaviour of the interface from a number of options.
|
||||
|
||||
- The default value is ``full``. In this mode, all discovery,
|
||||
meshing and transpor functionality is available.
|
||||
|
||||
- In the ``access_point`` (or shorthand ``ap``) mode, the
|
||||
interface will operate as a network access point. In this
|
||||
mode, announces will not be automatically broadcasted on
|
||||
the interface, and paths to destinations on the interface
|
||||
will have a much shorter expiry time. This mode is useful
|
||||
for creating interfaces that are mostly quiet, unless when
|
||||
someone is actually using them. An example of this could
|
||||
be a radio interface serving a wide area, where users are
|
||||
expected to connect momentarily, use the network, and then
|
||||
disappear again.
|
||||
|
||||
Reference in New Issue
Block a user