kopia lustrzana https://github.com/markqvist/reticulum
				
				
				
			
		
			
				
	
	
		
			636 wiersze
		
	
	
		
			26 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			636 wiersze
		
	
	
		
			26 KiB
		
	
	
	
		
			Python
		
	
	
# MIT License
 | 
						|
#
 | 
						|
# Copyright (c) 2016-2024 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.
 | 
						|
 | 
						|
from RNS.Interfaces.Interface import Interface
 | 
						|
import socketserver
 | 
						|
import threading
 | 
						|
import platform
 | 
						|
import socket
 | 
						|
import time
 | 
						|
import sys
 | 
						|
import os
 | 
						|
import RNS
 | 
						|
 | 
						|
class TCPInterface():
 | 
						|
    HW_MTU            = 262144
 | 
						|
 | 
						|
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
 | 
						|
 | 
						|
class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
 | 
						|
    pass
 | 
						|
 | 
						|
class ThreadingTCP6Server(socketserver.ThreadingMixIn, socketserver.TCPServer):
 | 
						|
    address_family = socket.AF_INET6
 | 
						|
 | 
						|
class TCPClientInterface(Interface):
 | 
						|
    BITRATE_GUESS = 10*1000*1000
 | 
						|
    DEFAULT_IFAC_SIZE = 16
 | 
						|
    AUTOCONFIGURE_MTU = True
 | 
						|
 | 
						|
    RECONNECT_WAIT = 5
 | 
						|
    RECONNECT_MAX_TRIES = None
 | 
						|
 | 
						|
    # TCP socket options
 | 
						|
    TCP_USER_TIMEOUT = 24
 | 
						|
    TCP_PROBE_AFTER = 5
 | 
						|
    TCP_PROBE_INTERVAL = 2
 | 
						|
    TCP_PROBES = 12
 | 
						|
 | 
						|
    INITIAL_CONNECT_TIMEOUT = 5
 | 
						|
    SYNCHRONOUS_START = True
 | 
						|
 | 
						|
    I2P_USER_TIMEOUT = 45
 | 
						|
    I2P_PROBE_AFTER = 10
 | 
						|
    I2P_PROBE_INTERVAL = 9
 | 
						|
    I2P_PROBES = 5
 | 
						|
 | 
						|
    def __init__(self, owner, configuration, connected_socket=None):
 | 
						|
        super().__init__()
 | 
						|
 | 
						|
        c = Interface.get_config_obj(configuration)
 | 
						|
        name = c["name"]
 | 
						|
        target_ip = c["target_host"] if "target_host" in c and c["target_host"] != None else None
 | 
						|
        target_port = int(c["target_port"]) if "target_port" in c and c["target_host"] != None else None
 | 
						|
        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
 | 
						|
        connect_timeout = c.as_int("connect_timeout") if "connect_timeout" in c else None
 | 
						|
        max_reconnect_tries = c.as_int("max_reconnect_tries") if "max_reconnect_tries" in c else None
 | 
						|
        
 | 
						|
        self.HW_MTU           = TCPInterface.HW_MTU
 | 
						|
        self.IN               = True
 | 
						|
        self.OUT              = False
 | 
						|
        self.socket           = None
 | 
						|
        self.parent_interface = None
 | 
						|
        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     = kiss_framing
 | 
						|
        self.i2p_tunneled     = i2p_tunneled
 | 
						|
        self.mode             = RNS.Interfaces.Interface.Interface.MODE_FULL
 | 
						|
        self.bitrate          = TCPClientInterface.BITRATE_GUESS
 | 
						|
        
 | 
						|
        if max_reconnect_tries == None:
 | 
						|
            self.max_reconnect_tries = TCPClientInterface.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()
 | 
						|
 | 
						|
            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
 | 
						|
 | 
						|
        elif target_ip != None and target_port != None:
 | 
						|
            self.receives    = True
 | 
						|
            self.target_ip   = target_ip
 | 
						|
            self.target_port = target_port
 | 
						|
            self.initiator   = True
 | 
						|
 | 
						|
            if connect_timeout != None:
 | 
						|
                self.connect_timeout = connect_timeout
 | 
						|
            else:
 | 
						|
                self.connect_timeout = TCPClientInterface.INITIAL_CONNECT_TIMEOUT
 | 
						|
            
 | 
						|
            if TCPClientInterface.SYNCHRONOUS_START:
 | 
						|
                self.initial_connect()
 | 
						|
            else:
 | 
						|
                thread = threading.Thread(target=self.initial_connect)
 | 
						|
                thread.daemon = True
 | 
						|
                thread.start()
 | 
						|
            
 | 
						|
    def initial_connect(self):
 | 
						|
        if not self.connect(initial=True):
 | 
						|
            thread = threading.Thread(target=self.reconnect)
 | 
						|
            thread.daemon = True
 | 
						|
            thread.start()
 | 
						|
        else:
 | 
						|
            thread = threading.Thread(target=self.read_loop)
 | 
						|
            thread.daemon = True
 | 
						|
            thread.start()
 | 
						|
            if not self.kiss_framing:
 | 
						|
                self.wants_tunnel = True
 | 
						|
 | 
						|
    def set_timeouts_linux(self):
 | 
						|
        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"):
 | 
						|
            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(TCPClientInterface.TCP_PROBE_AFTER))
 | 
						|
        else:
 | 
						|
            self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
 | 
						|
        
 | 
						|
    def detach(self):
 | 
						|
        self.online = False
 | 
						|
        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:
 | 
						|
            if initial:
 | 
						|
                RNS.log("Establishing TCP connection for "+str(self)+"...", RNS.LOG_DEBUG)
 | 
						|
 | 
						|
            address_info = socket.getaddrinfo(self.target_ip, self.target_port, proto=socket.IPPROTO_TCP)[0]
 | 
						|
            address_family = address_info[0]
 | 
						|
            target_address = address_info[4]
 | 
						|
 | 
						|
            self.socket = socket.socket(address_family, socket.SOCK_STREAM)
 | 
						|
            self.socket.settimeout(TCPClientInterface.INITIAL_CONNECT_TIMEOUT)
 | 
						|
            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
 | 
						|
            self.socket.connect(target_address)
 | 
						|
            self.socket.settimeout(None)
 | 
						|
            self.online  = True
 | 
						|
 | 
						|
            if initial:
 | 
						|
                RNS.log("TCP connection for "+str(self)+" established", RNS.LOG_DEBUG)
 | 
						|
        
 | 
						|
        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
 | 
						|
 | 
						|
        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
 | 
						|
 | 
						|
        return True
 | 
						|
 | 
						|
 | 
						|
    def reconnect(self):
 | 
						|
        if self.initiator:
 | 
						|
            if not self.reconnecting:
 | 
						|
                self.reconnecting = True
 | 
						|
                attempts = 0
 | 
						|
                while not self.online:
 | 
						|
                    time.sleep(TCPClientInterface.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:
 | 
						|
                        RNS.log("Connection attempt for "+str(self)+" failed: "+str(e), RNS.LOG_DEBUG)
 | 
						|
 | 
						|
                if not self.never_connected:
 | 
						|
                    RNS.log("Reconnected socket for "+str(self)+".", RNS.LOG_INFO)
 | 
						|
 | 
						|
                self.reconnecting = False
 | 
						|
                thread = threading.Thread(target=self.read_loop)
 | 
						|
                thread.daemon = True
 | 
						|
                thread.start()
 | 
						|
                if not self.kiss_framing:
 | 
						|
                    RNS.Transport.synthesize_tunnel(self)
 | 
						|
 | 
						|
        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")
 | 
						|
 | 
						|
    def process_incoming(self, data):
 | 
						|
        if self.online and not self.detached:
 | 
						|
            self.rxb += len(data)
 | 
						|
            if hasattr(self, "parent_interface") and self.parent_interface != None:
 | 
						|
                self.parent_interface.rxb += len(data)
 | 
						|
                        
 | 
						|
            self.owner.inbound(data, self)
 | 
						|
 | 
						|
    def process_outgoing(self, data):
 | 
						|
        if self.online and not self.detached:
 | 
						|
            # 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:
 | 
						|
                    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
 | 
						|
            frame_buffer = b""
 | 
						|
            data_in = b""
 | 
						|
            data_buffer = b""
 | 
						|
 | 
						|
            while True:
 | 
						|
                data_in = self.socket.recv(4096)
 | 
						|
                if len(data_in) > 0:
 | 
						|
                    if self.kiss_framing:
 | 
						|
                        # Read loop for KISS framing
 | 
						|
                        pointer = 0
 | 
						|
                        while pointer < len(data_in):
 | 
						|
                            byte = data_in[pointer]
 | 
						|
                            pointer += 1
 | 
						|
                            if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
 | 
						|
                                in_frame = False
 | 
						|
                                self.process_incoming(data_buffer)
 | 
						|
                            elif (byte == KISS.FEND):
 | 
						|
                                in_frame = True
 | 
						|
                                command = KISS.CMD_UNKNOWN
 | 
						|
                                data_buffer = b""
 | 
						|
                            elif (in_frame and len(data_buffer) < self.HW_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 standard HDLC framing
 | 
						|
                        frame_buffer += data_in
 | 
						|
                        flags_remaining = True
 | 
						|
                        while flags_remaining:
 | 
						|
                            frame_start = frame_buffer.find(HDLC.FLAG)
 | 
						|
                            if frame_start != -1:
 | 
						|
                                frame_end = frame_buffer.find(HDLC.FLAG, frame_start+1)
 | 
						|
                                if frame_end != -1:
 | 
						|
                                    frame = frame_buffer[frame_start+1:frame_end]
 | 
						|
                                    frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
 | 
						|
                                    frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC  ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
 | 
						|
                                    if len(frame) > RNS.Reticulum.HEADER_MINSIZE:
 | 
						|
                                        self.process_incoming(frame)
 | 
						|
                                    frame_buffer = frame_buffer[frame_end:]
 | 
						|
                                else:
 | 
						|
                                    flags_remaining = False
 | 
						|
                            else:
 | 
						|
                                flags_remaining = False
 | 
						|
 | 
						|
                else:
 | 
						|
                    self.online = False
 | 
						|
                    if self.initiator and not self.detached:
 | 
						|
                        RNS.log("The socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
 | 
						|
                        self.reconnect()
 | 
						|
                    else:
 | 
						|
                        RNS.log("The 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:
 | 
						|
            while self in self.parent_interface.spawned_interfaces:
 | 
						|
                self.parent_interface.spawned_interfaces.remove(self)
 | 
						|
 | 
						|
        if self in RNS.Transport.interfaces:
 | 
						|
            if not self.initiator:
 | 
						|
                RNS.Transport.interfaces.remove(self)
 | 
						|
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        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)+"]"
 | 
						|
 | 
						|
 | 
						|
class TCPServerInterface(Interface):
 | 
						|
    BITRATE_GUESS     = 10_000_000
 | 
						|
    DEFAULT_IFAC_SIZE = 16
 | 
						|
    AUTOCONFIGURE_MTU = True
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_address_for_if(name, bind_port, prefer_ipv6=False):
 | 
						|
        import RNS.vendor.ifaddr.niwrapper as netinfo
 | 
						|
        ifaddr = netinfo.ifaddresses(name)
 | 
						|
        if len(ifaddr) < 1:
 | 
						|
            raise SystemError(f"No addresses available on specified kernel interface \"{name}\" for TCPServerInterface to bind to")
 | 
						|
 | 
						|
        if (prefer_ipv6 or not netinfo.AF_INET in ifaddr) and netinfo.AF_INET6 in ifaddr:
 | 
						|
            bind_ip = ifaddr[netinfo.AF_INET6][0]["addr"]
 | 
						|
            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)
 | 
						|
        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")
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    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")
 | 
						|
 | 
						|
 | 
						|
    @property
 | 
						|
    def clients(self):
 | 
						|
        return len(self.spawned_interfaces)
 | 
						|
 | 
						|
    def __init__(self, owner, configuration):
 | 
						|
        super().__init__()
 | 
						|
 | 
						|
        c            = Interface.get_config_obj(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
 | 
						|
 | 
						|
        self.HW_MTU = TCPInterface.HW_MTU
 | 
						|
 | 
						|
        self.online = False
 | 
						|
        self.spawned_interfaces = []
 | 
						|
        
 | 
						|
        self.IN  = True
 | 
						|
        self.OUT = False
 | 
						|
        self.name = name
 | 
						|
        self.detached = False
 | 
						|
 | 
						|
        self.i2p_tunneled = i2p_tunneled
 | 
						|
        self.mode         = RNS.Interfaces.Interface.Interface.MODE_FULL
 | 
						|
 | 
						|
        if bindport == None:
 | 
						|
            raise SystemError(f"No TCP port configured for interface \"{name}\"")
 | 
						|
        else:
 | 
						|
            self.bind_port = bindport
 | 
						|
 | 
						|
        bind_address = None
 | 
						|
        if device != None:
 | 
						|
            bind_address = TCPServerInterface.get_address_for_if(device, self.bind_port, prefer_ipv6)
 | 
						|
        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)
 | 
						|
 | 
						|
        if bind_address != None:
 | 
						|
            self.receives = True
 | 
						|
            self.bind_ip = bind_address[0]
 | 
						|
 | 
						|
            def handlerFactory(callback):
 | 
						|
                def createHandler(*args, **keys):
 | 
						|
                    return TCPInterfaceHandler(callback, *args, **keys)
 | 
						|
                return createHandler
 | 
						|
 | 
						|
            self.owner = owner
 | 
						|
 | 
						|
            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))
 | 
						|
 | 
						|
            self.bitrate = TCPServerInterface.BITRATE_GUESS
 | 
						|
 | 
						|
            thread = threading.Thread(target=self.server.serve_forever)
 | 
						|
            thread.daemon = True
 | 
						|
            thread.start()
 | 
						|
 | 
						|
            self.online = True
 | 
						|
 | 
						|
        else:
 | 
						|
            raise SystemError("Insufficient parameters to create TCP listener")
 | 
						|
 | 
						|
    def incoming_connection(self, handler):
 | 
						|
        RNS.log("Accepting incoming TCP connection", RNS.LOG_VERBOSE)
 | 
						|
        spawned_configuration = {"name": "Client on "+self.name, "target_host": None, "target_port": None, "i2p_tunneled": self.i2p_tunneled}
 | 
						|
        spawned_interface = TCPClientInterface(self.owner, spawned_configuration, 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
 | 
						|
        spawned_interface.bitrate = self.bitrate
 | 
						|
        spawned_interface.optimise_mtu()
 | 
						|
        
 | 
						|
        spawned_interface.ifac_size = self.ifac_size
 | 
						|
        spawned_interface.ifac_netname = self.ifac_netname
 | 
						|
        spawned_interface.ifac_netkey = self.ifac_netkey
 | 
						|
        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))
 | 
						|
 | 
						|
        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
 | 
						|
        spawned_interface.mode = self.mode
 | 
						|
        spawned_interface.HW_MTU = self.HW_MTU
 | 
						|
        spawned_interface.online = True
 | 
						|
        RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
 | 
						|
        RNS.Transport.interfaces.append(spawned_interface)
 | 
						|
        while spawned_interface in self.spawned_interfaces:
 | 
						|
            self.spawned_interfaces.remove(spawned_interface)
 | 
						|
        self.spawned_interfaces.append(spawned_interface)
 | 
						|
        spawned_interface.read_loop()
 | 
						|
 | 
						|
    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())
 | 
						|
 | 
						|
    def process_outgoing(self, data):
 | 
						|
        pass
 | 
						|
 | 
						|
    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))
 | 
						|
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        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)+"]"
 | 
						|
 | 
						|
 | 
						|
class TCPInterfaceHandler(socketserver.BaseRequestHandler):
 | 
						|
    def __init__(self, callback, *args, **keys):
 | 
						|
        self.callback = callback
 | 
						|
        socketserver.BaseRequestHandler.__init__(self, *args, **keys)
 | 
						|
 | 
						|
    def handle(self):
 | 
						|
        self.callback(handler=self)
 |