2022-04-01 11:18:18 -04:00
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
#
# 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-11 10:09:00 -04:00
from . Interface import Interface
import socketserver
import threading
2021-09-23 10:07:57 -04:00
import platform
2020-05-11 10:09:00 -04:00
import socket
import time
import sys
import os
import RNS
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
2021-12-06 07:07:12 -05:00
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
2020-05-11 10:09:00 -04:00
class ThreadingTCPServer ( socketserver . ThreadingMixIn , socketserver . TCPServer ) :
pass
2024-11-20 10:44:39 -05:00
class ThreadingTCP6Server ( socketserver . ThreadingMixIn , socketserver . TCPServer ) :
address_family = socket . AF_INET6
2020-05-11 10:09:00 -04:00
class TCPClientInterface ( Interface ) :
2022-04-18 12:09:31 -04:00
BITRATE_GUESS = 10 * 1000 * 1000
2021-09-18 16:49:04 -04:00
RECONNECT_WAIT = 5
RECONNECT_MAX_TRIES = None
2020-05-11 10:09:00 -04:00
2021-09-23 10:07:57 -04:00
# TCP socket options
2022-06-12 05:50:09 -04:00
TCP_USER_TIMEOUT = 24
2021-09-23 10:07:57 -04:00
TCP_PROBE_AFTER = 5
2022-06-12 05:50:09 -04:00
TCP_PROBE_INTERVAL = 2
TCP_PROBES = 12
2021-09-23 10:07:57 -04:00
2022-09-15 09:35:28 -04:00
INITIAL_CONNECT_TIMEOUT = 5
SYNCHRONOUS_START = True
2022-06-12 05:50:09 -04:00
I2P_USER_TIMEOUT = 45
2022-01-31 17:31:29 -05:00
I2P_PROBE_AFTER = 10
2022-06-12 05:50:09 -04:00
I2P_PROBE_INTERVAL = 9
I2P_PROBES = 5
2022-01-31 17:31:29 -05:00
2022-09-15 09:35:28 -04:00
def __init__ ( self , owner , name , target_ip = None , target_port = None , connected_socket = None , max_reconnect_tries = None , kiss_framing = False , i2p_tunneled = False , connect_timeout = None ) :
2023-09-30 13:11:10 -04:00
super ( ) . __init__ ( )
2021-09-24 14:10:04 -04:00
2022-05-29 09:43:50 -04:00
self . HW_MTU = 1064
2020-05-13 07:08:48 -04:00
self . IN = True
self . OUT = False
self . socket = None
2020-05-11 10:09:00 -04:00
self . parent_interface = None
2020-05-13 07:08:48 -04:00
self . name = name
2021-09-18 16:49:04 -04:00
self . initiator = False
2021-09-23 10:07:57 -04:00
self . reconnecting = False
self . never_connected = True
2021-09-24 05:20:10 -04:00
self . owner = owner
self . writing = False
self . online = False
2021-09-24 10:42:31 -04:00
self . detached = False
2021-12-06 07:07:12 -05:00
self . kiss_framing = kiss_framing
2022-01-31 17:31:29 -05:00
self . i2p_tunneled = i2p_tunneled
2022-02-25 16:10:55 -05:00
self . mode = RNS . Interfaces . Interface . Interface . MODE_FULL
2022-04-18 12:09:31 -04:00
self . bitrate = TCPClientInterface . BITRATE_GUESS
2022-02-25 16:10:55 -05:00
2021-09-18 16:49:04 -04:00
if max_reconnect_tries == None :
self . max_reconnect_tries = TCPClientInterface . RECONNECT_MAX_TRIES
else :
self . max_reconnect_tries = max_reconnect_tries
2020-05-11 15:52:20 -04:00
2020-05-11 10:09:00 -04:00
if connected_socket != None :
2020-05-13 07:08:48 -04:00
self . receives = True
self . target_ip = None
2020-05-11 10:09:00 -04:00
self . target_port = None
2020-05-13 07:08:48 -04:00
self . socket = connected_socket
2020-05-11 10:09:00 -04:00
2021-09-23 10:07:57 -04:00
if platform . system ( ) == " Linux " :
self . set_timeouts_linux ( )
elif platform . system ( ) == " Darwin " :
self . set_timeouts_osx ( )
2023-11-05 16:57:03 -05:00
self . socket . setsockopt ( socket . IPPROTO_TCP , socket . TCP_NODELAY , 1 )
2020-05-11 10:09:00 -04:00
elif target_ip != None and target_port != None :
2020-05-13 07:08:48 -04:00
self . receives = True
self . target_ip = target_ip
2020-05-11 10:09:00 -04:00
self . target_port = target_port
2021-09-24 05:20:10 -04:00
self . initiator = True
2022-09-15 09:35:28 -04:00
if connect_timeout != None :
self . connect_timeout = connect_timeout
else :
self . connect_timeout = TCPClientInterface . INITIAL_CONNECT_TIMEOUT
2021-09-24 05:20:10 -04:00
2022-09-15 09:35:28 -04:00
if TCPClientInterface . SYNCHRONOUS_START :
self . initial_connect ( )
2021-09-24 05:20:10 -04:00
else :
2022-09-15 09:35:28 -04:00
thread = threading . Thread ( target = self . initial_connect )
2022-09-30 13:02:25 -04:00
thread . daemon = True
2021-09-24 05:20:10 -04:00
thread . start ( )
2022-09-15 09:35:28 -04:00
def initial_connect ( self ) :
if not self . connect ( initial = True ) :
thread = threading . Thread ( target = self . reconnect )
2022-09-30 13:02:25 -04:00
thread . daemon = True
2022-09-15 09:35:28 -04:00
thread . start ( )
else :
thread = threading . Thread ( target = self . read_loop )
2022-09-30 13:02:25 -04:00
thread . daemon = True
2022-09-15 09:35:28 -04:00
thread . start ( )
if not self . kiss_framing :
self . wants_tunnel = True
2020-05-11 10:09:00 -04:00
2021-09-23 10:07:57 -04:00
def set_timeouts_linux ( self ) :
2022-01-31 17:31:29 -05:00
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 ) )
2022-06-12 05:50:09 -04:00
2022-01-31 17:31:29 -05:00
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 ) )
2021-09-23 10:07:57 -04:00
def set_timeouts_osx ( self ) :
if hasattr ( socket , " TCP_KEEPALIVE " ) :
TCP_KEEPIDLE = socket . TCP_KEEPALIVE
else :
TCP_KEEPIDLE = 0x10
2022-01-20 20:34:55 -05:00
self . socket . setsockopt ( socket . SOL_SOCKET , socket . SO_KEEPALIVE , 1 )
2022-01-31 17:31:29 -05:00
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 ) )
2021-09-24 10:09:07 -04: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 )
2021-09-24 10:42:31 -04:00
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 ) )
2021-09-24 10:09:07 -04:00
self . socket = None
2021-09-24 05:20:10 -04:00
def connect ( self , initial = False ) :
try :
2022-09-15 09:35:28 -04:00
if initial :
RNS . log ( " Establishing TCP connection for " + str ( self ) + " ... " , RNS . LOG_DEBUG )
2024-11-20 10:44:39 -05:00
address_info = socket . getaddrinfo ( self . target_ip , self . target_port , proto = socket . IPPROTO_TCP ) [ 0 ]
address_family = address_info [ 0 ]
2024-11-20 11:10:08 -05:00
target_address = address_info [ 4 ]
2024-11-20 10:44:39 -05:00
self . socket = socket . socket ( address_family , socket . SOCK_STREAM )
2022-09-15 09:35:28 -04:00
self . socket . settimeout ( TCPClientInterface . INITIAL_CONNECT_TIMEOUT )
2023-11-05 16:57:03 -05:00
self . socket . setsockopt ( socket . IPPROTO_TCP , socket . TCP_NODELAY , 1 )
2024-11-20 11:10:08 -05:00
self . socket . connect ( target_address )
2022-09-15 09:35:28 -04:00
self . socket . settimeout ( None )
2021-09-24 05:20:10 -04:00
self . online = True
2022-09-15 09:35:28 -04:00
if initial :
RNS . log ( " TCP connection for " + str ( self ) + " established " , RNS . LOG_DEBUG )
2021-09-24 05:20:10 -04:00
except Exception as e :
if initial :
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 ( TCPClientInterface . RECONNECT_WAIT ) + " seconds. " , RNS . LOG_ERROR )
return False
else :
raise e
2021-09-23 10:07:57 -04:00
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
2021-09-24 05:20:10 -04:00
return True
2021-09-23 10:07:57 -04:00
2021-09-18 16:49:04 -04:00
def reconnect ( self ) :
if self . initiator :
2021-09-23 10:07:57 -04:00
if not self . reconnecting :
self . reconnecting = True
attempts = 0
while not self . online :
time . sleep ( TCPClientInterface . RECONNECT_WAIT )
attempts + = 1
2021-09-18 16:49:04 -04:00
2021-09-23 10:07:57 -04:00
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
2021-09-18 16:49:04 -04:00
2021-09-23 10:07:57 -04:00
try :
self . connect ( )
2021-09-18 16:49:04 -04:00
2021-09-23 10:07:57 -04:00
except Exception as e :
RNS . log ( " Connection attempt for " + str ( self ) + " failed: " + str ( e ) , RNS . LOG_DEBUG )
2021-09-18 16:49:04 -04:00
2021-09-23 10:07:57 -04:00
if not self . never_connected :
2022-06-12 12:55:06 -04:00
RNS . log ( " Reconnected socket for " + str ( self ) + " . " , RNS . LOG_INFO )
2021-09-18 16:49:04 -04:00
2021-09-23 10:07:57 -04:00
self . reconnecting = False
thread = threading . Thread ( target = self . read_loop )
2022-09-30 13:02:25 -04:00
thread . daemon = True
2021-09-23 10:07:57 -04:00
thread . start ( )
2021-12-06 07:07:12 -05:00
if not self . kiss_framing :
RNS . Transport . synthesize_tunnel ( self )
2021-09-18 16:49:04 -04:00
else :
RNS . log ( " Attempt to reconnect on a non-initiator TCP interface. This should not happen. " , RNS . LOG_ERROR )
raise IOError ( " Attempt to reconnect on a non-initiator TCP interface " )
2020-05-11 10:09:00 -04:00
def processIncoming ( self , data ) :
2021-09-24 14:10:04 -04:00
self . rxb + = len ( data )
if hasattr ( self , " parent_interface " ) and self . parent_interface != None :
self . parent_interface . rxb + = len ( data )
2020-05-11 10:09:00 -04:00
self . owner . inbound ( data , self )
def processOutgoing ( self , data ) :
if self . online :
2022-06-09 11:14:43 -04:00
# while self.writing:
# time.sleep(0.01)
2020-05-13 07:08:48 -04:00
2020-05-11 10:09:00 -04:00
try :
2020-05-13 07:08:48 -04:00
self . writing = True
2021-12-06 07:07:12 -05:00
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 ] )
2020-05-11 10:09:00 -04:00
self . socket . sendall ( data )
2020-05-13 07:08:48 -04:00
self . writing = False
2021-09-24 14:10:04 -04:00
self . txb + = len ( data )
if hasattr ( self , " parent_interface " ) and self . parent_interface != None :
self . parent_interface . txb + = len ( data )
2020-05-11 10:09:00 -04: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 " "
2021-12-06 07:07:12 -05:00
command = KISS . CMD_UNKNOWN
2020-05-11 10:09:00 -04:00
while True :
2020-05-13 07:08:48 -04:00
data_in = self . socket . recv ( 4096 )
2020-05-11 10:09:00 -04:00
if len ( data_in ) > 0 :
2020-05-12 02:50:51 -04:00
pointer = 0
while pointer < len ( data_in ) :
byte = data_in [ pointer ]
pointer + = 1
2021-12-06 07:07:12 -05:00
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 " "
2022-05-29 09:43:50 -04:00
elif ( in_frame and len ( data_buffer ) < self . HW_MTU ) :
2021-12-06 07:07:12 -05:00
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 " "
2022-05-29 09:43:50 -04:00
elif ( in_frame and len ( data_buffer ) < self . HW_MTU ) :
2021-12-06 07:07:12 -05: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 ] )
2020-05-11 10:09:00 -04:00
else :
2021-09-18 16:49:04 -04:00
self . online = False
2021-09-24 10:42:31 -04:00
if self . initiator and not self . detached :
2022-06-12 12:55:06 -04:00
RNS . log ( " The socket for " + str ( self ) + " was closed, attempting to reconnect... " , RNS . LOG_WARNING )
2021-09-18 16:49:04 -04:00
self . reconnect ( )
2021-09-23 10:07:57 -04:00
else :
2022-06-12 12:55:06 -04:00
RNS . log ( " The socket for remote client " + str ( self ) + " was closed. " , RNS . LOG_VERBOSE )
2021-09-23 10:07:57 -04:00
self . teardown ( )
2021-09-18 16:49:04 -04:00
2020-05-11 10:09:00 -04:00
break
except Exception as e :
self . online = False
2021-09-23 10:07:57 -04:00
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 ( )
2020-05-11 10:09:00 -04:00
def teardown ( self ) :
2021-09-24 14:10:04 -04:00
if self . initiator and not self . detached :
2021-09-23 10:07:57 -04:00
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 )
2020-05-11 10:09:00 -04:00
self . online = False
self . OUT = False
self . IN = False
2021-09-23 10:07:57 -04:00
2021-09-24 14:10:04 -04:00
if hasattr ( self , " parent_interface " ) and self . parent_interface != None :
self . parent_interface . clients - = 1
2020-05-12 10:45:51 -04:00
if self in RNS . Transport . interfaces :
2021-10-08 11:06:00 -04:00
if not self . initiator :
RNS . Transport . interfaces . remove ( self )
2020-05-11 10:09:00 -04:00
def __str__ ( self ) :
2024-11-20 13:24:06 -05:00
if " : " in self . target_ip :
ip_str = f " [ { self . target_ip } ] "
else :
ip_str = f " { self . target_ip } "
return " TCPInterface[ " + str ( self . name ) + " / " + ip_str + " : " + str ( self . target_port ) + " ] "
2020-05-11 10:09:00 -04:00
class TCPServerInterface ( Interface ) :
2022-04-18 12:09:31 -04:00
BITRATE_GUESS = 10 * 1000 * 1000
2024-11-20 14:27:01 -05:00
DEFAULT_IFAC_SIZE = 16
2022-04-18 12:09:31 -04:00
2021-08-19 14:13:53 -04:00
@staticmethod
2024-11-20 10:53:14 -05:00
def get_address_for_if ( name , bind_port , prefer_ipv6 = False ) :
2023-05-04 17:19:43 -04:00
import RNS . vendor . ifaddr . niwrapper as netinfo
ifaddr = netinfo . ifaddresses ( name )
2024-11-20 10:44:39 -05:00
if len ( ifaddr ) < 1 :
raise SystemError ( f " No addresses available on specified kernel interface \" { name } \" for TCPServerInterface to bind to " )
2024-11-20 13:16:15 -05:00
if ( prefer_ipv6 or not netinfo . AF_INET in ifaddr ) and netinfo . AF_INET6 in ifaddr :
2024-11-20 10:44:39 -05:00
bind_ip = ifaddr [ netinfo . AF_INET6 ] [ 0 ] [ " addr " ]
2024-11-20 12:02:50 -05:00
if bind_ip . lower ( ) . startswith ( " fe80:: " ) :
# We'll need to add the interface as scope for link-local addresses
return TCPServerInterface . get_address_for_host ( f " { bind_ip } % { name } " , bind_port )
else :
return TCPServerInterface . get_address_for_host ( bind_ip , bind_port )
2024-11-20 10:44:39 -05:00
elif netinfo . AF_INET in ifaddr :
bind_ip = ifaddr [ netinfo . AF_INET ] [ 0 ] [ " addr " ]
return ( bind_ip , bind_port )
else :
raise SystemError ( f " No addresses available on specified kernel interface \" { name } \" for TCPServerInterface to bind to " )
2021-08-19 14:13:53 -04:00
2021-12-01 05:39:40 -05:00
@staticmethod
2024-11-20 10:44:39 -05:00
def get_address_for_host ( name , bind_port ) :
address_info = socket . getaddrinfo ( name , bind_port , proto = socket . IPPROTO_TCP ) [ 0 ]
if address_info [ 0 ] == socket . AF_INET6 :
return ( name , bind_port , address_info [ 4 ] [ 2 ] , address_info [ 4 ] [ 3 ] )
elif address_info [ 0 ] == socket . AF_INET :
return ( name , bind_port )
else :
raise SystemError ( f " No suitable kernel interface available for address \" { name } \" for TCPServerInterface to bind to " )
2020-05-11 10:09:00 -04:00
2024-11-20 14:27:01 -05:00
# def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False, prefer_ipv6=False):
def __init__ ( self , owner , configuration ) :
c = configuration
name = c [ " name " ]
device = c [ " device " ] if " device " in c else None
port = int ( c [ " port " ] ) if " port " in c else None
bindip = c [ " listen_ip " ] if " listen_ip " in c else None
bindport = 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
prefer_ipv6 = c . as_bool ( " prefer_ipv6 " ) if " prefer_ipv6 " in c else False
if port != None :
bindport = port
2023-09-30 13:11:10 -04:00
super ( ) . __init__ ( )
2022-05-29 09:43:50 -04:00
self . HW_MTU = 1064
2021-09-24 14:10:04 -04:00
self . online = False
self . clients = 0
2020-05-11 10:09:00 -04:00
self . IN = True
self . OUT = False
self . name = name
2022-09-06 06:23:52 -04:00
self . detached = False
2020-05-11 10:09:00 -04:00
2022-01-31 17:31:29 -05:00
self . i2p_tunneled = i2p_tunneled
2022-02-25 16:10:55 -05:00
self . mode = RNS . Interfaces . Interface . Interface . MODE_FULL
2022-01-31 17:31:29 -05:00
2024-11-20 10:44:39 -05:00
if bindport == None :
raise SystemError ( f " No TCP port configured for interface \" { name } \" " )
else :
self . bind_port = bindport
bind_address = None
2021-08-19 14:13:53 -04:00
if device != None :
2024-11-20 10:53:14 -05:00
bind_address = TCPServerInterface . get_address_for_if ( device , self . bind_port , prefer_ipv6 )
2024-11-20 10:44:39 -05:00
else :
if bindip == None :
raise SystemError ( f " No TCP bind IP configured for interface \" { name } \" " )
bind_address = TCPServerInterface . get_address_for_host ( bindip , self . bind_port )
2021-08-19 14:13:53 -04:00
2024-11-20 10:44:39 -05:00
if bind_address != None :
2020-05-11 10:09:00 -04:00
self . receives = True
2024-11-20 10:44:39 -05:00
self . bind_ip = bind_address [ 0 ]
2020-05-11 10:09:00 -04:00
def handlerFactory ( callback ) :
def createHandler ( * args , * * keys ) :
return TCPInterfaceHandler ( callback , * args , * * keys )
return createHandler
self . owner = owner
2021-09-24 10:42:31 -04:00
2024-11-20 10:44:39 -05:00
if len ( bind_address ) == 4 :
try :
ThreadingTCP6Server . allow_reuse_address = True
self . server = ThreadingTCP6Server ( bind_address , handlerFactory ( self . incoming_connection ) )
except Exception as e :
RNS . log ( f " Error while binding IPv6 socket for interface, the contained exception was: { e } " , RNS . LOG_ERROR )
raise SystemError ( " Could not bind IPv6 socket for interface. Please check the specified \" listen_ip \" configuration option " )
else :
ThreadingTCPServer . allow_reuse_address = True
self . server = ThreadingTCPServer ( bind_address , handlerFactory ( self . incoming_connection ) )
2020-05-11 10:09:00 -04:00
2022-04-18 12:09:31 -04:00
self . bitrate = TCPServerInterface . BITRATE_GUESS
2020-05-11 10:09:00 -04:00
thread = threading . Thread ( target = self . server . serve_forever )
2022-09-30 13:02:25 -04:00
thread . daemon = True
2020-05-11 10:09:00 -04:00
thread . start ( )
2021-09-24 14:10:04 -04:00
self . online = True
2024-11-20 10:44:39 -05:00
else :
raise SystemError ( " Insufficient parameters to create TCP listener " )
2020-05-11 10:09:00 -04:00
def incoming_connection ( self , handler ) :
RNS . log ( " Accepting incoming TCP connection " , RNS . LOG_VERBOSE )
interface_name = " Client on " + self . name
2022-01-31 17:31:29 -05:00
spawned_interface = TCPClientInterface ( self . owner , interface_name , target_ip = None , target_port = None , connected_socket = handler . request , i2p_tunneled = self . i2p_tunneled )
2020-05-11 10:09:00 -04:00
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-18 12:09:31 -04:00
spawned_interface . bitrate = self . bitrate
2022-11-03 09:16:00 -04:00
2022-04-27 07:20:46 -04:00
spawned_interface . ifac_size = self . ifac_size
spawned_interface . ifac_netname = self . ifac_netname
spawned_interface . ifac_netkey = self . ifac_netkey
2022-11-03 09:16:00 -04:00
if spawned_interface . ifac_netname != None or spawned_interface . ifac_netkey != None :
ifac_origin = b " "
if spawned_interface . ifac_netname != None :
ifac_origin + = RNS . Identity . full_hash ( spawned_interface . ifac_netname . encode ( " utf-8 " ) )
if spawned_interface . ifac_netkey != None :
ifac_origin + = RNS . Identity . full_hash ( spawned_interface . ifac_netkey . encode ( " utf-8 " ) )
ifac_origin_hash = RNS . Identity . full_hash ( ifac_origin )
spawned_interface . ifac_key = RNS . Cryptography . hkdf (
length = 64 ,
derive_from = ifac_origin_hash ,
salt = RNS . Reticulum . IFAC_SALT ,
context = None
)
spawned_interface . ifac_identity = RNS . Identity . from_bytes ( spawned_interface . ifac_key )
spawned_interface . ifac_signature = spawned_interface . ifac_identity . sign ( RNS . Identity . full_hash ( spawned_interface . ifac_key ) )
2022-05-14 12:09:38 -04:00
spawned_interface . announce_rate_target = self . announce_rate_target
spawned_interface . announce_rate_grace = self . announce_rate_grace
spawned_interface . announce_rate_penalty = self . announce_rate_penalty
2022-05-22 18:06:26 -04:00
spawned_interface . mode = self . mode
2022-05-29 09:43:50 -04:00
spawned_interface . HW_MTU = self . HW_MTU
2021-09-24 08:11:04 -04:00
spawned_interface . online = True
2020-05-11 10:09:00 -04:00
RNS . log ( " Spawned new TCPClient Interface: " + str ( spawned_interface ) , RNS . LOG_VERBOSE )
RNS . Transport . interfaces . append ( spawned_interface )
2021-09-24 14:10:04 -04:00
self . clients + = 1
2020-05-11 10:09:00 -04:00
spawned_interface . read_loop ( )
2023-09-30 13:11:10 -04: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-11 10:09:00 -04:00
def processOutgoing ( self , data ) :
pass
2022-09-06 06:23:52 -04:00
def detach ( self ) :
if self . server != None :
if hasattr ( self . server , " shutdown " ) :
if callable ( self . server . shutdown ) :
try :
RNS . log ( " Detaching " + str ( self ) , RNS . LOG_DEBUG )
self . server . shutdown ( )
self . detached = True
self . server = None
except Exception as e :
RNS . log ( " Error while shutting down server for " + str ( self ) + " : " + str ( e ) )
2020-05-11 10:09:00 -04:00
def __str__ ( self ) :
2024-11-20 13:24:06 -05:00
if " : " in self . bind_ip :
ip_str = f " [ { self . bind_ip } ] "
else :
ip_str = f " { self . bind_ip } "
return " TCPServerInterface[ " + self . name + " / " + ip_str + " : " + str ( self . bind_port ) + " ] "
2020-05-11 10:09:00 -04:00
2022-09-06 11:42:13 -04:00
2020-05-11 10:09:00 -04:00
class TCPInterfaceHandler ( socketserver . BaseRequestHandler ) :
def __init__ ( self , callback , * args , * * keys ) :
self . callback = callback
socketserver . BaseRequestHandler . __init__ ( self , * args , * * keys )
def handle ( self ) :
2022-01-20 20:34:55 -05:00
self . callback ( handler = self )