/ RNS / Interfaces / TCPInterface.py
TCPInterface.py
  1  # Reticulum License
  2  #
  3  # Copyright (c) 2016-2025 Mark Qvist
  4  #
  5  # Permission is hereby granted, free of charge, to any person obtaining a copy
  6  # of this software and associated documentation files (the "Software"), to deal
  7  # in the Software without restriction, including without limitation the rights
  8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9  # copies of the Software, and to permit persons to whom the Software is
 10  # furnished to do so, subject to the following conditions:
 11  #
 12  # - The Software shall not be used in any kind of system which includes amongst
 13  #   its functions the ability to purposefully do harm to human beings.
 14  #
 15  # - The Software shall not be used, directly or indirectly, in the creation of
 16  #   an artificial intelligence, machine learning or language model training
 17  #   dataset, including but not limited to any use that contributes to the
 18  #   training or development of such a model or algorithm.
 19  #
 20  # - The above copyright notice and this permission notice shall be included in
 21  #   all copies or substantial portions of the Software.
 22  #
 23  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 24  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 25  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 26  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 27  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 28  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 29  # SOFTWARE.
 30  
 31  from RNS.Interfaces.Interface import Interface
 32  import socketserver
 33  import threading
 34  import platform
 35  import socket
 36  import time
 37  import sys
 38  import os
 39  import RNS
 40  
 41  class TCPInterface():
 42      HW_MTU            = 262144
 43  
 44  class HDLC():
 45      FLAG              = 0x7E
 46      ESC               = 0x7D
 47      ESC_MASK          = 0x20
 48  
 49      @staticmethod
 50      def escape(data):
 51          data = data.replace(bytes([HDLC.ESC]), bytes([HDLC.ESC, HDLC.ESC^HDLC.ESC_MASK]))
 52          data = data.replace(bytes([HDLC.FLAG]), bytes([HDLC.ESC, HDLC.FLAG^HDLC.ESC_MASK]))
 53          return data
 54  
 55  class KISS():
 56      FEND              = 0xC0
 57      FESC              = 0xDB
 58      TFEND             = 0xDC
 59      TFESC             = 0xDD
 60      CMD_DATA          = 0x00
 61      CMD_UNKNOWN       = 0xFE
 62  
 63      @staticmethod
 64      def escape(data):
 65          data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
 66          data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
 67          return data
 68  
 69  class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
 70      pass
 71  
 72  class ThreadingTCP6Server(socketserver.ThreadingMixIn, socketserver.TCPServer):
 73      address_family = socket.AF_INET6
 74  
 75  class TCPClientInterface(Interface):
 76      BITRATE_GUESS = 10*1000*1000
 77      DEFAULT_IFAC_SIZE = 16
 78      AUTOCONFIGURE_MTU = True
 79  
 80      RECONNECT_WAIT = 5
 81      RECONNECT_MAX_TRIES = None
 82  
 83      # TCP socket options
 84      TCP_USER_TIMEOUT = 24
 85      TCP_PROBE_AFTER = 5
 86      TCP_PROBE_INTERVAL = 2
 87      TCP_PROBES = 12
 88  
 89      INITIAL_CONNECT_TIMEOUT = 5
 90      SYNCHRONOUS_START = True
 91  
 92      I2P_USER_TIMEOUT = 45
 93      I2P_PROBE_AFTER = 10
 94      I2P_PROBE_INTERVAL = 9
 95      I2P_PROBES = 5
 96  
 97      def __init__(self, owner, configuration, connected_socket=None):
 98          super().__init__()
 99  
