2022-04-01 17:18:18 +02: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.
2022-02-23 17:40:31 +01:00
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
2022-02-23 22:53:16 +01:00
# 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.
2022-02-23 21:19:43 +01:00
class I2PController :
def __init__ ( self , rns_storagepath ) :
import RNS . vendor . i2plib as i2plib
import RNS . vendor . i2plib . utils
2022-02-24 01:30:10 +01:00
self . client_tunnels = { }
self . server_tunnels = { }
2022-05-28 02:24:01 +02:00
self . i2plib_tunnels = { }
2022-02-23 21:19:43 +01:00
self . loop = None
self . i2plib = i2plib
self . utils = i2plib . utils
self . sam_address = i2plib . get_sam_address ( )
2022-05-26 09:54:56 +02:00
self . ready = False
2022-02-23 21:19:43 +01:00
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 ( )
2022-05-25 23:11:01 +02:00
2022-05-26 09:54:56 +02:00
time . sleep ( 0.10 )
if self . loop == None :
RNS . log ( " Could not get event loop for " + str ( self ) + " , waiting for event loop to appear " , RNS . LOG_VERBOSE )
2022-05-25 23:11:01 +02:00
while self . loop == None :
self . loop = asyncio . get_event_loop ( )
sleep ( 0.25 )
2022-02-23 21:19:43 +01:00
try :
2022-05-26 09:54:56 +02:00
self . ready = True
2022-02-23 21:19:43 +01:00
self . loop . run_forever ( )
except Exception as e :
2022-05-26 09:54:56 +02:00
self . ready = False
2022-02-23 21:19:43 +01:00
RNS . log ( " Exception on event loop for " + str ( self ) + " : " + str ( e ) , RNS . LOG_ERROR )
finally :
self . loop . close ( )
2022-02-24 01:30:10 +01:00
2022-02-23 21:19:43 +01:00
def stop ( self ) :
2022-06-11 11:27:01 +02:00
for i2ptunnel in self . i2plib_tunnels :
if hasattr ( i2ptunnel , " stop " ) and callable ( i2ptunnel . stop ) :
i2ptunnel . stop ( )
if hasattr ( asyncio . Task , " all_tasks " ) and callable ( asyncio . Task . all_tasks ) :
for task in asyncio . Task . all_tasks ( loop = self . loop ) :
task . cancel ( )
2022-02-23 21:19:43 +01:00
2022-06-11 13:52:56 +02:00
time . sleep ( 0.2 )
2022-02-23 21:19:43 +01:00
self . loop . stop ( )
2022-02-24 01:30:10 +01:00
2022-02-23 21:19:43 +01:00
def get_free_port ( self ) :
return self . i2plib . utils . get_free_port ( )
2022-02-24 01:30:10 +01:00
2022-06-11 13:52:56 +02:00
def stop_tunnel ( self , i2ptunnel ) :
if hasattr ( i2ptunnel , " stop " ) and callable ( i2ptunnel . stop ) :
i2ptunnel . stop ( )
2022-02-23 21:19:43 +01:00
def client_tunnel ( self , owner , i2p_destination ) :
2022-02-24 01:30:10 +01:00
self . client_tunnels [ i2p_destination ] = False
2022-05-28 02:24:01 +02:00
self . i2plib_tunnels [ i2p_destination ] = None
2022-05-25 20:18:06 +02:00
2022-02-24 01:30:10 +01:00
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 )
2022-05-28 02:24:01 +02:00
self . i2plib_tunnels [ i2p_destination ] = tunnel
2022-02-24 01:30:10 +01:00
await tunnel . run ( )
2022-02-23 21:19:43 +01:00
2022-05-25 20:18:06 +02:00
self . loop . ext_owner = self
result = asyncio . run_coroutine_threadsafe ( tunnel_up ( ) , self . loop ) . result ( )
2022-05-28 02:24:01 +02:00
if not i2p_destination in self . i2plib_tunnels :
2022-05-25 20:18:06 +02:00
raise IOError ( " No tunnel control instance was created " )
2022-02-23 21:19:43 +01:00
2022-05-25 20:18:06 +02:00
else :
2022-05-28 02:24:01 +02:00
tn = self . i2plib_tunnels [ i2p_destination ]
2022-05-25 20:18:06 +02:00
if tn != None and hasattr ( tn , " status " ) :
RNS . log ( " Waiting for status from I2P control process " , RNS . LOG_EXTREME )
while not tn . status [ " setup_ran " ] :
time . sleep ( 0.1 )
RNS . log ( " Got status from I2P control process " , RNS . LOG_EXTREME )
if tn . status [ " setup_failed " ] :
2022-06-11 13:52:56 +02:00
self . stop_tunnel ( tn )
2022-05-25 20:18:06 +02:00
raise tn . status [ " exception " ]
else :
self . client_tunnels [ i2p_destination ] = True
owner . awaiting_i2p_tunnel = False
2022-05-28 02:24:01 +02:00
if owner . socket != None :
if hasattr ( owner . socket , " close " ) :
if callable ( owner . socket . close ) :
try :
owner . socket . shutdown ( socket . SHUT_RDWR )
except Exception as e :
RNS . log ( " Error while shutting down socket for " + str ( owner ) + " : " + str ( e ) )
try :
owner . socket . close ( )
except Exception as e :
RNS . log ( " Error while closing socket for " + str ( owner ) + " : " + str ( e ) )
RNS . log ( str ( owner ) + " tunnel setup complete " , RNS . LOG_VERBOSE )
2022-02-24 01:30:10 +01:00
2022-05-25 20:18:06 +02:00
else :
raise IOError ( " Got no status response from SAM API " )
except ConnectionRefusedError as e :
raise e
except ConnectionAbortedError as e :
raise e
2022-02-24 01:30:10 +01:00
except Exception as e :
2022-05-25 20:18:06 +02:00
RNS . log ( " Unexpected error type from I2P SAM: " + str ( e ) , RNS . LOG_ERROR )
raise e
2022-02-24 01:30:10 +01:00
2022-05-28 02:24:01 +02:00
else :
i2ptunnel = self . i2plib_tunnels [ i2p_destination ]
if hasattr ( i2ptunnel , " status " ) :
i2p_exception = i2ptunnel . status [ " exception " ]
if i2ptunnel . status [ " setup_ran " ] == False :
RNS . log ( str ( self ) + " I2P tunnel setup did not complete " , RNS . LOG_ERROR )
2022-06-11 11:27:01 +02:00
2022-06-11 13:52:56 +02:00
self . stop_tunnel ( i2ptunnel )
2022-05-28 02:24:01 +02:00
return False
elif i2p_exception != None :
2022-06-11 13:52:56 +02:00
RNS . log ( " An error ocurred while setting up I2P tunnel to " + str ( i2p_destination ) , RNS . LOG_ERROR )
if isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . CantReachPeer ) :
RNS . log ( " The I2P daemon can ' t reach peer " + str ( i2p_destination ) , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . DuplicatedDest ) :
RNS . log ( " The I2P daemon reported that the destination is already in use " , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . DuplicatedId ) :
RNS . log ( " The I2P daemon reported that the ID is arleady in use " , RNS . LOG_ERROR )
2022-06-11 11:27:01 +02:00
2022-06-11 13:52:56 +02:00
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . InvalidId ) :
RNS . log ( " The I2P daemon reported that the stream session ID doesn ' t exist " , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . InvalidKey ) :
RNS . log ( " The I2P daemon reported that the key for " + str ( i2p_destination ) + " is invalid " , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . KeyNotFound ) :
RNS . log ( " The I2P daemon could not find the key for " + str ( i2p_destination ) , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . PeerNotFound ) :
RNS . log ( " The I2P daemon mould not find the peer " + str ( i2p_destination ) , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . I2PError ) :
RNS . log ( " The I2P daemon experienced an unspecified error " , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . Timeout ) :
RNS . log ( " I2P daemon timed out while setting up client tunnel to " + str ( i2p_destination ) , RNS . LOG_ERROR )
RNS . log ( " Resetting I2P tunnel and retrying later " , RNS . LOG_ERROR )
self . stop_tunnel ( i2ptunnel )
2022-05-28 02:24:01 +02:00
return False
elif i2ptunnel . status [ " setup_failed " ] == True :
RNS . log ( str ( self ) + " Unspecified I2P tunnel setup error, resetting I2P tunnel " , RNS . LOG_ERROR )
2022-06-11 11:27:01 +02:00
2022-06-11 13:52:56 +02:00
self . stop_tunnel ( i2ptunnel )
2022-05-28 02:24:01 +02:00
return False
else :
RNS . log ( str ( self ) + " Got no status from SAM API, resetting I2P tunnel " , RNS . LOG_ERROR )
2022-06-11 11:27:01 +02:00
2022-06-11 13:52:56 +02:00
self . stop_tunnel ( i2ptunnel )
2022-05-28 02:24:01 +02:00
return False
2022-06-11 13:52:56 +02:00
# Wait for status from I2P control process
2022-02-24 01:30:10 +01:00
time . sleep ( 5 )
2022-02-23 21:19:43 +01:00
def server_tunnel ( self , owner ) :
2022-05-25 21:22:16 +02:00
while RNS . Transport . identity == None :
time . sleep ( 1 )
# Old format
i2p_dest_hash_of = RNS . Identity . full_hash ( RNS . Identity . full_hash ( owner . name . encode ( " utf-8 " ) ) )
i2p_keyfile_of = self . storagepath + " / " + RNS . hexrep ( i2p_dest_hash_of , delimit = False ) + " .i2p "
# New format
i2p_dest_hash_nf = RNS . Identity . full_hash ( RNS . Identity . full_hash ( owner . name . encode ( " utf-8 " ) ) + RNS . Identity . full_hash ( RNS . Transport . identity . hash ) )
i2p_keyfile_nf = self . storagepath + " / " + RNS . hexrep ( i2p_dest_hash_nf , delimit = False ) + " .i2p "
# Use old format if a key is already present
if os . path . isfile ( i2p_keyfile_of ) :
i2p_keyfile = i2p_keyfile_of
else :
i2p_keyfile = i2p_keyfile_nf
2022-02-23 21:19:43 +01:00
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
2022-02-26 21:04:54 +01:00
owner . b32 = i2p_b32
2022-02-23 21:19:43 +01:00
2022-02-24 01:30:10 +01:00
self . server_tunnels [ i2p_b32 ] = False
2022-05-28 02:24:01 +02:00
self . i2plib_tunnels [ i2p_b32 ] = None
2022-02-23 21:19:43 +01:00
2022-05-28 02:24:01 +02:00
while True :
if 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 )
self . i2plib_tunnels [ i2p_b32 ] = tunnel
await tunnel . run ( )
RNS . log ( str ( owner ) + " endpoint setup complete. Now reachable at: " + str ( i2p_dest . base32 ) + " .b32.i2p " , RNS . LOG_VERBOSE )
2022-02-23 21:19:43 +01:00
2022-05-28 02:24:01 +02:00
asyncio . run_coroutine_threadsafe ( tunnel_up ( ) , self . loop ) . result ( )
self . server_tunnels [ i2p_b32 ] = True
2022-02-24 01:30:10 +01:00
2022-05-28 02:24:01 +02:00
except Exception as e :
raise e
else :
i2ptunnel = self . i2plib_tunnels [ i2p_b32 ]
if hasattr ( i2ptunnel , " status " ) :
# TODO: Remove
# RNS.log(str(i2ptunnel.status))
i2p_exception = i2ptunnel . status [ " exception " ]
if i2ptunnel . status [ " setup_ran " ] == False :
RNS . log ( str ( self ) + " I2P tunnel setup did not complete " , RNS . LOG_ERROR )
2022-06-11 11:27:01 +02:00
2022-06-11 13:52:56 +02:00
self . stop_tunnel ( i2ptunnel )
2022-05-28 02:24:01 +02:00
return False
elif i2p_exception != None :
2022-06-11 13:52:56 +02:00
RNS . log ( " An error ocurred while setting up I2P tunnel " , RNS . LOG_ERROR )
if isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . CantReachPeer ) :
RNS . log ( " The I2P daemon can ' t reach peer " + str ( i2p_destination ) , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . DuplicatedDest ) :
RNS . log ( " The I2P daemon reported that the destination is already in use " , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . DuplicatedId ) :
RNS . log ( " The I2P daemon reported that the ID is arleady in use " , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . InvalidId ) :
RNS . log ( " The I2P daemon reported that the stream session ID doesn ' t exist " , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . InvalidKey ) :
RNS . log ( " The I2P daemon reported that the key for " + str ( i2p_destination ) + " is invalid " , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . KeyNotFound ) :
RNS . log ( " The I2P daemon could not find the key for " + str ( i2p_destination ) , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . PeerNotFound ) :
RNS . log ( " The I2P daemon mould not find the peer " + str ( i2p_destination ) , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . I2PError ) :
RNS . log ( " The I2P daemon experienced an unspecified error " , RNS . LOG_ERROR )
elif isinstance ( i2p_exception , RNS . vendor . i2plib . exceptions . Timeout ) :
RNS . log ( " I2P daemon timed out while setting up client tunnel to " + str ( i2p_destination ) , RNS . LOG_ERROR )
RNS . log ( " Resetting I2P tunnel and retrying later " , RNS . LOG_ERROR )
self . stop_tunnel ( i2ptunnel )
2022-05-28 02:24:01 +02:00
return False
elif i2ptunnel . status [ " setup_failed " ] == True :
RNS . log ( str ( self ) + " Unspecified I2P tunnel setup error, resetting I2P tunnel " , RNS . LOG_ERROR )
2022-06-11 11:27:01 +02:00
2022-06-11 13:52:56 +02:00
self . stop_tunnel ( i2ptunnel )
2022-05-28 02:24:01 +02:00
return False
else :
RNS . log ( str ( self ) + " Got no status from SAM API, resetting I2P tunnel " , RNS . LOG_ERROR )
2022-06-11 11:27:01 +02:00
2022-06-11 13:52:56 +02:00
self . stop_tunnel ( i2ptunnel )
2022-05-28 02:24:01 +02:00
return False
2022-02-24 01:30:10 +01:00
time . sleep ( 5 )
2022-02-23 21:19:43 +01:00
def get_loop ( self ) :
return asyncio . get_event_loop ( )
2022-02-23 17:40:31 +01:00
class ThreadingI2PServer ( socketserver . ThreadingMixIn , socketserver . TCPServer ) :
pass
class I2PInterfacePeer ( Interface ) :
2022-02-23 22:15:06 +01:00
RECONNECT_WAIT = 15
2022-02-23 17:40:31 +01:00
RECONNECT_MAX_TRIES = None
# TCP socket options
I2P_USER_TIMEOUT = 40
I2P_PROBE_AFTER = 10
I2P_PROBE_INTERVAL = 5
I2P_PROBES = 6
2022-02-23 22:43:08 +01:00
def __init__ ( self , parent_interface , owner , name , target_i2p_dest = None , connected_socket = None , max_reconnect_tries = None ) :
2022-02-23 17:40:31 +01:00
self . rxb = 0
self . txb = 0
2022-05-29 15:43:50 +02:00
self . HW_MTU = 1064
2022-02-23 17:40:31 +01:00
self . IN = True
self . OUT = False
self . socket = None
2022-02-23 22:43:08 +01:00
self . parent_interface = parent_interface
2022-02-24 01:30:10 +01:00
self . parent_count = True
2022-02-23 17:40:31 +01:00
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
2022-02-23 21:19:43 +01:00
self . i2p_tunnel_ready = False
2022-02-26 21:37:50 +01:00
self . mode = RNS . Interfaces . Interface . Interface . MODE_FULL
2022-04-17 19:35:31 +02:00
self . bitrate = I2PInterface . BITRATE_GUESS
2022-02-23 17:40:31 +01:00
2022-05-23 00:28:06 +02:00
self . announce_rate_target = None
self . announce_rate_grace = None
self . announce_rate_penalty = None
2022-02-23 17:40:31 +01:00
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 "
2022-02-23 21:19:43 +01:00
2022-02-23 22:43:08 +01:00
self . awaiting_i2p_tunnel = True
2022-02-24 01:30:10 +01:00
def tunnel_job ( ) :
2022-05-25 20:18:06 +02:00
while self . awaiting_i2p_tunnel :
try :
2022-05-28 02:24:01 +02:00
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
if not self . parent_interface . i2p . client_tunnel ( self , target_i2p_dest ) :
RNS . log ( str ( self ) + " I2P control process experienced an error, requesting new tunnel... " , RNS . LOG_ERROR )
self . awaiting_i2p_tunnel = True
2022-05-25 20:18:06 +02:00
except Exception as e :
RNS . log ( " Error while while configuring " + str ( self ) + " : " + str ( e ) , RNS . LOG_ERROR )
RNS . log ( " Check that I2P is installed and running, and that SAM is enabled. Retrying tunnel setup later. " , RNS . LOG_ERROR )
time . sleep ( 15 )
2022-02-24 01:30:10 +01:00
thread = threading . Thread ( target = tunnel_job )
thread . setDaemon ( True )
thread . start ( )
def wait_job ( ) :
while self . awaiting_i2p_tunnel :
time . sleep ( 0.25 )
2022-02-23 17:40:31 +01:00
if not self . kiss_framing :
self . wants_tunnel = True
2022-02-24 01:30:10 +01:00
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 ( )
2022-02-23 17:40:31 +01:00
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 ) )
2022-06-11 11:27:01 +02:00
def shutdown_socket ( self , socket ) :
if callable ( socket . close ) :
try :
socket . shutdown ( socket . SHUT_RDWR )
except Exception as e :
RNS . log ( " Error while shutting down socket for " + str ( self ) + " : " + str ( e ) )
try :
socket . close ( )
except Exception as e :
RNS . log ( " Error while closing socket for " + str ( self ) + " : " + str ( e ) )
2022-02-23 17:40:31 +01:00
def detach ( self ) :
2022-06-11 13:52:56 +02:00
RNS . log ( " Detaching " + str ( self ) , RNS . LOG_DEBUG )
2022-02-23 17:40:31 +01:00
if self . socket != None :
if hasattr ( self . socket , " close " ) :
if callable ( self . socket . close ) :
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 :
2022-02-23 22:43:08 +01:00
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 )
2022-02-23 17:40:31 +01:00
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
2022-02-24 01:30:10 +01:00
if not self . kiss_framing and self . wants_tunnel :
RNS . Transport . synthesize_tunnel ( self )
2022-02-23 17:40:31 +01:00
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 :
2022-02-23 22:43:08 +01:00
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 )
2022-02-23 17:40:31 +01:00
if not self . never_connected :
2022-02-23 22:43:08 +01:00
RNS . log ( str ( self ) + " Re-established connection via I2P tunnel " , RNS . LOG_INFO )
2022-02-23 17:40:31 +01:00
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 :
2022-02-23 22:15:06 +01:00
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 " )
2022-02-23 17:40:31 +01:00
def processIncoming ( self , data ) :
self . rxb + = len ( data )
2022-02-24 01:30:10 +01:00
if hasattr ( self , " parent_interface " ) and self . parent_interface != None and self . parent_count :
2022-02-23 17:40:31 +01:00
self . parent_interface . rxb + = len ( data )
self . owner . inbound ( data , self )
def processOutgoing ( self , data ) :
if self . online :
while self . writing :
2022-06-10 17:05:00 +02:00
time . sleep ( 0.001 )
2022-02-23 17:40:31 +01:00
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 )
2022-06-10 17:05:00 +02:00
2022-02-24 01:30:10 +01:00
if hasattr ( self , " parent_interface " ) and self . parent_interface != None and self . parent_count :
2022-02-23 17:40:31 +01:00
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 " "
2022-05-29 15:43:50 +02:00
elif ( in_frame and len ( data_buffer ) < self . HW_MTU ) :
2022-02-23 17:40:31 +01: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 15:43:50 +02:00
elif ( in_frame and len ( data_buffer ) < self . HW_MTU ) :
2022-02-23 17:40:31 +01: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 :
self . online = False
if self . initiator and not self . detached :
2022-02-23 22:43:08 +01:00
RNS . log ( " Socket for " + str ( self ) + " was closed, attempting to reconnect... " , RNS . LOG_WARNING )
2022-02-23 17:40:31 +01:00
self . reconnect ( )
else :
2022-02-23 22:43:08 +01:00
RNS . log ( " Socket for remote client " + str ( self ) + " was closed. " , RNS . LOG_VERBOSE )
2022-02-23 17:40:31 +01:00
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 :
2022-05-25 21:44:49 +02:00
if self . parent_interface . clients > 0 :
self . parent_interface . clients - = 1
2022-02-23 17:40:31 +01:00
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 ) :
2022-04-17 19:35:31 +02:00
BITRATE_GUESS = 256 * 1000
2022-02-23 17:40:31 +01:00
2022-05-25 15:50:54 +02:00
def __init__ ( self , owner , name , rns_storagepath , peers , connectable = False ) :
2022-02-23 17:40:31 +01:00
self . rxb = 0
self . txb = 0
2022-05-29 15:43:50 +02:00
self . HW_MTU = 1064
2022-02-23 17:40:31 +01:00
self . online = False
self . clients = 0
2022-02-23 21:19:43 +01:00
self . owner = owner
2022-02-24 01:30:10 +01:00
self . connectable = connectable
2022-02-23 21:19:43 +01:00
self . i2p_tunneled = True
2022-02-26 21:37:50 +01:00
self . mode = RNS . Interfaces . Interface . Interface . MODE_FULL
2022-02-23 21:19:43 +01:00
2022-02-26 21:04:54 +01:00
self . b32 = None
2022-02-23 21:19:43 +01:00
self . i2p = I2PController ( rns_storagepath )
2022-02-26 21:04:54 +01:00
2022-02-23 17:40:31 +01:00
self . IN = True
self . OUT = False
self . name = name
self . receives = True
self . bind_ip = " 127.0.0.1 "
2022-02-23 21:19:43 +01:00
self . bind_port = self . i2p . get_free_port ( )
self . address = ( self . bind_ip , self . bind_port )
2022-04-17 19:35:31 +02:00
self . bitrate = I2PInterface . BITRATE_GUESS
2022-02-23 17:40:31 +01:00
2022-05-25 21:22:16 +02:00
self . online = False
2022-02-23 21:19:43 +01:00
i2p_thread = threading . Thread ( target = self . i2p . start )
i2p_thread . setDaemon ( True )
i2p_thread . start ( )
2022-02-23 17:40:31 +01:00
2022-05-26 09:54:56 +02:00
i2p_notready_warning = False
time . sleep ( 0.25 )
if not self . i2p . ready :
RNS . log ( " I2P controller did not become available in time, waiting for controller " , RNS . LOG_VERBOSE )
i2p_notready_warning = True
while not self . i2p . ready :
time . sleep ( 0.25 )
if i2p_notready_warning == True :
RNS . log ( " I2P controller ready, continuing setup " , RNS . LOG_VERBOSE )
2022-02-23 17:40:31 +01:00
def handlerFactory ( callback ) :
def createHandler ( * args , * * keys ) :
return I2PInterfaceHandler ( callback , * args , * * keys )
return createHandler
2022-02-23 21:19:43 +01:00
2022-02-23 17:40:31 +01:00
ThreadingI2PServer . allow_reuse_address = True
2022-02-23 21:19:43 +01:00
self . server = ThreadingI2PServer ( self . address , handlerFactory ( self . incoming_connection ) )
2022-02-23 17:40:31 +01:00
thread = threading . Thread ( target = self . server . serve_forever )
thread . setDaemon ( True )
thread . start ( )
2022-02-24 01:30:10 +01:00
if self . connectable :
def tunnel_job ( ) :
2022-05-28 02:24:01 +02:00
while True :
2022-05-25 21:22:16 +02:00
try :
2022-05-28 02:24:01 +02:00
if not self . i2p . server_tunnel ( self ) :
RNS . log ( str ( self ) + " I2P control process experienced an error, requesting new tunnel... " , RNS . LOG_ERROR )
self . online = False
2022-05-25 21:22:16 +02:00
except Exception as e :
RNS . log ( " Error while while configuring " + str ( self ) + " : " + str ( e ) , RNS . LOG_ERROR )
RNS . log ( " Check that I2P is installed and running, and that SAM is enabled. Retrying tunnel setup later. " , RNS . LOG_ERROR )
time . sleep ( 15 )
2022-02-24 01:30:10 +01:00
thread = threading . Thread ( target = tunnel_job )
thread . setDaemon ( True )
thread . start ( )
2022-02-23 17:40:31 +01:00
if peers != None :
for peer_addr in peers :
2022-05-25 15:50:54 +02:00
interface_name = self . name + " to " + peer_addr
2022-02-23 22:43:08 +01:00
peer_interface = I2PInterfacePeer ( self , self . owner , interface_name , peer_addr )
2022-02-23 22:15:06 +01:00
peer_interface . OUT = True
peer_interface . IN = True
2022-02-23 21:47:30 +01:00
peer_interface . parent_interface = self
2022-02-24 01:30:10 +01:00
peer_interface . parent_count = False
2022-02-23 21:39:29 +01:00
RNS . Transport . interfaces . append ( peer_interface )
2022-02-23 17:40:31 +01:00
def incoming_connection ( self , handler ) :
RNS . log ( " Accepting incoming I2P connection " , RNS . LOG_VERBOSE )
interface_name = " Connected peer on " + self . name
2022-02-23 22:43:08 +01:00
spawned_interface = I2PInterfacePeer ( self , self . owner , interface_name , connected_socket = handler . request )
2022-02-23 22:15:06 +01:00
spawned_interface . OUT = True
spawned_interface . IN = True
2022-02-23 17:40:31 +01:00
spawned_interface . parent_interface = self
spawned_interface . online = True
2022-04-27 13:20:46 +02:00
spawned_interface . bitrate = self . bitrate
spawned_interface . ifac_size = self . ifac_size
spawned_interface . ifac_netname = self . ifac_netname
spawned_interface . ifac_netkey = self . ifac_netkey
2022-05-14 18:09:38 +02: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-23 00:06:26 +02:00
spawned_interface . mode = self . mode
2022-05-29 15:43:50 +02:00
spawned_interface . HW_MTU = self . HW_MTU
2022-02-23 17:40:31 +01:00
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
2022-02-23 21:19:43 +01:00
def detach ( self ) :
2022-06-11 13:52:56 +02:00
RNS . log ( " Detaching " + str ( self ) , RNS . LOG_DEBUG )
2022-02-23 21:19:43 +01:00
self . i2p . stop ( )
2022-02-23 17:40:31 +01:00
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 )