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)