Run all BackboneInterface I/O on single epoll instance

This commit is contained in:
Mark Qvist 2025-04-06 18:17:37 +02:00
parent 456eea9c13
commit de3438248f

View file

@ -42,10 +42,17 @@ class HDLC():
class BackboneInterface(Interface): class BackboneInterface(Interface):
HW_MTU = 1048576 HW_MTU = 1048576
BITRATE_GUESS = 100_000_000 BITRATE_GUESS = 1_000_000_000
DEFAULT_IFAC_SIZE = 16 DEFAULT_IFAC_SIZE = 16
AUTOCONFIGURE_MTU = True AUTOCONFIGURE_MTU = True
listener_filenos = {}
spawned_interfaces = []
spawned_interface_filenos = {}
epoll = None
_job_active = False
_job_lock = threading.Lock()
@staticmethod @staticmethod
def get_address_for_if(name, bind_port, prefer_ipv6=False): def get_address_for_if(name, bind_port, prefer_ipv6=False):
import RNS.vendor.ifaddr.niwrapper as netinfo import RNS.vendor.ifaddr.niwrapper as netinfo
@ -95,20 +102,14 @@ class BackboneInterface(Interface):
bindport = int(c["listen_port"]) if "listen_port" in c else None bindport = int(c["listen_port"]) if "listen_port" in c else None
prefer_ipv6 = c.as_bool("prefer_ipv6") if "prefer_ipv6" in c else False prefer_ipv6 = c.as_bool("prefer_ipv6") if "prefer_ipv6" in c else False
if port != None: if port != None: bindport = port
bindport = port
self.HW_MTU = BackboneInterface.HW_MTU self.HW_MTU = BackboneInterface.HW_MTU
self.online = False self.online = False
self.listeners = []
self.spawned_interfaces = []
self.IN = True self.IN = True
self.OUT = False self.OUT = False
self.name = name self.name = name
self.detached = False self.detached = False
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if bindport == None: if bindport == None:
@ -129,16 +130,7 @@ class BackboneInterface(Interface):
self.bind_ip = bind_address[0] self.bind_ip = bind_address[0]
self.owner = owner self.owner = owner
# if len(bind_address) == 4: if not BackboneInterface.epoll: BackboneInterface.epoll = select.epoll()
# try:
# ThreadingTCP6Server.allow_reuse_address = True
# self.server = ThreadingTCP6Server(bind_address, handlerFactory(self.incoming_connection))
# except Exception as e:
# RNS.log(f"Error while binding IPv6 socket for interface, the contained exception was: {e}", RNS.LOG_ERROR)
# raise SystemError("Could not bind IPv6 socket for interface. Please check the specified \"listen_ip\" configuration option")
# else:
self.epoll = select.epoll()
self.add_listener(bind_address) self.add_listener(bind_address)
self.bitrate = self.BITRATE_GUESS self.bitrate = self.BITRATE_GUESS
@ -150,89 +142,95 @@ class BackboneInterface(Interface):
def start(self): def start(self):
RNS.log(f"Starting {self}") RNS.log(f"Starting {self}")
threading.Thread(target=self.__job, daemon=True).start() if not BackboneInterface._job_active: threading.Thread(target=self.__job, daemon=True).start()
def __job(self): @staticmethod
def __job():
with BackboneInterface._job_lock:
if BackboneInterface._job_active: return
else:
BackboneInterface._job_active = True
try: try:
while True: while True:
events = self.epoll.poll(1) events = BackboneInterface.epoll.poll(1)
for fileno, event in BackboneInterface.epoll.poll(1):
for spawned_interface in self.spawned_interfaces: if fileno in BackboneInterface.spawned_interface_filenos:
clientsocket = spawned_interface.socket spawned_interface = BackboneInterface.spawned_interface_filenos[fileno]
for fileno, event in events: client_socket = spawned_interface.socket
if fileno == clientsocket.fileno() and (event & select.EPOLLIN): if fileno == client_socket.fileno() and (event & select.EPOLLIN):
try: try: received_bytes = client_socket.recv(4096)
inb = clientsocket.recv(4096)
except Exception as e: except Exception as e:
RNS.log(f"Error while reading from {spawned_interface}: {e}", RNS.LOG_ERROR) RNS.log(f"Error while reading from {spawned_interface}: {e}", RNS.LOG_ERROR)
inb = b"" received_bytes = b""
if len(inb): if len(received_bytes): spawned_interface.receive(received_bytes)
spawned_interface.receive(inb)
else: else:
self.epoll.unregister(fileno) BackboneInterface.epoll.unregister(fileno); client_socket.close()
clientsocket.close() spawned_interface.receive(received_bytes)
spawned_interface.receive(inb)
elif fileno == clientsocket.fileno() and (event & select.EPOLLOUT): elif fileno == client_socket.fileno() and (event & select.EPOLLOUT):
try: try:
written = clientsocket.send(spawned_interface.transmit_buffer) written = client_socket.send(spawned_interface.transmit_buffer)
except Exception as e: except Exception as e:
RNS.log(f"Error while writing to {spawned_interface}: {e}", RNS.LOG_ERROR) RNS.log(f"Error while writing to {spawned_interface}: {e}", RNS.LOG_ERROR)
written = 0 written = 0
BackboneInterface.epoll.unregister(fileno)
spawned_interface.transmit_buffer = spawned_interface.transmit_buffer[written:] try: client_socket.close()
if len(spawned_interface.transmit_buffer) == 0: self.epoll.modify(fileno, select.EPOLLIN) except Exception as e: RNS.log(f"Error while closing socket for {spawned_interface}: {e}", RNS.LOG_ERROR)
self.txb += written; spawned_interface.txb += written
elif fileno == clientsocket.fileno() and event & (select.EPOLLHUP):
self.epoll.unregister(fileno)
try: clientsocket.close()
except Exception as e:
RNS.log(f"Error while closing socket for {spawned_interface}: {e}", RNS.LOG_ERROR)
spawned_interface.receive(b"") spawned_interface.receive(b"")
for serversocket in self.listeners: spawned_interface.transmit_buffer = spawned_interface.transmit_buffer[written:]
for fileno, event in events: if len(spawned_interface.transmit_buffer) == 0: BackboneInterface.epoll.modify(fileno, select.EPOLLIN)
if fileno == serversocket.fileno(): RNS.log(f"Listener {serversocket}, fd {fileno}, event {event}") spawned_interface.parent_interface.txb += written; spawned_interface.txb += written
if fileno == serversocket.fileno() and (event & select.EPOLLIN):
connection, address = serversocket.accept() elif fileno == client_socket.fileno() and event & (select.EPOLLHUP):
connection.setblocking(0) BackboneInterface.epoll.unregister(fileno)
if self.incoming_connection(connection): try: client_socket.close()
self.epoll.register(connection.fileno(), select.EPOLLIN) except Exception as e: RNS.log(f"Error while closing socket for {spawned_interface}: {e}", RNS.LOG_ERROR)
spawned_interface.receive(b"")
elif fileno in BackboneInterface.listener_filenos:
owner_interface, server_socket = BackboneInterface.listener_filenos[fileno]
if fileno == server_socket.fileno() and (event & select.EPOLLIN):
client_socket, address = server_socket.accept()
client_socket.setblocking(0)
if owner_interface.incoming_connection(client_socket):
BackboneInterface.epoll.register(client_socket.fileno(), select.EPOLLIN)
else: else:
connection.close() client_socket.close()
elif fileno == serversocket.fileno() and (event & select.EPOLLHUP): elif fileno == server_socket.fileno() and (event & select.EPOLLHUP):
try: self.epoll.unregister(fileno) try: BackboneInterface.epoll.unregister(fileno)
except Exception as e: except Exception as e: RNS.log(f"Error while deregistering listener file descriptor {fileno}: {e}", RNS.LOG_ERROR)
RNS.log(f"Error while unregistering file descriptor {fileno}: {e}", RNS.LOG_ERROR)
try: serversocket.close() try: server_socket.close()
except Exception as e: except Exception as e: RNS.log(f"Error while closing listener socket for {server_socket}: {e}", RNS.LOG_ERROR)
RNS.log(f"Error while closing socket for {serversocket}: {e}", RNS.LOG_ERROR)
except Exception as e: except Exception as e:
RNS.log(f"{self} error: {e}", RNS.LOG_ERROR) RNS.log(f"BackboneInterface error: {e}", RNS.LOG_ERROR)
RNS.trace_exception(e) RNS.trace_exception(e)
finally: finally:
for serversocket in self.listeners: for owner_interface, serversocket in BackboneInterface.listener_filenos:
self.epoll.unregister(serversocket.fileno()) fileno = serversocket.fileno()
BackboneInterface.epoll.unregister(fileno)
serversocket.close() serversocket.close()
self.epoll.close() BackboneInterface.listener_filenos.clear()
BackboneInterface.epoll.close()
def add_listener(self, bind_address): def add_listener(self, bind_address, socket_type=socket.AF_INET):
if socket_type == socket.AF_INET:
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(bind_address) server_socket.bind(bind_address)
else: raise TypeError(f"Invalid socket type {socket_type} for {self}")
server_socket.listen(1) server_socket.listen(1)
server_socket.setblocking(0) server_socket.setblocking(0)
self.epoll.register(server_socket.fileno(), select.EPOLLIN) BackboneInterface.listener_filenos[server_socket.fileno()] = (self, server_socket)
self.listeners.append(server_socket) BackboneInterface.epoll.register(server_socket.fileno(), select.EPOLLIN)
RNS.log(f"Listener added: {server_socket}") RNS.log(f"{self} listener added: {server_socket}", RNS.LOG_DEBUG) # TODO: Remove debug
def incoming_connection(self, socket): def incoming_connection(self, socket):
RNS.log("Accepting incoming connection", RNS.LOG_VERBOSE) RNS.log("Accepting incoming connection", RNS.LOG_VERBOSE)
@ -275,9 +273,9 @@ class BackboneInterface(Interface):
spawned_interface.online = True spawned_interface.online = True
RNS.log("Spawned new BackBoneClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE) RNS.log("Spawned new BackBoneClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface) RNS.Transport.interfaces.append(spawned_interface)
while spawned_interface in self.spawned_interfaces: while spawned_interface in self.spawned_interfaces: self.spawned_interfaces.remove(spawned_interface)
self.spawned_interfaces.remove(spawned_interface)
self.spawned_interfaces.append(spawned_interface) self.spawned_interfaces.append(spawned_interface)
self.spawned_interface_filenos[socket.fileno()] = spawned_interface
return True return True
@ -293,17 +291,14 @@ class BackboneInterface(Interface):
def detach(self): def detach(self):
self.detached = True self.detached = True
self.online = False self.online = False
for listener_socket in self.listeners: detached = []
for fileno in BackboneInterface.listener_filenos:
owner_interface, listener_socket = BackboneInterface.listener_filenos[fileno]
if owner_interface == self:
if hasattr(listener_socket, "shutdown"): if hasattr(listener_socket, "shutdown"):
if callable(listener_socket.shutdown): if callable(listener_socket.shutdown):
try: try: listener_socket.shutdown(socket.SHUT_RDWR)
# RNS.log("Detaching "+str(self), RNS.LOG_DEBUG) except Exception as e: RNS.log("Error while shutting down server for "+str(self)+": "+str(e))
listener_socket.shutdown(socket.SHUT_RDWR)
except Exception as e:
RNS.log("Error while shutting down server for "+str(self)+": "+str(e))
while len(self.listeners): self.listeners.pop()
def __str__(self): def __str__(self):
if ":" in self.bind_ip: if ":" in self.bind_ip: