/ 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          
111          self.HW_MTU           = TCPInterface.HW_MTU
112          self.IN               = True
113          self.OUT              = False
114          self.socket           = None
115          self.parent_interface = None
116          self.name             = name
117          self.initiator        = False
118          self.reconnecting     = False
119          self.never_connected  = True
120          self.owner            = owner
121          self.writing          = False
122          self.online           = False
123          self.detached         = False
124          self.kiss_framing     = kiss_framing
125          self.i2p_tunneled     = i2p_tunneled
126          self.mode             = RNS.Interfaces.Interface.Interface.MODE_FULL
127          self.bitrate          = TCPClientInterface.BITRATE_GUESS
128          
129          if max_reconnect_tries == None:
130              self.max_reconnect_tries = TCPClientInterface.RECONNECT_MAX_TRIES
131          else:
132              self.max_reconnect_tries = max_reconnect_tries
133  
134          if connected_socket != None:
135              self.receives    = True
136              self.target_ip   = None
137              self.target_port = None
138              self.socket      = connected_socket
139  
140              if platform.system() == "Linux":
141                  self.set_timeouts_linux()
142              elif platform.system() == "Darwin":
143                  self.set_timeouts_osx()
144  
145              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
146  
147          elif target_ip != None and target_port != None:
148              self.receives    = True
149              self.target_ip   = target_ip
150              self.target_port = target_port
151              self.initiator   = True
152  
153              if connect_timeout != None:
154                  self.connect_timeout = connect_timeout
155              else:
156                  self.connect_timeout = TCPClientInterface.INITIAL_CONNECT_TIMEOUT
157              
158              if TCPClientInterface.SYNCHRONOUS_START:
159                  self.initial_connect()
160              else:
161                  thread = threading.Thread(target=self.initial_connect)
162                  thread.daemon = True
163                  thread.start()
164              
165      def initial_connect(self):
166          if not self.connect(initial=True):
167              thread = threading.Thread(target=self.reconnect)
168              thread.daemon = True
169              thread.start()
170          else:
171              thread = threading.Thread(target=self.read_loop)
172              thread.daemon = True
173              thread.start()
174              if not self.kiss_framing:
175                  self.wants_tunnel = True
176  
177      def set_timeouts_linux(self):
178          if not self.i2p_tunneled:
179              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.TCP_USER_TIMEOUT * 1000))
180              self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
181              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
182              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.TCP_PROBE_INTERVAL))
183              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.TCP_PROBES))
184  
185          else:
186              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.I2P_USER_TIMEOUT * 1000))
187              self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
188              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
189              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.I2P_PROBE_INTERVAL))
190              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.I2P_PROBES))
191  
192      def set_timeouts_osx(self):
193          if hasattr(socket, "TCP_KEEPALIVE"):
194              TCP_KEEPIDLE = socket.TCP_KEEPALIVE
195          else:
196              TCP_KEEPIDLE = 0x10
197  
198          self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
199          
200          if not self.i2p_tunneled:
201              self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
202          else:
203              self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
204          
205      def detach(self):
206          self.online = False
207          if self.socket != None:
208              if hasattr(self.socket, "close"):
209                  if callable(self.socket.close):
210                      self.detached = True
211                      
212                      try:
213                          if self.socket != None:
214                              self.socket.shutdown(socket.SHUT_RDWR)
215                      except Exception as e:
216                          RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
217  
218                      try:
219                          if self.socket != None:
220                              self.socket.close()
221                      except Exception as e:
222                          RNS.log("Error while closing socket for "+str(self)+": "+str(e))
223  
224                      self.socket = None
225  
226      def connect(self, initial=False):
227          try:
228              if initial:
229                  RNS.log("Establishing TCP connection for "+str(self)+"...", RNS.LOG_DEBUG)
230  
231              address_info = socket.getaddrinfo(self.target_ip, self.target_port, proto=socket.IPPROTO_TCP)[0]
232              address_family = address_info[0]
233              target_address = address_info[4]
234  
235              self.socket = socket.socket(address_family, socket.SOCK_STREAM)
236              self.socket.settimeout(TCPClientInterface.INITIAL_CONNECT_TIMEOUT)
237              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
238              self.socket.connect(target_address)
239              self.socket.settimeout(None)
240              self.online  = True
241  
242              if initial:
243                  RNS.log("TCP connection for "+str(self)+" established", RNS.LOG_DEBUG)
244          
245          except Exception as e:
246              if initial:
247                  RNS.log("Initial connection for "+str(self)+" could not be established: "+str(e), RNS.LOG_ERROR)
248                  RNS.log("Leaving unconnected and retrying connection in "+str(TCPClientInterface.RECONNECT_WAIT)+" seconds.", RNS.LOG_ERROR)
249                  return False
250              
251              else:
252                  raise e
253  
254          if platform.system() == "Linux":
255              self.set_timeouts_linux()
256          elif platform.system() == "Darwin":
257              self.set_timeouts_osx()
258          
259          self.online  = True
260          self.writing = False
261          self.never_connected = False
262  
263          return True
264  
265  
266      def reconnect(self):
267          if self.initiator:
268              if not self.reconnecting:
269                  self.reconnecting = True
270                  attempts = 0
271                  while not self.online:
272                      time.sleep(TCPClientInterface.RECONNECT_WAIT)
273                      attempts += 1
274  
275                      if self.max_reconnect_tries != None and attempts > self.max_reconnect_tries:
276                          RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_ERROR)
277                          self.teardown()
278                          break
279  
280                      try:
281                          self.connect()
282  
283                      except Exception as e:
284                          RNS.log("Connection attempt for "+str(self)+" failed: "+str(e), RNS.LOG_DEBUG)
285  
286                  if not self.never_connected:
287                      RNS.log("Reconnected socket for "+str(self)+".", RNS.LOG_INFO)
288  
289                  self.reconnecting = False
290                  thread = threading.Thread(target=self.read_loop)
291                  thread.daemon = True
292                  thread.start()
293                  if not self.kiss_framing:
294                      RNS.Transport.synthesize_tunnel(self)
295  
296          else:
297              RNS.log("Attempt to reconnect on a non-initiator TCP interface. This should not happen.", RNS.LOG_ERROR)
298              raise IOError("Attempt to reconnect on a non-initiator TCP interface")
299  
300      def process_incoming(self, data):
301          if self.online and not self.detached:
302              self.rxb += len(data)
303              if hasattr(self, "parent_interface") and self.parent_interface != None:
304                  self.parent_interface.rxb += len(data)
305                          
306              self.owner.inbound(data, self)
307  
308      def process_outgoing(self, data):
309          if self.online and not self.detached:
310              # while self.writing:
311              #     time.sleep(0.01)
312  
313              try:
314                  self.writing = True
315  
316                  if self.kiss_framing:
317                      data = bytes([KISS.FEND])+bytes([KISS.CMD_DATA])+KISS.escape(data)+bytes([KISS.FEND])
318                  else:
319                      data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
320  
321                  self.socket.sendall(data)
322                  self.writing = False
323                  self.txb += len(data)
324                  if hasattr(self, "parent_interface") and self.parent_interface != None:
325                      self.parent_interface.txb += len(data)
326  
327              except Exception as e:
328                  RNS.log("Exception occurred while transmitting via "+str(self)+", tearing down interface", RNS.LOG_ERROR)
329                  RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
330                  self.teardown()
331  
332  
333      def read_loop(self):
334          try:
335              in_frame = False
336              escape = False
337              frame_buffer = b""
338              data_in = b""
339              data_buffer = b""
340  
341              while True:
342                  if self.socket: data_in = self.socket.recv(4096)
343                  else: data_in = b""
344                  if len(data_in) > 0:
345                      if self.kiss_framing:
346                          # Read loop for KISS framing
347                          pointer = 0
348                          while pointer < len(data_in):
349                              byte = data_in[pointer]
350                              pointer += 1
351                              if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
352                                  in_frame = False
353                                  self.process_incoming(data_buffer)
354                              elif (byte == KISS.FEND):
355                                  in_frame = True
356                                  command = KISS.CMD_UNKNOWN
357                                  data_buffer = b""
358                              elif (in_frame and len(data_buffer) < self.HW_MTU):
359                                  if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
360                                      # We only support one HDLC port for now, so
361                                      # strip off the port nibble
362                                      byte = byte & 0x0F
363                                      command = byte
364                                  elif (command == KISS.CMD_DATA):
365                                      if (byte == KISS.FESC):
366                                          escape = True
367                                      else:
368                                          if (escape):
369                                              if (byte == KISS.TFEND):
370                                                  byte = KISS.FEND
371                                              if (byte == KISS.TFESC):
372                                                  byte = KISS.FESC
373                                              escape = False
374                                          data_buffer = data_buffer+bytes([byte])
375  
376                      else:
377                          # Read loop for standard HDLC framing
378                          frame_buffer += data_in
379                          flags_remaining = True
380                          while flags_remaining:
381                              frame_start = frame_buffer.find(HDLC.FLAG)
382                              if frame_start != -1:
383                                  frame_end = frame_buffer.find(HDLC.FLAG, frame_start+1)
384                                  if frame_end != -1:
385                                      frame = frame_buffer[frame_start+1:frame_end]
386                                      frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
387                                      frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC  ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
388                                      if len(frame) > RNS.Reticulum.HEADER_MINSIZE:
389                                          self.process_incoming(frame)
390                                      frame_buffer = frame_buffer[frame_end:]
391                                  else:
392                                      flags_remaining = False
393                              else:
394                                  flags_remaining = False
395  
396                  else:
397                      self.online = False
398                      if self.initiator and not self.detached:
399                          RNS.log("The socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
400                          self.reconnect()
401                      else:
402                          RNS.log("The socket for remote client "+str(self)+" was closed.", RNS.LOG_VERBOSE)
403                          self.teardown()
404  
405                      break
406  
407                  
408          except Exception as e:
409              self.online = False
410              RNS.log("An interface error occurred for "+str(self)+", the contained exception was: "+str(e), RNS.LOG_WARNING)
411  
412              if self.initiator:
413                  RNS.log("Attempting to reconnect...", RNS.LOG_WARNING)
414                  self.reconnect()
415              else:
416                  self.teardown()
417  
418      def teardown(self):
419          if self.initiator and not self.detached:
420              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)
421              if RNS.Reticulum.panic_on_interface_error:
422                  RNS.panic()
423  
424          else:
425              RNS.log("The interface "+str(self)+" is being torn down.", RNS.LOG_VERBOSE)
426  
427          self.online = False
428          self.OUT = False
429          self.IN = False
430  
431          if hasattr(self, "parent_interface") and self.parent_interface != None:
432              while self in self.parent_interface.spawned_interfaces:
433                  self.parent_interface.spawned_interfaces.remove(self)
434  
435          if self in RNS.Transport.interfaces:
436              if not self.initiator:
437                  RNS.Transport.interfaces.remove(self)
438  
439  
440      def __str__(self):
441          if ":" in self.target_ip:
442              ip_str = f"[{self.target_ip}]"
443          else:
444              ip_str = f"{self.target_ip}"
445  
446          return "TCPInterface["+str(self.name)+"/"+ip_str+":"+str(self.target_port)+"]"
447  
448  
449  class TCPServerInterface(Interface):
450      BITRATE_GUESS     = 10_000_000
451      DEFAULT_IFAC_SIZE = 16
452      AUTOCONFIGURE_MTU = True
453  
454      @staticmethod
455      def get_address_for_if(name, bind_port, prefer_ipv6=False):
456          from RNS.Interfaces import netinfo
457          ifaddr = netinfo.ifaddresses(name)
458          if len(ifaddr) < 1:
459              raise SystemError(f"No addresses available on specified kernel interface \"{name}\" for TCPServerInterface to bind to")
460  
461          if (prefer_ipv6 or not netinfo.AF_INET in ifaddr) and netinfo.AF_INET6 in ifaddr:
462              bind_ip = ifaddr[netinfo.AF_INET6][0]["addr"]
463              if bind_ip.lower().startswith("fe80::"):
464                  # We'll need to add the interface as scope for link-local addresses
465                  return TCPServerInterface.get_address_for_host(f"{bind_ip}%{name}", bind_port, prefer_ipv6)
466              else:
467                  return TCPServerInterface.get_address_for_host(bind_ip, bind_port, prefer_ipv6)
468          elif netinfo.AF_INET in ifaddr:
469              bind_ip = ifaddr[netinfo.AF_INET][0]["addr"]
470              return (bind_ip, bind_port)
471          else:
472              raise SystemError(f"No addresses available on specified kernel interface \"{name}\" for TCPServerInterface to bind to")
473  
474      @staticmethod
475      def get_address_for_host(name, bind_port, prefer_ipv6=False):
476          address_infos = socket.getaddrinfo(name, bind_port, proto=socket.IPPROTO_TCP)
477          address_info  = address_infos[0]
478          for entry in address_infos:
479              if prefer_ipv6 and entry[0] == socket.AF_INET6:
480                  address_info = entry; break
481              elif not prefer_ipv6 and entry[0] == socket.AF_INET:
482                  address_info = entry; break
483  
484          if address_info[0] == socket.AF_INET6:
485              return (name, bind_port, address_info[4][2], address_info[4][3])
486          elif address_info[0] == socket.AF_INET:
487              return (name, bind_port)
488          else:
489              raise SystemError(f"No suitable kernel interface available for address \"{name}\" for TCPServerInterface to bind to")
490  
491  
492      @property
493      def clients(self):
494          return len(self.spawned_interfaces)
495  
496      def __init__(self, owner, configuration):
497          super().__init__()
498  
499          c            = Interface.get_config_obj(configuration)
500          name         = c["name"]
501          device       = c["device"] if "device" in c else None
502          port         = int(c["port"]) if "port" in c else None
503          bindip       = c["listen_ip"] if "listen_ip" in c else None
504          bindport     = int(c["listen_port"]) if "listen_port" in c else None
505          i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
506          prefer_ipv6  = c.as_bool("prefer_ipv6") if "prefer_ipv6" in c else False
507  
508          if port != None:
509              bindport = port
510  
511          self.HW_MTU = TCPInterface.HW_MTU
512  
513          self.online = False
514          self.spawned_interfaces = []
515          
516          self.IN  = True
517          self.OUT = False
518          self.name = name
519          self.detached = False
520  
521          self.i2p_tunneled = i2p_tunneled
522          self.mode         = RNS.Interfaces.Interface.Interface.MODE_FULL
523  
524          if bindport == None:
525              raise SystemError(f"No TCP port configured for interface \"{name}\"")
526          else:
527              self.bind_port = bindport
528  
529          bind_address = None
530          if device != None:
531              bind_address = TCPServerInterface.get_address_for_if(device, self.bind_port, prefer_ipv6)
532          else:
533              if bindip == None:
534                  raise SystemError(f"No TCP bind IP configured for interface \"{name}\"")
535              bind_address = TCPServerInterface.get_address_for_host(bindip, self.bind_port, prefer_ipv6)
536  
537          if bind_address != None:
538              self.receives = True
539              self.bind_ip = bind_address[0]
540  
541              def handlerFactory(callback):
542                  def createHandler(*args, **keys):
543                      return TCPInterfaceHandler(callback, *args, **keys)
544                  return createHandler
545  
546              self.owner = owner
547  
548              if len(bind_address) == 4:
549                  try:
550                      ThreadingTCP6Server.allow_reuse_address = True
551                      self.server = ThreadingTCP6Server(bind_address, handlerFactory(self.incoming_connection))
552                  except Exception as e:
553                      RNS.log(f"Error while binding IPv6 socket for interface, the contained exception was: {e}", RNS.LOG_ERROR)
554                      raise SystemError("Could not bind IPv6 socket for interface. Please check the specified \"listen_ip\" configuration option")
555              else:
556                  ThreadingTCPServer.allow_reuse_address = True
557                  self.server = ThreadingTCPServer(bind_address, handlerFactory(self.incoming_connection))
558                  self.server.daemon_threads = True
559  
560              self.bitrate = TCPServerInterface.BITRATE_GUESS
561  
562              thread = threading.Thread(target=self.server.serve_forever)
563              thread.daemon = True
564              thread.start()
565  
566              self.online = True
567  
568          else:
569              raise SystemError("Insufficient parameters to create TCP listener")
570  
571      def incoming_connection(self, handler):
572          RNS.log("Accepting incoming TCP connection", RNS.LOG_VERBOSE)
573          spawned_configuration = {"name": "Client on "+self.name, "target_host": None, "target_port": None, "i2p_tunneled": self.i2p_tunneled}
574          spawned_interface = TCPClientInterface(self.owner, spawned_configuration, connected_socket=handler.request)
575          spawned_interface.OUT = self.OUT
576          spawned_interface.IN  = self.IN
577          spawned_interface.target_ip = handler.client_address[0]
578          spawned_interface.target_port = str(handler.client_address[1])
579          spawned_interface.parent_interface = self
580          spawned_interface.bitrate = self.bitrate
581          spawned_interface.optimise_mtu()
582          
583          spawned_interface.ifac_size = self.ifac_size
584          spawned_interface.ifac_netname = self.ifac_netname
585          spawned_interface.ifac_netkey = self.ifac_netkey
586          if spawned_interface.ifac_netname != None or spawned_interface.ifac_netkey != None:
587              ifac_origin = b""
588              if spawned_interface.ifac_netname != None:
589                  ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netname.encode("utf-8"))
590              if spawned_interface.ifac_netkey != None:
591                  ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netkey.encode("utf-8"))
592  
593              ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
594              spawned_interface.ifac_key = RNS.Cryptography.hkdf(
595                  length=64,
596                  derive_from=ifac_origin_hash,
597                  salt=RNS.Reticulum.IFAC_SALT,
598                  context=None
599              )
600              spawned_interface.ifac_identity = RNS.Identity.from_bytes(spawned_interface.ifac_key)
601              spawned_interface.ifac_signature = spawned_interface.ifac_identity.sign(RNS.Identity.full_hash(spawned_interface.ifac_key))
602  
603          spawned_interface.announce_rate_target = self.announce_rate_target
604          spawned_interface.announce_rate_grace = self.announce_rate_grace
605          spawned_interface.announce_rate_penalty = self.announce_rate_penalty
606          spawned_interface.mode = self.mode
607          spawned_interface.HW_MTU = self.HW_MTU
608          spawned_interface.online = True
609          RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
610          RNS.Transport.interfaces.append(spawned_interface)
611          while spawned_interface in self.spawned_interfaces:
612              self.spawned_interfaces.remove(spawned_interface)
613          self.spawned_interfaces.append(spawned_interface)
614          spawned_interface.read_loop()
615  
616      def received_announce(self, from_spawned=False):
617          if from_spawned: self.ia_freq_deque.append(time.time())
618  
619      def sent_announce(self, from_spawned=False):
620          if from_spawned: self.oa_freq_deque.append(time.time())
621  
622      def process_outgoing(self, data):
623          pass
624  
625      def detach(self):
626          self.detached = True
627          self.online = False
628          if self.server != None:
629              if hasattr(self.server, "shutdown"):
630                  if callable(self.server.shutdown):
631                      try:
632                          RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
633                          self.server.shutdown()
634                          self.server.server_close()
635                          self.server = None
636  
637                      except Exception as e:
638                          RNS.log("Error while shutting down server for "+str(self)+": "+str(e))
639  
640  
641      def __str__(self):
642          if ":" in self.bind_ip:
643              ip_str = f"[{self.bind_ip}]"
644          else:
645              ip_str = f"{self.bind_ip}"
646  
647          return "TCPServerInterface["+self.name+"/"+ip_str+":"+str(self.bind_port)+"]"
648  
649  
650  class TCPInterfaceHandler(socketserver.BaseRequestHandler):
651      def __init__(self, callback, *args, **keys):
652          self.callback = callback
653          socketserver.BaseRequestHandler.__init__(self, *args, **keys)
654  
655      def handle(self):
656          self.callback(handler=self)