AutoInterface.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 from collections import deque 33 import socketserver 34 import threading 35 import re 36 import socket 37 import struct 38 import time 39 import sys 40 import RNS 41 42 43 class AutoInterface(Interface): 44 HW_MTU = 1196 45 FIXED_MTU = True 46 47 DEFAULT_DISCOVERY_PORT = 29716 48 DEFAULT_DATA_PORT = 42671 49 DEFAULT_GROUP_ID = "reticulum".encode("utf-8") 50 DEFAULT_IFAC_SIZE = 16 51 52 SCOPE_LINK = "2" 53 SCOPE_ADMIN = "4" 54 SCOPE_SITE = "5" 55 SCOPE_ORGANISATION = "8" 56 SCOPE_GLOBAL = "e" 57 58 MULTICAST_PERMANENT_ADDRESS_TYPE = "0" 59 MULTICAST_TEMPORARY_ADDRESS_TYPE = "1" 60 61 PEERING_TIMEOUT = 22.0 62 ANNOUNCE_INTERVAL = 1.6 63 PEER_JOB_INTERVAL = 4.0 64 MCAST_ECHO_TIMEOUT = 6.5 65 66 ALL_IGNORE_IFS = ["lo0"] 67 DARWIN_IGNORE_IFS = ["awdl0", "llw0", "lo0", "en5"] 68 ANDROID_IGNORE_IFS = ["dummy0", "lo", "tun0"] 69 70 BITRATE_GUESS = 10*1000*1000 71 72 MULTI_IF_DEQUE_LEN = 48 73 MULTI_IF_DEQUE_TTL = 0.75 74 75 def handler_factory(self, callback): 76 def create_handler(*args, **keys): 77 return AutoInterfaceHandler(callback, *args, **keys) 78 return create_handler 79 80 def descope_linklocal(self, link_local_addr): 81 # Drop scope specifier expressd as %ifname (macOS) 82 link_local_addr = link_local_addr.split("%")[0] 83 # Drop embedded scope specifier (NetBSD, OpenBSD) 84 link_local_addr = re.sub(r"fe80:[0-9a-f]*::","fe80::", link_local_addr) 85 return link_local_addr 86 87 def list_interfaces(self): 88 ifs = self.netinfo.interfaces() 89 return ifs 90 91 def list_addresses(self, ifname): 92 ifas = self.netinfo.ifaddresses(ifname) 93 return ifas 94 95 def interface_name_to_index(self, ifname): 96 # socket.if_nametoindex doesn't work with uuid interface names on windows, it wants the ethernet_0 style 97 # we will just get the index from netinfo instead as it seems to work 98 if RNS.vendor.platformutils.is_windows(): 99 return self.netinfo.interface_names_to_indexes()[ifname] 100 101 return socket.if_nametoindex(ifname) 102 103 def __init__(self, owner, configuration): 104 c = Interface.get_config_obj(configuration) 105 name = c["name"] 106 group_id = c["group_id"] if "group_id" in c else None 107 discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None 108 discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None 109 multicast_address_type = c["multicast_address_type"] if "multicast_address_type" in c else None 110 data_port = int(c["data_port"]) if "data_port" in c else None 111 allowed_interfaces = c.as_list("devices") if "devices" in c else None 112 ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None 113 configured_bitrate = c["configured_bitrate"] if "configured_bitrate" in c else None 114 115 from RNS.Interfaces import netinfo 116 super().__init__() 117 self.netinfo = netinfo 118 119 self.HW_MTU = AutoInterface.HW_MTU 120 self.IN = True 121 self.OUT = False 122 self.name = name 123 self.owner = owner 124 self.online = False 125 self.final_init_done = False 126 self.peers = {} 127 self.link_local_addresses = [] 128 self.adopted_interfaces = {} 129 self.interface_servers = {} 130 self.multicast_echoes = {} 131 self.initial_echoes = {} 132 self.timed_out_interfaces = {} 133 self.spawned_interfaces = {} 134 self.write_lock = threading.Lock() 135 self.mif_deque = deque(maxlen=AutoInterface.MULTI_IF_DEQUE_LEN) 136 self.mif_deque_times = deque(maxlen=AutoInterface.MULTI_IF_DEQUE_LEN) 137 self.carrier_changed = False 138 139 self.outbound_udp_socket = None 140 141 self.announce_rate_target = None 142 self.announce_interval = AutoInterface.ANNOUNCE_INTERVAL 143 self.peer_job_interval = AutoInterface.PEER_JOB_INTERVAL 144 self.peering_timeout = AutoInterface.PEERING_TIMEOUT 145 self.multicast_echo_timeout = AutoInterface.MCAST_ECHO_TIMEOUT 146 self.reverse_peering_interval = self.announce_interval*3.25 147 148 # Increase peering timeout on Android, due to potential 149 # low-power modes implemented on many chipsets. 150 if RNS.vendor.platformutils.is_android(): 151 self.peering_timeout *= 1.25 152 153 if allowed_interfaces == None: 154 self.allowed_interfaces = [] 155 else: 156 self.allowed_interfaces = allowed_interfaces 157 158 if ignored_interfaces == None: 159 self.ignored_interfaces = [] 160 else: 161 self.ignored_interfaces = ignored_interfaces 162 163 if group_id == None: 164 self.group_id = AutoInterface.DEFAULT_GROUP_ID 165 else: 166 self.group_id = group_id.encode("utf-8") 167 168 if discovery_port == None: 169 self.discovery_port = AutoInterface.DEFAULT_DISCOVERY_PORT 170 else: 171 self.discovery_port = discovery_port 172 173 self.unicast_discovery_port = self.discovery_port+1 174 175 if multicast_address_type == None: 176 self.multicast_address_type = AutoInterface.MULTICAST_TEMPORARY_ADDRESS_TYPE 177 elif str(multicast_address_type).lower() == "temporary": 178 self.multicast_address_type = AutoInterface.MULTICAST_TEMPORARY_ADDRESS_TYPE 179 elif str(multicast_address_type).lower() == "permanent": 180 self.multicast_address_type = AutoInterface.MULTICAST_PERMANENT_ADDRESS_TYPE 181 else: 182 self.multicast_address_type = AutoInterface.MULTICAST_TEMPORARY_ADDRESS_TYPE 183 184 if data_port == None: 185 self.data_port = AutoInterface.DEFAULT_DATA_PORT 186 else: 187 self.data_port = data_port 188 189 if discovery_scope == None: 190 self.discovery_scope = AutoInterface.SCOPE_LINK 191 elif str(discovery_scope).lower() == "link": 192 self.discovery_scope = AutoInterface.SCOPE_LINK 193 elif str(discovery_scope).lower() == "admin": 194 self.discovery_scope = AutoInterface.SCOPE_ADMIN 195 elif str(discovery_scope).lower() == "site": 196 self.discovery_scope = AutoInterface.SCOPE_SITE 197 elif str(discovery_scope).lower() == "organisation": 198 self.discovery_scope = AutoInterface.SCOPE_ORGANISATION 199 elif str(discovery_scope).lower() == "global": 200 self.discovery_scope = AutoInterface.SCOPE_GLOBAL 201 202 self.group_hash = RNS.Identity.full_hash(self.group_id) 203 g = self.group_hash 204 #gt = "{:02x}".format(g[1]+(g[0]<<8)) 205 gt = "0" 206 gt += ":"+"{:02x}".format(g[3]+(g[2]<<8)) 207 gt += ":"+"{:02x}".format(g[5]+(g[4]<<8)) 208 gt += ":"+"{:02x}".format(g[7]+(g[6]<<8)) 209 gt += ":"+"{:02x}".format(g[9]+(g[8]<<8)) 210 gt += ":"+"{:02x}".format(g[11]+(g[10]<<8)) 211 gt += ":"+"{:02x}".format(g[13]+(g[12]<<8)) 212 self.mcast_discovery_address = "ff"+self.multicast_address_type+self.discovery_scope+":"+gt 213 214 suitable_interfaces = 0 215 for ifname in self.list_interfaces(): 216 try: 217 if RNS.vendor.platformutils.is_darwin() and ifname in AutoInterface.DARWIN_IGNORE_IFS and not ifname in self.allowed_interfaces: 218 RNS.log(str(self)+" skipping Darwin AWDL or tethering interface "+str(ifname), RNS.LOG_EXTREME) 219 elif RNS.vendor.platformutils.is_darwin() and ifname == "lo0": 220 RNS.log(str(self)+" skipping Darwin loopback interface "+str(ifname), RNS.LOG_EXTREME) 221 elif RNS.vendor.platformutils.is_android() and ifname in AutoInterface.ANDROID_IGNORE_IFS and not ifname in self.allowed_interfaces: 222 RNS.log(str(self)+" skipping Android system interface "+str(ifname), RNS.LOG_EXTREME) 223 elif ifname in self.ignored_interfaces: 224 RNS.log(str(self)+" ignoring disallowed interface "+str(ifname), RNS.LOG_EXTREME) 225 elif ifname in AutoInterface.ALL_IGNORE_IFS: 226 RNS.log(str(self)+" skipping interface "+str(ifname), RNS.LOG_EXTREME) 227 else: 228 if len(self.allowed_interfaces) > 0 and not ifname in self.allowed_interfaces: 229 RNS.log(str(self)+" ignoring interface "+str(ifname)+" since it was not allowed", RNS.LOG_EXTREME) 230 else: 231 addresses = self.list_addresses(ifname) 232 if self.netinfo.AF_INET6 in addresses: 233 link_local_addr = None 234 for address in addresses[self.netinfo.AF_INET6]: 235 if "addr" in address: 236 if address["addr"].startswith("fe80:"): 237 link_local_addr = self.descope_linklocal(address["addr"]) 238 self.link_local_addresses.append(link_local_addr) 239 self.adopted_interfaces[ifname] = link_local_addr 240 self.multicast_echoes[ifname] = time.time() 241 nice_name = self.netinfo.interface_name_to_nice_name(ifname) 242 if nice_name != None and nice_name != ifname: 243 RNS.log(f"{self} Selecting link-local address {link_local_addr} for interface {nice_name} / {ifname}", RNS.LOG_EXTREME) 244 else: 245 RNS.log(f"{self} Selecting link-local address {link_local_addr} for interface {ifname}", RNS.LOG_EXTREME) 246 247 if link_local_addr == None: 248 RNS.log(str(self)+" No link-local IPv6 address configured for "+str(ifname)+", skipping interface", RNS.LOG_EXTREME) 249 else: 250 RNS.log(str(self)+" Creating unicast discovery listener on "+str(ifname)+" with address "+str(link_local_addr), RNS.LOG_EXTREME) 251 252 # Struct with interface index 253 if_struct = struct.pack("I", self.interface_name_to_index(ifname)) 254 255 # Set up unicast discovery socket 256 unicast_discovery_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 257 unicast_discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 258 if hasattr(socket, "SO_REUSEPORT"): unicast_discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 259 260 # Bind unicast discovery socket 261 if RNS.vendor.platformutils.is_windows(): 262 # Windows throws "[WinError 10049] The requested address is not valid in its context" 263 # when trying to use the multicast address as host, or when providing interface index 264 # passing an empty host appears to work, but probably not exactly how we want it to... 265 unicast_discovery_socket.bind(('', self.unicast_discovery_port)) 266 267 else: 268 addr_info = socket.getaddrinfo(link_local_addr+"%"+ifname, self.unicast_discovery_port, socket.AF_INET6, socket.SOCK_DGRAM) 269 unicast_discovery_socket.bind(addr_info[0][4]) 270 271 mcast_addr = self.mcast_discovery_address 272 RNS.log(str(self)+" Creating multicast discovery listener on "+str(ifname)+" with address "+str(mcast_addr), RNS.LOG_EXTREME) 273 274 # Set up multicast discovery socket 275 discovery_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 276 discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 277 if hasattr(socket, "SO_REUSEPORT"): discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 278 discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, if_struct) 279 280 # Join multicast group 281 mcast_group = socket.inet_pton(socket.AF_INET6, mcast_addr) + if_struct 282 discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mcast_group) 283 284 # Bind multicast socket 285 if RNS.vendor.platformutils.is_windows(): 286 # Windows throws "[WinError 10049] The requested address is not valid in its context" 287 # when trying to use the multicast address as host, or when providing interface index 288 # passing an empty host appears to work, but probably not exactly how we want it to... 289 discovery_socket.bind(('', self.discovery_port)) 290 291 else: 292 if self.discovery_scope == AutoInterface.SCOPE_LINK: 293 addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM) 294 else: 295 addr_info = socket.getaddrinfo(mcast_addr, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM) 296 297 discovery_socket.bind(addr_info[0][4]) 298 299 # Set up thread for multicast discovery packets 300 def discovery_loop(): self.discovery_handler(discovery_socket, ifname) 301 thread = threading.Thread(target=discovery_loop, daemon=True).start() 302 303 # Set up thread for unicast discovery packets 304 def unicast_discovery_loop(): self.discovery_handler(unicast_discovery_socket, ifname, announce=False) 305 thread = threading.Thread(target=unicast_discovery_loop, daemon=True).start() 306 307 suitable_interfaces += 1 308 309 except Exception as e: 310 nice_name = self.netinfo.interface_name_to_nice_name(ifname) 311 if nice_name != None and nice_name != ifname: 312 RNS.log(f"Could not configure the system interface {nice_name} / {ifname} for use with {self}, skipping it. The contained exception was: {e}", RNS.LOG_ERROR) 313 else: 314 RNS.log(f"Could not configure the system interface {ifname} for use with {self}, skipping it. The contained exception was: {e}", RNS.LOG_ERROR) 315 316 if suitable_interfaces == 0: 317 RNS.log(str(self)+" could not autoconfigure. This interface currently provides no connectivity.", RNS.LOG_WARNING) 318 else: 319 self.receives = True 320 321 if configured_bitrate != None: 322 self.bitrate = configured_bitrate 323 else: 324 self.bitrate = AutoInterface.BITRATE_GUESS 325 326 def final_init(self): 327 peering_wait = self.announce_interval*1.2 328 RNS.log(str(self)+" discovering peers for "+str(round(peering_wait, 2))+" seconds...", RNS.LOG_VERBOSE) 329 330 socketserver.UDPServer.address_family = socket.AF_INET6 331 332 for ifname in self.adopted_interfaces: 333 local_addr = self.adopted_interfaces[ifname]+"%"+str(self.interface_name_to_index(ifname)) 334 addr_info = socket.getaddrinfo(local_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM) 335 address = addr_info[0][4] 336 337 udp_server = socketserver.UDPServer(address, self.handler_factory(self.process_incoming)) 338 self.interface_servers[ifname] = udp_server 339 340 thread = threading.Thread(target=udp_server.serve_forever) 341 thread.daemon = True 342 thread.start() 343 344 job_thread = threading.Thread(target=self.peer_jobs) 345 job_thread.daemon = True 346 job_thread.start() 347 348 time.sleep(peering_wait) 349 350 self.online = True 351 self.final_init_done = True 352 353 def discovery_handler(self, socket, ifname, announce=True): 354 def announce_loop(): self.announce_handler(ifname) 355 356 if announce: 357 thread = threading.Thread(target=announce_loop) 358 thread.daemon = True 359 thread.start() 360 361 while True: 362 data, ipv6_src = socket.recvfrom(1024) 363 if self.final_init_done: 364 peering_hash = data[:RNS.Identity.HASHLENGTH//8] 365 expected_hash = RNS.Identity.full_hash(self.group_id+ipv6_src[0].encode("utf-8")) 366 if peering_hash == expected_hash: 367 self.add_peer(ipv6_src[0], ifname) 368 else: 369 RNS.log(str(self)+" received peering packet on "+str(ifname)+" from "+str(ipv6_src[0])+", but authentication hash was incorrect.", RNS.LOG_DEBUG) 370 371 def peer_jobs(self): 372 while True: 373 time.sleep(self.peer_job_interval) 374 now = time.time() 375 timed_out_peers = [] 376 377 # Check for timed out peers 378 for peer_addr in self.peers: 379 peer = self.peers[peer_addr] 380 last_heard = peer[1] 381 if now > last_heard+self.peering_timeout: 382 timed_out_peers.append(peer_addr) 383 384 # Remove any timed out peers 385 for peer_addr in timed_out_peers: 386 removed_peer = self.peers.pop(peer_addr) 387 if peer_addr in self.spawned_interfaces: 388 spawned_interface = self.spawned_interfaces[peer_addr] 389 spawned_interface.detach() 390 spawned_interface.teardown() 391 RNS.log(str(self)+" removed peer "+str(peer_addr)+" on "+str(removed_peer[0]), RNS.LOG_DEBUG) 392 393 # Send reverse peering packets 394 for peer_addr in self.peers: 395 try: 396 peer = self.peers[peer_addr] 397 ifname = peer[0] 398 last_outbound = peer[2] 399 if now > last_outbound+self.reverse_peering_interval: 400 self.reverse_announce(ifname, peer_addr) 401 peer[2] = time.time() 402 except Exception as e: 403 RNS.log(f"Error while sending reverse peering packet to {peer_addr}: {e}", RNS.LOG_ERROR) 404 405 for ifname in self.adopted_interfaces: 406 # Check that the link-local address has not changed 407 try: 408 addresses = self.list_addresses(ifname) 409 if self.netinfo.AF_INET6 in addresses: 410 link_local_addr = None 411 for address in addresses[self.netinfo.AF_INET6]: 412 if "addr" in address: 413 if address["addr"].startswith("fe80:"): 414 link_local_addr = self.descope_linklocal(address["addr"]) 415 if link_local_addr != self.adopted_interfaces[ifname]: 416 old_link_local_address = self.adopted_interfaces[ifname] 417 RNS.log("Replacing link-local address "+str(old_link_local_address)+" for "+str(ifname)+" with "+str(link_local_addr), RNS.LOG_DEBUG) 418 self.adopted_interfaces[ifname] = link_local_addr 419 self.link_local_addresses.append(link_local_addr) 420 421 if old_link_local_address in self.link_local_addresses: 422 self.link_local_addresses.remove(old_link_local_address) 423 424 local_addr = link_local_addr+"%"+ifname 425 addr_info = socket.getaddrinfo(local_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM) 426 listen_address = addr_info[0][4] 427 428 if ifname in self.interface_servers: 429 RNS.log("Shutting down previous UDP listener for "+str(self)+" "+str(ifname), RNS.LOG_DEBUG) 430 previous_server = self.interface_servers[ifname] 431 def shutdown_server(): 432 previous_server.shutdown() 433 threading.Thread(target=shutdown_server, daemon=True).start() 434 435 RNS.log("Starting new UDP listener for "+str(self)+" "+str(ifname), RNS.LOG_DEBUG) 436 437 udp_server = socketserver.UDPServer(listen_address, self.handler_factory(self.process_incoming)) 438 self.interface_servers[ifname] = udp_server 439 440 thread = threading.Thread(target=udp_server.serve_forever) 441 thread.daemon = True 442 thread.start() 443 444 self.carrier_changed = True 445 446 except Exception as e: 447 RNS.log("Could not get device information while updating link-local addresses for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR) 448 449 # Check multicast echo timeouts 450 last_multicast_echo = 0 451 multicast_echo_received = False 452 if ifname in self.multicast_echoes: last_multicast_echo = self.multicast_echoes[ifname] 453 if ifname in self.initial_echoes: multicast_echo_received = True 454 455 if now - last_multicast_echo > self.multicast_echo_timeout: 456 if ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == False: 457 self.carrier_changed = True 458 RNS.log("Multicast echo timeout for "+str(ifname)+". Carrier lost.", RNS.LOG_WARNING) 459 self.timed_out_interfaces[ifname] = True 460 else: 461 if ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == True: 462 self.carrier_changed = True 463 RNS.log(str(self)+" Carrier recovered on "+str(ifname), RNS.LOG_WARNING) 464 self.timed_out_interfaces[ifname] = False 465 466 if not multicast_echo_received: 467 RNS.log(f"{self} No multicast echoes received on {ifname}. The networking hardware or a firewall may be blocking multicast traffic.", RNS.LOG_ERROR) 468 # else: 469 # RNS.log(f"{self} Initial multicast echo on {ifname} received {RNS.prettytime(time.time()-self.initial_echoes[ifname])} ago.", RNS.LOG_DEBUG) 470 471 472 def announce_handler(self, ifname): 473 while True: 474 self.peer_announce(ifname) 475 time.sleep(self.announce_interval) 476 477 def reverse_announce(self, ifname, peer_addr): 478 try: 479 link_local_address = self.adopted_interfaces[ifname] 480 discovery_token = RNS.Identity.full_hash(self.group_id+link_local_address.encode("utf-8")) 481 announce_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 482 addr_info = socket.getaddrinfo(f"{peer_addr}%{ifname}", self.unicast_discovery_port, socket.AF_INET6, socket.SOCK_DGRAM) 483 484 ifis = struct.pack("I", self.interface_name_to_index(ifname)) 485 announce_socket.sendto(discovery_token, addr_info[0][4]) 486 announce_socket.close() 487 488 except Exception as e: 489 RNS.log(f"Could not send reverse peering packet to {peer_addr} on {ifname}: {e}", RNS.LOG_ERROR) 490 491 def peer_announce(self, ifname): 492 try: 493 link_local_address = self.adopted_interfaces[ifname] 494 discovery_token = RNS.Identity.full_hash(self.group_id+link_local_address.encode("utf-8")) 495 announce_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 496 addr_info = socket.getaddrinfo(self.mcast_discovery_address, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM) 497 498 ifis = struct.pack("I", self.interface_name_to_index(ifname)) 499 announce_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, ifis) 500 announce_socket.sendto(discovery_token, addr_info[0][4]) 501 announce_socket.close() 502 503 except Exception as e: 504 if (ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == False) or not ifname in self.timed_out_interfaces: 505 RNS.log(str(self)+" Detected possible carrier loss on "+str(ifname)+": "+str(e), RNS.LOG_WARNING) 506 else: 507 pass 508 509 @property 510 def peer_count(self): 511 return len(self.spawned_interfaces) 512 513 def add_peer(self, addr, ifname): 514 if addr in self.link_local_addresses: 515 ifname = None 516 for interface_name in self.adopted_interfaces: 517 if self.adopted_interfaces[interface_name] == addr: 518 ifname = interface_name 519 520 if ifname != None: 521 self.multicast_echoes[ifname] = time.time() 522 if not ifname in self.initial_echoes: self.initial_echoes[ifname] = time.time() 523 else: 524 RNS.log(str(self)+" received multicast echo on unexpected interface "+str(ifname), RNS.LOG_WARNING) 525 526 else: 527 if not addr in self.peers: 528 self.peers[addr] = [ifname, time.time(), time.time()] 529 530 spawned_interface = AutoInterfacePeer(self, addr, ifname) 531 spawned_interface.OUT = self.OUT 532 spawned_interface.IN = self.IN 533 spawned_interface.parent_interface = self 534 spawned_interface.bitrate = self.bitrate 535 536 spawned_interface.ifac_size = self.ifac_size 537 spawned_interface.ifac_netname = self.ifac_netname 538 spawned_interface.ifac_netkey = self.ifac_netkey 539 if spawned_interface.ifac_netname != None or spawned_interface.ifac_netkey != None: 540 ifac_origin = b"" 541 if spawned_interface.ifac_netname != None: 542 ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netname.encode("utf-8")) 543 if spawned_interface.ifac_netkey != None: 544 ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netkey.encode("utf-8")) 545 546 ifac_origin_hash = RNS.Identity.full_hash(ifac_origin) 547 spawned_interface.ifac_key = RNS.Cryptography.hkdf( 548 length=64, 549 derive_from=ifac_origin_hash, 550 salt=RNS.Reticulum.IFAC_SALT, 551 context=None 552 ) 553 spawned_interface.ifac_identity = RNS.Identity.from_bytes(spawned_interface.ifac_key) 554 spawned_interface.ifac_signature = spawned_interface.ifac_identity.sign(RNS.Identity.full_hash(spawned_interface.ifac_key)) 555 556 spawned_interface.announce_rate_target = self.announce_rate_target 557 spawned_interface.announce_rate_grace = self.announce_rate_grace 558 spawned_interface.announce_rate_penalty = self.announce_rate_penalty 559 spawned_interface.mode = self.mode 560 spawned_interface.HW_MTU = self.HW_MTU 561 spawned_interface.online = True 562 RNS.Transport.interfaces.append(spawned_interface) 563 if addr in self.spawned_interfaces: 564 self.spawned_interfaces[addr].detach() 565 self.spawned_interfaces[addr].teardown() 566 if addr in self.spawned_interfaces: self.spawned_interfaces.pop(addr) 567 self.spawned_interfaces[addr] = spawned_interface 568 569 RNS.log(str(self)+" added peer "+str(addr)+" on "+str(ifname), RNS.LOG_DEBUG) 570 else: 571 self.refresh_peer(addr) 572 573 def refresh_peer(self, addr): 574 try: self.peers[addr][1] = time.time() 575 except Exception as e: RNS.log(f"An error occurred while refreshing peer {addr} on {self}: {e}", RNS.LOG_ERROR) 576 577 def process_incoming(self, data, addr=None): 578 if self.online and addr in self.spawned_interfaces: 579 self.spawned_interfaces[addr].process_incoming(data, addr) 580 581 def process_outgoing(self, data): pass 582 583 def detach(self): self.online = False 584 585 def __str__(self): return f"AutoInterface[{self.name}]" 586 587 class AutoInterfacePeer(Interface): 588 589 def __init__(self, owner, addr, ifname): 590 super().__init__() 591 self.owner = owner 592 self.parent_interface = owner 593 self.addr = addr 594 self.ifname = ifname 595 self.peer_addr = None 596 self.addr_info = None 597 self.HW_MTU = self.owner.HW_MTU 598 self.FIXED_MTU = self.owner.FIXED_MTU 599 600 def __str__(self): 601 return f"AutoInterfacePeer[{self.ifname}/{self.addr}]" 602 603 def process_incoming(self, data, addr=None): 604 if self.online and self.owner.online: 605 data_hash = RNS.Identity.full_hash(data) 606 deque_hit = False 607 if data_hash in self.owner.mif_deque: 608 for te in self.owner.mif_deque_times: 609 if te[0] == data_hash and time.time() < te[1]+AutoInterface.MULTI_IF_DEQUE_TTL: 610 deque_hit = True 611 break 612 613 if not deque_hit: 614 self.owner.refresh_peer(self.addr) 615 self.owner.mif_deque.append(data_hash) 616 self.owner.mif_deque_times.append([data_hash, time.time()]) 617 self.rxb += len(data) 618 self.owner.rxb += len(data) 619 self.owner.owner.inbound(data, self) 620 621 def process_outgoing(self, data): 622 if self.online: 623 with self.owner.write_lock: 624 try: 625 if self.owner.outbound_udp_socket == None: self.owner.outbound_udp_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 626 if self.peer_addr == None: self.peer_addr = str(self.addr)+"%"+str(self.owner.interface_name_to_index(self.ifname)) 627 if self.addr_info == None: self.addr_info = socket.getaddrinfo(self.peer_addr, self.owner.data_port, socket.AF_INET6, socket.SOCK_DGRAM) 628 self.owner.outbound_udp_socket.sendto(data, self.addr_info[0][4]) 629 self.txb += len(data) 630 self.owner.txb += len(data) 631 except Exception as e: 632 RNS.log("Could not transmit on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR) 633 634 def detach(self): 635 self.online = False 636 self.detached = True 637 638 def teardown(self): 639 if not self.detached: 640 RNS.log(f"The interface {self} experienced an unrecoverable error and is being torn down.", RNS.LOG_ERROR) 641 if RNS.Reticulum.panic_on_interface_error: RNS.panic() 642 643 else: RNS.log(f"The interface {self} is being torn down.", RNS.LOG_VERBOSE) 644 645 self.online = False 646 self.OUT = False 647 self.IN = False 648 649 if self.addr in self.owner.spawned_interfaces: 650 try: self.owner.spawned_interfaces.pop(self.addr) 651 except Exception as e: 652 RNS.log(f"Could not remove {self} from parent interface on detach. The contained exception was: {e}", RNS.LOG_ERROR) 653 654 if self in RNS.Transport.interfaces: RNS.Transport.interfaces.remove(self) 655 656 class AutoInterfaceHandler(socketserver.BaseRequestHandler): 657 def __init__(self, callback, *args, **keys): 658 self.callback = callback 659 socketserver.BaseRequestHandler.__init__(self, *args, **keys) 660 661 def handle(self): 662 data = self.request[0] 663 addr = self.client_address[0] 664 self.callback(data, addr)