2022-04-01 09:18:18 -06:00
# MIT License
#
2023-09-30 16:16:32 -06:00
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io
2022-04-01 09:18:18 -06:00
#
# 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.
2020-05-13 08:01:27 -06:00
from . Interface import Interface
import socketserver
import threading
import socket
import time
import sys
import os
import RNS
2023-10-27 10:12:53 -06:00
from threading import Lock
2020-05-13 08:01:27 -06:00
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 ThreadingTCPServer ( socketserver . ThreadingMixIn , socketserver . TCPServer ) :
2023-04-18 20:01:21 -06:00
def server_bind ( self ) :
2023-05-03 04:21:57 -06:00
if RNS . vendor . platformutils . is_windows ( ) :
2023-04-18 20:01:21 -06:00
self . socket . setsockopt ( socket . SOL_SOCKET , socket . SO_EXCLUSIVEADDRUSE , 1 )
else :
self . socket . setsockopt ( socket . SOL_SOCKET , socket . SO_REUSEADDR , 1 )
self . socket . bind ( self . server_address )
self . server_address = self . socket . getsockname ( )
2020-05-13 08:01:27 -06:00
class LocalClientInterface ( Interface ) :
2023-10-18 15:18:59 -06:00
RECONNECT_WAIT = 8
2020-05-13 08:01:27 -06:00
def __init__ ( self , owner , name , target_port = None , connected_socket = None ) :
2023-09-30 11:11:10 -06:00
super ( ) . __init__ ( )
2022-06-09 06:45:00 -06:00
# TODO: Remove at some point
2022-07-01 13:16:01 -06:00
# self.rxptime = 0
2022-05-29 07:43:50 -06:00
self . HW_MTU = 1064
2021-09-24 12:10:04 -06:00
self . online = False
2020-05-13 08:01:27 -06:00
self . IN = True
self . OUT = False
self . socket = None
self . parent_interface = None
2021-12-10 10:32:24 -07:00
self . reconnecting = False
self . never_connected = True
self . detached = False
2020-05-13 08:01:27 -06:00
self . name = name
2022-02-25 10:47:55 -07:00
self . mode = RNS . Interfaces . Interface . Interface . MODE_FULL
2020-05-13 08:01:27 -06:00
if connected_socket != None :
self . receives = True
self . target_ip = None
self . target_port = None
self . socket = connected_socket
2023-11-05 14:57:03 -07:00
self . socket . setsockopt ( socket . IPPROTO_TCP , socket . TCP_NODELAY , 1 )
2020-05-13 08:01:27 -06:00
2021-09-24 08:42:31 -06:00
self . is_connected_to_shared_instance = False
2020-05-13 08:01:27 -06:00
elif target_port != None :
self . receives = True
self . target_ip = " 127.0.0.1 "
self . target_port = target_port
2021-12-10 10:32:24 -07:00
self . connect ( )
2020-05-13 12:33:10 -06:00
2020-05-13 08:01:27 -06:00
self . owner = owner
2022-04-17 11:07:32 -06:00
self . bitrate = 1000 * 1000 * 1000
2020-05-13 08:01:27 -06:00
self . online = True
self . writing = False
2023-03-04 22:37:58 -07:00
self . _force_bitrate = False
2022-05-14 10:09:38 -06:00
self . announce_rate_target = None
self . announce_rate_grace = None
self . announce_rate_penalty = None
2020-05-13 08:01:27 -06:00
if connected_socket == None :
thread = threading . Thread ( target = self . read_loop )
2022-09-30 11:02:25 -06:00
thread . daemon = True
2020-05-13 08:01:27 -06:00
thread . start ( )
2023-09-30 16:16:32 -06:00
def should_ingress_limit ( self ) :
return False
2021-12-10 10:32:24 -07:00
def connect ( self ) :
self . socket = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
2023-11-05 14:57:03 -07:00
self . socket . setsockopt ( socket . IPPROTO_TCP , socket . TCP_NODELAY , 1 )
2021-12-10 10:32:24 -07:00
self . socket . connect ( ( self . target_ip , self . target_port ) )
self . online = True
self . is_connected_to_shared_instance = True
self . never_connected = False
return True
def reconnect ( self ) :
if self . is_connected_to_shared_instance :
if not self . reconnecting :
self . reconnecting = True
attempts = 0
while not self . online :
time . sleep ( LocalClientInterface . RECONNECT_WAIT )
attempts + = 1
try :
self . connect ( )
except Exception as e :
RNS . log ( " Connection attempt for " + str ( self ) + " failed: " + str ( e ) , RNS . LOG_DEBUG )
if not self . never_connected :
2022-06-12 10:55:06 -06:00
RNS . log ( " Reconnected socket for " + str ( self ) + " . " , RNS . LOG_INFO )
2021-12-10 10:32:24 -07:00
self . reconnecting = False
thread = threading . Thread ( target = self . read_loop )
2022-09-30 11:02:25 -06:00
thread . daemon = True
2021-12-10 10:32:24 -07:00
thread . start ( )
2023-10-18 15:18:59 -06:00
def job ( ) :
time . sleep ( LocalClientInterface . RECONNECT_WAIT + 2 )
RNS . Transport . shared_connection_reappeared ( )
threading . Thread ( target = job , daemon = True ) . start ( )
2021-12-10 10:32:24 -07:00
else :
RNS . log ( " Attempt to reconnect on a non-initiator shared local interface. This should not happen. " , RNS . LOG_ERROR )
raise IOError ( " Attempt to reconnect on a non-initiator local interface " )
2020-05-13 08:01:27 -06:00
def processIncoming ( self , data ) :
2021-09-24 12:10:04 -06:00
self . rxb + = len ( data )
if hasattr ( self , " parent_interface " ) and self . parent_interface != None :
self . parent_interface . rxb + = len ( data )
2022-06-09 06:45:00 -06:00
# TODO: Remove at some point
2022-07-01 13:16:01 -06:00
# processing_start = time.time()
2022-06-09 06:45:00 -06:00
2020-05-13 08:01:27 -06:00
self . owner . inbound ( data , self )
2022-06-09 06:45:00 -06:00
# TODO: Remove at some point
2022-07-01 13:16:01 -06:00
# duration = time.time() - processing_start
# self.rxptime += duration
2021-12-10 10:32:24 -07:00
2020-05-13 08:01:27 -06:00
def processOutgoing ( self , data ) :
if self . online :
try :
self . writing = True
2023-10-27 10:12:53 -06:00
2023-03-04 22:37:58 -07:00
if self . _force_bitrate :
2023-10-27 10:12:53 -06:00
if not hasattr ( self , " send_lock " ) :
self . send_lock = Lock ( )
with self . send_lock :
s = len ( data ) / self . bitrate * 8
RNS . log ( f " Simulating latency of { RNS . prettytime ( s ) } for { len ( data ) } bytes " )
time . sleep ( s )
2020-05-13 08:01:27 -06:00
data = bytes ( [ HDLC . FLAG ] ) + HDLC . escape ( data ) + bytes ( [ HDLC . FLAG ] )
self . socket . sendall ( data )
self . writing = False
2021-09-24 12:10:04 -06:00
self . txb + = len ( data )
if hasattr ( self , " parent_interface " ) and self . parent_interface != None :
self . parent_interface . txb + = len ( data )
2020-05-13 08:01:27 -06:00
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 " "
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 ( in_frame and byte == HDLC . FLAG ) :
in_frame = False
self . processIncoming ( data_buffer )
elif ( byte == HDLC . FLAG ) :
in_frame = True
data_buffer = b " "
2022-05-29 07:43:50 -06:00
elif ( in_frame and len ( data_buffer ) < self . HW_MTU ) :
2020-05-13 08:01:27 -06:00
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 :
2021-12-10 10:32:24 -07:00
self . online = False
if self . is_connected_to_shared_instance and not self . detached :
RNS . log ( " Socket for " + str ( self ) + " was closed, attempting to reconnect... " , RNS . LOG_WARNING )
RNS . Transport . shared_connection_disappeared ( )
self . reconnect ( )
else :
self . teardown ( nowarning = True )
2020-05-13 08:01:27 -06:00
break
except Exception as e :
self . online = False
RNS . log ( " An interface error occurred, the contained exception was: " + str ( e ) , RNS . LOG_ERROR )
RNS . log ( " Tearing down " + str ( self ) , RNS . LOG_ERROR )
self . teardown ( )
2021-09-24 08:42:31 -06:00
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
2021-09-24 06:10:18 -06:00
def teardown ( self , nowarning = False ) :
2020-05-13 08:01:27 -06:00
self . online = False
self . OUT = False
self . IN = False
2020-05-13 12:33:10 -06:00
2020-05-13 08:01:27 -06:00
if self in RNS . Transport . interfaces :
RNS . Transport . interfaces . remove ( self )
2020-05-13 12:33:10 -06:00
if self in RNS . Transport . local_client_interfaces :
RNS . Transport . local_client_interfaces . remove ( self )
2021-09-24 12:10:04 -06:00
if hasattr ( self , " parent_interface " ) and self . parent_interface != None :
self . parent_interface . clients - = 1
2022-09-13 14:32:00 -06:00
RNS . Transport . owner . _should_persist_data ( )
2020-05-13 12:33:10 -06:00
2021-09-24 06:10:18 -06:00
if nowarning == False :
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 ( )
2021-09-18 14:49:04 -06:00
2021-09-24 08:42:31 -06:00
if self . is_connected_to_shared_instance :
2021-11-04 10:14:58 -06:00
if nowarning == False :
2021-12-10 10:32:24 -07:00
RNS . log ( " Permanently lost connection to local shared RNS instance. Exiting now. " , RNS . LOG_CRITICAL )
2021-11-04 10:14:58 -06:00
RNS . exit ( )
2021-09-24 08:42:31 -06:00
2020-05-13 08:01:27 -06:00
def __str__ ( self ) :
return " LocalInterface[ " + str ( self . target_port ) + " ] "
class LocalServerInterface ( Interface ) :
def __init__ ( self , owner , bindport = None ) :
2023-09-30 11:11:10 -06:00
super ( ) . __init__ ( )
2021-09-24 12:10:04 -06:00
self . online = False
self . clients = 0
2020-05-13 08:01:27 -06:00
self . IN = True
self . OUT = False
self . name = " Reticulum "
2022-02-25 10:47:55 -07:00
self . mode = RNS . Interfaces . Interface . Interface . MODE_FULL
2020-05-13 08:01:27 -06:00
if ( bindport != None ) :
self . receives = True
self . bind_ip = " 127.0.0.1 "
self . bind_port = bindport
def handlerFactory ( callback ) :
def createHandler ( * args , * * keys ) :
return LocalInterfaceHandler ( callback , * args , * * keys )
return createHandler
self . owner = owner
2020-05-13 12:33:10 -06:00
self . is_local_shared_instance = True
2020-05-13 08:01:27 -06:00
address = ( self . bind_ip , self . bind_port )
2021-09-24 08:42:31 -06:00
2020-05-13 08:01:27 -06:00
self . server = ThreadingTCPServer ( address , handlerFactory ( self . incoming_connection ) )
thread = threading . Thread ( target = self . server . serve_forever )
2022-09-30 11:02:25 -06:00
thread . daemon = True
2020-05-13 08:01:27 -06:00
thread . start ( )
2022-05-14 10:09:38 -06:00
self . announce_rate_target = None
self . announce_rate_grace = None
self . announce_rate_penalty = None
2022-04-17 11:07:32 -06:00
self . bitrate = 1000 * 1000 * 1000
2021-09-24 12:10:04 -06:00
self . online = True
2020-05-13 08:01:27 -06:00
def incoming_connection ( self , handler ) :
interface_name = str ( str ( handler . client_address [ 1 ] ) )
spawned_interface = LocalClientInterface ( self . owner , name = interface_name , connected_socket = handler . request )
spawned_interface . OUT = self . OUT
spawned_interface . IN = self . IN
spawned_interface . target_ip = handler . client_address [ 0 ]
spawned_interface . target_port = str ( handler . client_address [ 1 ] )
spawned_interface . parent_interface = self
2022-04-27 05:20:46 -06:00
spawned_interface . bitrate = self . bitrate
2023-11-02 05:44:57 -06:00
if hasattr ( self , " _force_bitrate " ) :
spawned_interface . _force_bitrate = self . _force_bitrate
2023-09-30 16:16:32 -06:00
# RNS.log("Accepting new connection to shared instance: "+str(spawned_interface), RNS.LOG_EXTREME)
2020-05-13 08:01:27 -06:00
RNS . Transport . interfaces . append ( spawned_interface )
2020-05-13 12:33:10 -06:00
RNS . Transport . local_client_interfaces . append ( spawned_interface )
2021-09-24 12:10:04 -06:00
self . clients + = 1
2020-05-13 08:01:27 -06:00
spawned_interface . read_loop ( )
def processOutgoing ( self , data ) :
pass
2023-09-30 11:11:10 -06:00
def received_announce ( self , from_spawned = False ) :
if from_spawned : self . ia_freq_deque . append ( time . time ( ) )
def sent_announce ( self , from_spawned = False ) :
if from_spawned : self . oa_freq_deque . append ( time . time ( ) )
2020-05-13 08:01:27 -06:00
def __str__ ( self ) :
2021-09-24 12:10:04 -06:00
return " Shared Instance[ " + str ( self . bind_port ) + " ] "
2020-05-13 08:01:27 -06:00
class LocalInterfaceHandler ( socketserver . BaseRequestHandler ) :
def __init__ ( self , callback , * args , * * keys ) :
self . callback = callback
socketserver . BaseRequestHandler . __init__ ( self , * args , * * keys )
def handle ( self ) :
self . callback ( handler = self )