100          c = Interface.get_config_obj(configuration)
101          name = c["name"]
102          target_ip = c["target_host"] if "target_host" in c and c["target_host"] != None else None
103          target_port = int(c["target_port"]) if "target_port" in c and c["target_host"] != None else None
104          kiss_framing = False
105          if "kiss_framing" in c and c.as_bool("kiss_framing") == True:
106              kiss_framing = True
107          i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
108          connect_timeout = c.as_int("connect_timeout") if "connect_timeout" in c else None
109          max_reconnect_tries = c.as_int("max_reconnect_tries") if "max_reconnect_tries" in c else None
110          fixed_mtu = c.as_int("fixed_mtu") if "fixed_mtu" in c else None
111          if fixed_mtu:
112              if fixed_mtu < RNS.Reticulum.MTU: raise ValueError(f"Configured MTU of {fixed_mtu} bytes is too small")
113              self.AUTOCONFIGURE_MTU = False
114              self.FIXED_MTU = True
115          
116          self.HW_MTU           = TCPInterface.HW_MTU if not fixed_mtu else fixed_mtu
117          self.IN               = True
118          self.OUT              = False
119          self.socket           = None
120          self.parent_interface = None
121          self.name             = name
122          self.initiator        = False
123          self.reconnecting     = False
124          self.never_connected  = True
125          self.owner            = owner
126          self.writing          = False
127          self.online           = False
128          self.detached         = False
129          self.kiss_framing     = kiss_framing
130          self.i2p_tunneled     = i2p_tunneled
131          self.mode             = RNS.Interfaces.Interface.Interface.MODE_FULL
132          self.bitrate          = TCPClientInterface.BITRATE_GUESS
133          
134          self.supports_discovery = True
135          if max_reconnect_tries == None: self.max_reconnect_tries = TCPClientInterface.RECONNECT_MAX_TRIES
136          else: self.max_reconnect_tries = max_reconnect_tries
137  
138          if connected_socket != None:
139              self.receives    = True
140              self.target_ip   = None
141              self.target_port = None
142              self.socket      = connected_socket
143  
144              if platform.system() == "Linux":
145                  self.set_timeouts_linux()
146              elif platform.system() == "Darwin":
147                  self.set_timeouts_osx()
148  
149              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
150  
151          elif target_ip != None and target_port != None:
152              self.receives    = True
153              self.target_ip   = target_ip
154              self.target_port = target_port
155              self.initiator   = True
156  
157              if connect_timeout != None:
158                  self.connect_timeout = connect_timeout
159              else:
160                  self.connect_timeout = TCPClientInterface.INITIAL_CONNECT_TIMEOUT
161              
162              if TCPClientInterface.SYNCHRONOUS_START:
163                  self.initial_connect()
164              else:
165                  thread = threading.Thread(target=self.initial_connect)
166                  thread.daemon = True
167                  thread.start()
168              
169      def initial_connect(self):
170          if not self.connect(initial=True):
171              thread = threading.Thread(target=self.reconnect)
172              thread.daemon = True
173              thread.start()
174          else:
175              thread = threading.Thread(target=self.read_loop)
176              thread.daemon = True
177              thread.start()
178              if not self.kiss_framing:
179                  self.wants_tunnel = True
180  
181      def set_timeouts_linux(self):
182          if not self.i2p_tunneled:
183              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.TCP_USER_TIMEOUT * 1000))
184              self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
185              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
186              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.TCP_PROBE_INTERVAL))
187              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.TCP_PROBES))
188  
189          else:
190              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.I2P_USER_TIMEOUT * 1000))
191              self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
192              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
193              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.I2P_PROBE_INTERVAL))
194              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.I2P_PROBES))
195  
196      def set_timeouts_osx(self):
197          if hasattr(socket, "TCP_KEEPALIVE"):
198              TCP_KEEPIDLE = socket.TCP_KEEPALIVE
199          else:
200              TCP_KEEPIDLE = 0x10
201  
202          self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
203          
204          if not self.i2p_tunneled:
205              self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
206          else:
207              self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
208          
209      def detach(self):
210          self.online = False
211          if self.socket != None:
212              if hasattr(self.socket, "close"):
213                  if callable(self.socket.close):
214                      self.detached = True
215                      
216                      try:
217                          if self.socket != None:
218                              self.socket.shutdown(socket.SHUT_RDWR)
219                      except Exception as e:
220                          RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
221  
222                      try:
223                          if self.socket != None:
224                              self.socket.close()
225                      except Exception as e:
226                          RNS.log("Error while closing socket for "+str(self)+": "+str(e))
227  
228                      self.socket = None
229  
230      def connect(self, initial=False):
231          try:
232              if initial:
233                  RNS.log("Establishing TCP connection for "+str(self)+"...", RNS.LOG_DEBUG)
234  
235              address_info = socket.getaddrinfo(self.target_ip, self.target_port, proto=socket.IPPROTO_TCP)[0]
236              address_family = address_info[0]
237              target_address = address_info[4]
238  
239              self.socket = socket.socket(address_family, socket.SOCK_STREAM)
240              self.socket.settimeout(TCPClientInterface.INITIAL_CONNECT_TIMEOUT)
241              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
242              self.socket.connect(target_address)
243              self.socket.settimeout(None)
244              self.online  = True
245  
246              if initial:
247                  RNS.log("TCP connection for "+str(self)+" established", RNS.LOG_DEBUG)
248          
249          except Exception as e:
250              if initial:
251                  RNS.log("Initial connection for "+str(self)+" could not be established: "+str(e), RNS.LOG_ERROR)
252                  RNS.log("Leaving unconnected and retrying connection in "+str(TCPClientInterface.RECONNECT_WAIT)+" seconds.", RNS.LOG_ERROR)
253                  return False
254              
255              else:
256                  raise e
257  
258          if platform.system() == "Linux":
259              self.set_timeouts_linux()
260          elif platform.system() == "Darwin":
261              self.set_timeouts_osx()
262          
263          self.online  = True
264          self.writing = False
265          self.never_connected = False
266  
267          return True
268  
269  
270      def reconnect(self):
271          if self.initiator:
272              if not self.reconnecting:
273                  self.reconnecting = True
274                  attempts = 0
275                  while not self.online:
276                      time.sleep(TCPClientInterface.RECONNECT_WAIT)
277                      attempts += 1
278  
279                      if self.max_reconnect_tries != None and attempts > self.max_reconnect_tries:
280                          RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_ERROR)
281                          self.teardown()
282                          break
283  
284                      try:
285                          self.connect()
286  
287                      except Exception as e:
288                          RNS.log("Connection attempt for "+str(self)+" failed: "+str(e), RNS.LOG_DEBUG)
289  
290                  if not self.never_connected:
291                      RNS.log("Reconnected socket for "+str(self)+".", RNS.LOG_INFO)
292  
293                  self.reconnecting = False
294                  thread = threading.Thread(target=self.read_loop)
295                  thread.daemon = True
296                  thread.start()
297                  if not self.kiss_framing:
298                      RNS.Transport.synthesize_tunnel(self)
299  
300          else:
301              RNS.log("Attempt to reconnect on a non-initiator TCP interface. This should not happen.", RNS.LOG_ERROR)
302              raise IOError("Attempt to reconnect on a non-initiator TCP interface")
303  
304      def process_incoming(self, data):
305          if self.online and not self.detached:
306              self.rxb += len(data)
307              if hasattr(self, "parent_interface") and self.parent_interface != None:
308                  self.parent_interface.rxb += len(data)
309                          
310              self.owner.inbound(data, self)
311  
312      def process_outgoing(self, data):
313          if self.online and not self.detached:
314              # while self.writing:
315              #     time.sleep(0.01)
316  
317              try:
318                  self.writing = True
319  
320                  if self.kiss_framing:
321                      data = bytes([KISS.FEND])+bytes([KISS.CMD_DATA])+KISS.escape(data)+bytes([KISS.FEND])
322                  else:
323                      data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
324  
325                  self.socket.sendall(data)
326                  self.writing = False
327                  self.txb += len(data)
328                  if hasattr(self, "parent_interface") and self.parent_interface != None:
329                      self.parent_interface.txb += len(data)
330  
331              except Exception as e:
332                  RNS.log("Exception occurred while transmitting via "+str(self)+", tearing down interface", RNS.LOG_ERROR)
333                  RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
334                  self.teardown()
335  
336  
337      def read_loop(self):
338          try:
339              in_frame = False
340              escape = False
341              frame_buffer = b""
342              data_in = b""
343              data_buffer = b""
344  
345              while True:
346                  if self.socket: data_in = self.socket.recv(4096)
347                  else: data_in = b""
348                  if len(data_in) > 0:
349                      if self.kiss_framing:
350                          # Read loop for KISS framing
351                          pointer = 0
352                          while pointer < len(data_in):
353                              byte = data_in[pointer]
354                              pointer += 1
355                              if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
356                                  in_frame = False
357                                  self.process_incoming(data_buffer)
358                              elif (byte == KISS.FEND):
359                                  in_frame = True
360                                  command = KISS.CMD_UNKNOWN
361                                  data_buffer = b""
362                              elif (in_frame and len(data_buffer) < self.HW_MTU):
363                                  if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
364                                      # We only support one HDLC port for now, so
365                                      # strip off the port nibble
366                                      byte = byte & 0x0F
367                                      command = byte
368                                  elif (command == KISS.CMD_DATA):
369                                      if (byte == KISS.FESC):
370                                          escape = True
371                                      else:
372                                          if (escape):
373                                              if (byte == KISS.TFEND):
374                                                  byte = KISS.FEND
375                                              if (byte == KISS.TFESC):
376                                                  byte = KISS.FESC
377                                              escape = False
378                                          data_buffer = data_buffer+bytes([byte])
379  
380                      else:
381                          # Read loop for standard HDLC framing
382                          frame_buffer += data_in
383                          flags_remaining = True
384                          while flags_remaining:
385                              frame_start = frame_buffer.find(HDLC.FLAG)
386                              if frame_start != -1:
387                                  frame_end = frame_buffer.find(HDLC.FLAG, frame_start+1)
388                                  if frame_end != -1:
389                                      frame = frame_buffer[frame_start+1:frame_end]
390                                      frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
391                                      frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC  ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
392                                      if len(frame) > RNS.Reticulum.HEADER_MINSIZE:
393                                          self.process_incoming(frame)
394                                      frame_buffer = frame_buffer[frame_end:]
395                                  else:
396                                      flags_remaining = False
397                              else:
398                                  flags_remaining = False
399  
400                  else:
401                      self.online = False
402                      if self.initiator and not self.detached:
403                          RNS.log("The socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
404                          self.reconnect()
405                      else:
406                          RNS.log("The socket for remote client "+str(self)+" was closed.", RNS.LOG_VERBOSE)
407                          self.teardown()
408  
409                      break
410  
411                  
412          except Exception as e:
413              self.online = False
414              RNS.log("An interface error occurred for "+str(self)+", the contained exception was: "+str(e), RNS.LOG_WARNING)
415  
416              if self.initiator:
417                  RNS.log("Attempting to reconnect...", RNS.LOG_WARNING)
418                  self.reconnect()
419              else:
420                  self.teardown()
421  
422      def teardown(self):
423          if self.initiator and not self.detached:
424              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)
425              if RNS.Reticulum.panic_on_interface_error:
426                  RNS.panic()
427  
428          else:
429              RNS.log("The interface "+str(self)+" is being torn down.", RNS.LOG_VERBOSE)
430  
431          self.online = False
432          self.OUT = False
433          self.IN = False
434  
435          if hasattr(self, "parent_interface") and self.parent_interface != None:
436              while self in self.parent_interface.spawned_interfaces:
437                  self.parent_interface.spawned_interfaces.remove(self)
438  
439          if self in RNS.Transport.interfaces:
440              if not self.initiator:
441                  RNS.Transport.interfaces.remove(self)
442  
443  
444      def __str__(self):
445          if ":" in self.target_ip:
446              ip_str = f"[{self.target_ip}]"
447          else:
448              ip_str = f"{self.target_ip}"
449  
450          return "TCPInterface["+str(self.name)+"/"+ip_str+":"+str(self.target_port)+"]"
451  
452  
453  class TCPServerInterface(Interface):
454      BITRATE_GUESS     = 10_000_000
455      DEFAULT_IFAC_SIZE = 16
456      AUTOCONFIGURE_MTU = True
457  
458      @staticmethod
459      def get_address_for_if(name, bind_port, prefer_ipv6=False):
460          from RNS.Interfaces import netinfo
461          ifaddr = netinfo.ifaddresses(name)
462          if len(ifaddr) < 1:
463              raise SystemError(f"No addresses available on specified kernel interface \"{name}\" for TCPServerInterface to bind to")
464  
465          if (prefer_ipv6 or not netinfo.AF_INET in ifaddr) and netinfo.AF_INET6 in ifaddr:
466              bind_ip = ifaddr[netinfo.AF_INET6][0]["addr"]
467              if bind_ip.lower().startswith("fe80::"):
468                  # We'll need to add the interface as scope for link-local addresses
469                  return TCPServerInterface.get_address_for_host(f"{bind_ip}%{name}", bind_port, prefer_ipv6)
470              else:
471                  return TCPServerInterface.get_address_for_host(bind_ip, bind_port, prefer_ipv6)
472          elif netinfo.AF_INET in ifaddr:
473              bind_ip = ifaddr[netinfo.AF_INET][0]["addr"]
474              return (bind_ip, bind_port)
475          else:
476              raise SystemError(f"No addresses available on specified kernel interface \"{name}\" for TCPServerInterface to bind to")
477  
478      @staticmethod
479      def get_address_for_host(name, bind_port, prefer_ipv6=False):
480          address_infos = socket.getaddrinfo(name, bind_port, proto=socket.IPPROTO_TCP)
481          address_info  = address_infos[0]
482          for entry in address_infos:
483              if prefer_ipv6 and entry[0] == socket.AF_INET6:
484                  address_info = entry; break
485              elif not prefer_ipv6 and entry[0] == socket.AF_INET:
486                  address_info = entry; break
487  
488          if address_info[0] == socket.AF_INET6:
489              return (name, bind_port, address_info[4][2], address_info[4][3])
490          elif address_info[0] == socket.AF_INET:
491              return (name, bind_port)
492          else:
493              raise SystemError(f"No suitable kernel interface available for address \"{name}\" for TCPServerInterface to bind to")
494  
495  
496      @property
497      def clients(self):
498          return len(self.spawned_interfaces)
499  
500      def __init__(self, owner, configuration):
501          super().__init__()
502  
503          c            = Interface.get_config_obj(configuration)
504          name         = c["name"]
505          device       = c["device"] if "device" in c else None
506          port         = int(c["port"]) if "port" in c else None
507          bindip       = c["listen_ip"] if "listen_ip" in c else None
508          bindport     = int(c["listen_port"]) if "listen_port" in c else None
509          i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
510          prefer_ipv6  = c.as_bool("prefer_ipv6") if "prefer_ipv6" in c else False
511  
512          if port != None:
513              bindport = port
514  
515          self.supports_discovery = True
516          self.HW_MTU = TCPInterface.HW_MTU
517  
518          self.online = False
519          self.spawned_interfaces = []
520          
521          self.IN  = True
522          self.OUT = False
523          self.name = name
524          self.detached = False
525  
526          self.i2p_tunneled = i2p_tunneled
527          self.mode         = RNS.Interfaces.Interface.Interface.MODE_FULL
528  
529          if bindport == None:
530              raise SystemError(f"No TCP port configured for interface \"{name}\"")
531          else:
532              self.bind_port = bindport
533  
534          bind_address = None
535          if device != None:
536              bind_address = TCPServerInterface.get_address_for_if(device, self.bind_port, prefer_ipv6)
537          else:
538              if bindip == None:
539                  raise SystemError(f"No TCP bind IP configured for interface \"{name}\"")
540              bind_address = TCPServerInterface.get_address_for_host(bindip, self.bind_port, prefer_ipv6)
541  
542          if bind_address != None:
543              self.receives = True
544              self.bind_ip = bind_address[0]
545  
546              def handlerFactory(callback):
547                  def createHandler(*args, **keys):
548                      return TCPInterfaceHandler(callback, *args, **keys)
549                  return createHandler
550  
551              self.owner = owner
552  
553              if len(bind_address) == 4:
554                  try:
555                      ThreadingTCP6Server.allow_reuse_address = True
556                      self.server = ThreadingTCP6Server(bind_address, handlerFactory(self.incoming_connection))
557                  except Exception as e:
558                      RNS.log(f"Error while binding IPv6 socket for interface, the contained exception was: {e}", RNS.LOG_ERROR)
559                      raise SystemError("Could not bind IPv6 socket for interface. Please check the specified \"listen_ip\" configuration option")
560              else:
561                  ThreadingTCPServer.allow_reuse_address = True
562                  self.server = ThreadingTCPServer(bind_address, handlerFactory(self.incoming_connection))
563                  self.server.daemon_threads = True
564  
565              self.bitrate = TCPServerInterface.BITRATE_GUESS
566  
567              thread = threading.Thread(target=self.server.serve_forever)
568              thread.daemon = True
569              thread.start()
570  
571              self.online = True
572  
573          else:
574              raise SystemError("Insufficient parameters to create TCP listener")
575  
576      def incoming_connection(self, handler):
577          RNS.log("Accepting incoming TCP connection", RNS.LOG_VERBOSE)
578          spawned_configuration = {"name": "Client on "+self.name, "target_host": None, "target_port": None, "i2p_tunneled": self.i2p_tunneled}
579          spawned_interface = TCPClientInterface(self.owner, spawned_configuration, connected_socket=handler.request)
580          spawned_interface.OUT = self.OUT
581          spawned_interface.IN  = self.IN
582          spawned_interface.target_ip = handler.client_address[0]
583          spawned_interface.target_port = str(handler.client_address[1])
584          spawned_interface.parent_interface = self
585          spawned_interface.bitrate = self.bitrate
586          spawned_interface.optimise_mtu()
587          
588          spawned_interface.ifac_size = self.ifac_size
589          spawned_interface.ifac_netname = self.ifac_netname
590          spawned_interface.ifac_netkey = self.ifac_netkey
591          if spawned_interface.ifac_netname != None or spawned_interface.ifac_netkey != None:
592              ifac_origin = b""
593              if spawned_interface.ifac_netname != None:
594                  ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netname.encode("utf-8"))
595              if spawned_interface.ifac_netkey != None:
596                  ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netkey.encode("utf-8"))
597  
598              ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
599              spawned_interface.ifac_key = RNS.Cryptography.hkdf(
600                  length=64,
601                  derive_from=ifac_origin_hash,
602                  salt=RNS.Reticulum.IFAC_SALT,
603                  context=None
604              )
605              spawned_interface.ifac_identity = RNS.Identity.from_bytes(spawned_interface.ifac_key)
606              spawned_interface.ifac_signature = spawned_interface.ifac_identity.sign(RNS.Identity.full_hash(spawned_interface.ifac_key))
607  
608          spawned_interface.announce_rate_target = self.announce_rate_target
609          spawned_interface.announce_rate_grace = self.announce_rate_grace
610          spawned_interface.announce_rate_penalty = self.announce_rate_penalty
611          spawned_interface.mode = self.mode
612          spawned_interface.HW_MTU = self.HW_MTU
613          spawned_interface.online = True
614          RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
615          RNS.Transport.interfaces.append(spawned_interface)
616          while spawned_interface in self.spawned_interfaces:
617              self.spawned_interfaces.remove(spawned_interface)
618          self.spawned_interfaces.append(spawned_interface)
619          spawned_interface.read_loop()
620  
621      def received_announce(self, from_spawned=False):
622          if from_spawned: self.ia_freq_deque.append(time.time())
623  
624      def sent_announce(self, from_spawned=False):
625          if from_spawned: self.oa_freq_deque.append(time.time())
626  
627      def process_outgoing(self, data):
628          pass
629  
630      def detach(self):
631          self.detached = True
632          self.online = False
633          if self.server != None:
634              if hasattr(self.server, "shutdown"):
635                  if callable(self.server.shutdown):
636                      try:
637                          RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
638                          self.server.shutdown()
639                          self.server.server_close()
640                          self.server = None
641  
642                      except Exception as e:
643                          RNS.log("Error while shutting down server for "+str(self)+": "+str(e))
644  
645  
646      def __str__(self):
647          if ":" in self.bind_ip:
648              ip_str = f"[{self.bind_ip}]"
649          else:
650              ip_str = f"{self.bind_ip}"
651  
652          return "TCPServerInterface["+self.name+"/"+ip_str+":"+str(self.bind_port)+"]"
653  
654  
655  class TCPInterfaceHandler(socketserver.BaseRequestHandler):
656      def __init__(self, callback, *args, **keys):
657          self.callback = callback
658          socketserver.BaseRequestHandler.__init__(self, *args, **keys)
659  
660      def handle(self):
661          self.callback(handler=self)