mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat: add network.force_ipv4 config to fix IPv6 timeout issues (#8196)
On servers with broken or unreachable IPv6, Python's socket.getaddrinfo returns AAAA records first. urllib/httpx/requests all try IPv6 connections first and hang for the full TCP timeout before falling back to IPv4. This affects web_extract, web_search, the OpenAI SDK, and all HTTP tools. Adds network.force_ipv4 config option (default: false) that monkey-patches socket.getaddrinfo to resolve as AF_INET when the caller didn't specify a family. Falls back to full resolution if no A record exists, so pure-IPv6 hosts still work. Applied early at all three entry points (CLI, gateway, cron scheduler) before any HTTP clients are created. Reported by user @29n — Chinese Ubuntu server with unreachable IPv6 causing timeouts on lobste.rs and other IPv6-enabled sites while Google/GitHub worked fine (IPv4-only resolution).
This commit is contained in:
parent
1cec910b6a
commit
1ca9b19750
6 changed files with 197 additions and 0 deletions
|
|
@ -216,6 +216,51 @@ def get_env_path() -> Path:
|
|||
return get_hermes_home() / ".env"
|
||||
|
||||
|
||||
# ─── Network Preferences ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def apply_ipv4_preference(force: bool = False) -> None:
|
||||
"""Monkey-patch ``socket.getaddrinfo`` to prefer IPv4 connections.
|
||||
|
||||
On servers with broken or unreachable IPv6, Python tries AAAA records
|
||||
first and hangs for the full TCP timeout before falling back to IPv4.
|
||||
This affects httpx, requests, urllib, the OpenAI SDK — everything that
|
||||
uses ``socket.getaddrinfo``.
|
||||
|
||||
When *force* is True, patches ``getaddrinfo`` so that calls with
|
||||
``family=AF_UNSPEC`` (the default) resolve as ``AF_INET`` instead,
|
||||
skipping IPv6 entirely. If no A record exists, falls back to the
|
||||
original unfiltered resolution so pure-IPv6 hosts still work.
|
||||
|
||||
Safe to call multiple times — only patches once.
|
||||
Set ``network.force_ipv4: true`` in ``config.yaml`` to enable.
|
||||
"""
|
||||
if not force:
|
||||
return
|
||||
|
||||
import socket
|
||||
|
||||
# Guard against double-patching
|
||||
if getattr(socket.getaddrinfo, "_hermes_ipv4_patched", False):
|
||||
return
|
||||
|
||||
_original_getaddrinfo = socket.getaddrinfo
|
||||
|
||||
def _ipv4_getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
|
||||
if family == 0: # AF_UNSPEC — caller didn't request a specific family
|
||||
try:
|
||||
return _original_getaddrinfo(
|
||||
host, port, socket.AF_INET, type, proto, flags
|
||||
)
|
||||
except socket.gaierror:
|
||||
# No A record — fall back to full resolution (pure-IPv6 hosts)
|
||||
return _original_getaddrinfo(host, port, family, type, proto, flags)
|
||||
return _original_getaddrinfo(host, port, family, type, proto, flags)
|
||||
|
||||
_ipv4_getaddrinfo._hermes_ipv4_patched = True # type: ignore[attr-defined]
|
||||
socket.getaddrinfo = _ipv4_getaddrinfo # type: ignore[assignment]
|
||||
|
||||
|
||||
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
||||
OPENROUTER_MODELS_URL = f"{OPENROUTER_BASE_URL}/models"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue