feat(ogagent): select network interface by routing table instead of first found
ogagent/pipeline/head This commit looks good Details

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).
pull/76/head
Nicolas Arenas 2026-05-20 14:19:08 +02:00
parent a222528d00
commit 25fc2cf5f8
1 changed files with 33 additions and 2 deletions

View File

@ -34,6 +34,7 @@ import base64
import os import os
import random import random
import shutil import shutil
import socket
import string import string
import sys import sys
import threading import threading
@ -142,6 +143,36 @@ class OpenGnSysWorker(ServerWorker):
logger.info('Connection to ogcore restored, re-registering') logger.info('Connection to ogcore restored, re-registering')
self._send_started() 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): def _setup_network(self, ca_file, crt_file, key_file):
""" """
Background thread: waits indefinitely for a network interface, then Background thread: waits indefinitely for a network interface, then
@ -159,8 +190,8 @@ class OpenGnSysWorker(ServerWorker):
try: try:
nets = list(operations.getNetworkInfo()) nets = list(operations.getNetworkInfo())
if nets: if nets:
self.interface = nets[0] self.interface = self._best_interface(nets)
if t > 0: if self.interface and t > 0:
logger.debug("Network interface found after {} seconds".format(t)) logger.debug("Network interface found after {} seconds".format(t))
else: else:
logger.error('No network interfaces found') logger.error('No network interfaces found')