From 25fc2cf5f8a2bcd928feb8e24d4196a34d258ae9 Mon Sep 17 00:00:00 2001 From: Nicolas Arenas Date: Wed, 20 May 2026 14:19:08 +0200 Subject: [PATCH] feat(ogagent): select network interface by routing table instead of first found On machines with multiple NICs, the agent was always registering with ogcore using the first interface returned by the OS, which could be the wrong one if it wasn't the interface with a route to ogcore. _best_interface() opens a no-op UDP socket toward the ogcore host and reads the local IP assigned by the kernel routing table, then matches it against the available interfaces. If the routing query fails (network not ready yet) it returns None so the detection loop keeps retrying rather than accepting a wrong interface. Falls back to nets[0] only when the query succeeds but no interface matches (NAT/VPN scenario). --- .../modules/server/OpenGnSys/__init__.py | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 765bb5a..1203eb2 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -34,6 +34,7 @@ import base64 import os import random import shutil +import socket import string import sys import threading @@ -142,6 +143,36 @@ class OpenGnSysWorker(ServerWorker): logger.info('Connection to ogcore restored, re-registering') self._send_started() + def _best_interface(self, nets): + """ + Return the interface whose IP the OS routing table would use to reach + ogcore, by opening a no-op UDP socket (no packets sent) and reading + the local address assigned by the kernel. + + Returns None if the routing query itself fails (network not ready yet), + so the caller keeps retrying. Falls back to nets[0] only when the query + succeeds but no interface matches the routing IP (e.g. NAT or VPN). + """ + try: + host = urllib.parse.urlparse(self.REST.endpoint).hostname + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect((host, 80)) + local_ip = s.getsockname()[0] + except Exception as e: + logger.debug('Routing query to ogcore failed (network not ready?): {}'.format(e)) + return None + + for iface in nets: + if iface.ip == local_ip: + logger.debug('Selected interface {} ({}) for ogcore at {}'.format( + iface.name, local_ip, host)) + return iface + + # Routing succeeded but no interface matched (NAT/VPN): best effort + logger.debug('No interface matched routing IP {}, falling back to {}'.format( + local_ip, nets[0].ip)) + return nets[0] + def _setup_network(self, ca_file, crt_file, key_file): """ Background thread: waits indefinitely for a network interface, then @@ -159,8 +190,8 @@ class OpenGnSysWorker(ServerWorker): try: nets = list(operations.getNetworkInfo()) if nets: - self.interface = nets[0] - if t > 0: + self.interface = self._best_interface(nets) + if self.interface and t > 0: logger.debug("Network interface found after {} seconds".format(t)) else: logger.error('No network interfaces found')