mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
fix(gateway): keep DoH-confirmed Telegram IPs that match system DNS (#14520)
discover_fallback_ips() filtered out any DoH-resolved IP that also appeared in the system resolver's answer set, on the assumption that the system IP was unreachable. When DoH and system DNS agreed (a common case), the function returned the hardcoded _SEED_FALLBACK_IPS list instead — and on networks where those seed addresses are not routable, the Telegram fallback transport had nothing usable to retry against and polling failed. Drop the system_ips exclusion so DoH-confirmed IPs are preserved regardless of system DNS overlap. The TelegramFallbackTransport already tries the primary path first via system DNS, then falls through to the IP-rewrite path on connect failure; including the same IP in both lanes lets a transient primary failure recover via the explicit IP route instead of escalating to seed addresses. Update the two tests that codified the old exclusion to reflect the new, inclusion-by-default behaviour. Fixes #14520
This commit is contained in:
parent
aacf36e943
commit
f6b68f0f50
2 changed files with 27 additions and 13 deletions
|
|
@ -185,10 +185,13 @@ async def _query_doh_provider(
|
||||||
async def discover_fallback_ips() -> list[str]:
|
async def discover_fallback_ips() -> list[str]:
|
||||||
"""Auto-discover Telegram API IPs via DNS-over-HTTPS.
|
"""Auto-discover Telegram API IPs via DNS-over-HTTPS.
|
||||||
|
|
||||||
Resolves api.telegram.org through Google and Cloudflare DoH, collects all
|
Resolves api.telegram.org through Google and Cloudflare DoH and returns all
|
||||||
unique IPs, and excludes the system-DNS-resolved IP (which is presumably
|
unique A records. IPs that match the local system resolver are kept rather
|
||||||
unreachable on this network). Falls back to a hardcoded seed list when DoH
|
than excluded: in many networks the system-DNS IP is the most reliable path
|
||||||
is also unavailable.
|
to api.telegram.org and a transient primary-path failure should be retried
|
||||||
|
against the same address via the IP-rewrite path before the seed list is
|
||||||
|
consulted (#14520). Falls back to a hardcoded seed list only when DoH
|
||||||
|
yields no usable answers.
|
||||||
"""
|
"""
|
||||||
async with httpx.AsyncClient(timeout=httpx.Timeout(_DOH_TIMEOUT)) as client:
|
async with httpx.AsyncClient(timeout=httpx.Timeout(_DOH_TIMEOUT)) as client:
|
||||||
doh_tasks = [_query_doh_provider(client, p) for p in _DOH_PROVIDERS]
|
doh_tasks = [_query_doh_provider(client, p) for p in _DOH_PROVIDERS]
|
||||||
|
|
@ -203,11 +206,11 @@ async def discover_fallback_ips() -> list[str]:
|
||||||
if isinstance(r, list):
|
if isinstance(r, list):
|
||||||
doh_ips.extend(r)
|
doh_ips.extend(r)
|
||||||
|
|
||||||
# Deduplicate preserving order, exclude system-DNS IPs
|
# Deduplicate preserving order
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
candidates: list[str] = []
|
candidates: list[str] = []
|
||||||
for ip in doh_ips:
|
for ip in doh_ips:
|
||||||
if ip not in seen and ip not in system_ips:
|
if ip not in seen:
|
||||||
seen.add(ip)
|
seen.add(ip)
|
||||||
candidates.append(ip)
|
candidates.append(ip)
|
||||||
|
|
||||||
|
|
@ -219,7 +222,7 @@ async def discover_fallback_ips() -> list[str]:
|
||||||
return validated
|
return validated
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"DoH discovery yielded no new IPs (system DNS: %s); using seed fallback IPs %s",
|
"DoH discovery yielded no usable IPs (system DNS: %s); using seed fallback IPs %s",
|
||||||
", ".join(system_ips) or "unknown",
|
", ".join(system_ips) or "unknown",
|
||||||
", ".join(_SEED_FALLBACK_IPS),
|
", ".join(_SEED_FALLBACK_IPS),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -534,15 +534,20 @@ class TestDiscoverFallbackIps:
|
||||||
assert "149.154.167.221" in ips
|
assert "149.154.167.221" in ips
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_system_dns_ip_excluded(self, monkeypatch):
|
async def test_system_dns_ip_kept_when_doh_confirms(self, monkeypatch):
|
||||||
"""The IP from system DNS is the one that doesn't work — exclude it."""
|
"""DoH-confirmed IPs are kept even when they match system DNS (#14520).
|
||||||
|
|
||||||
|
The system-DNS IP is often the most reliable path; including it as a
|
||||||
|
fallback lets the IP-rewrite retry recover from transient primary-path
|
||||||
|
failures instead of jumping straight to the hardcoded seed list.
|
||||||
|
"""
|
||||||
self._patch_doh(monkeypatch, {
|
self._patch_doh(monkeypatch, {
|
||||||
"https://dns.google": (200, _doh_answer("149.154.166.110", "149.154.167.220")),
|
"https://dns.google": (200, _doh_answer("149.154.166.110", "149.154.167.220")),
|
||||||
"https://cloudflare-dns.com": (200, _doh_answer("149.154.166.110")),
|
"https://cloudflare-dns.com": (200, _doh_answer("149.154.166.110")),
|
||||||
}, system_dns_ips=["149.154.166.110"])
|
}, system_dns_ips=["149.154.166.110"])
|
||||||
|
|
||||||
ips = await tnet.discover_fallback_ips()
|
ips = await tnet.discover_fallback_ips()
|
||||||
assert ips == ["149.154.167.220"]
|
assert ips == ["149.154.166.110", "149.154.167.220"]
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_doh_results_deduplicated(self, monkeypatch):
|
async def test_doh_results_deduplicated(self, monkeypatch):
|
||||||
|
|
@ -607,15 +612,21 @@ class TestDiscoverFallbackIps:
|
||||||
assert "149.154.167.220" in ips
|
assert "149.154.167.220" in ips
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_all_doh_ips_same_as_system_dns_uses_seed(self, monkeypatch):
|
async def test_all_doh_ips_same_as_system_dns_kept(self, monkeypatch):
|
||||||
"""DoH returns only the same blocked IP — seed list is the fallback."""
|
"""DoH agrees with system DNS — keep that IP instead of seed list (#14520).
|
||||||
|
|
||||||
|
Previous behavior fell through to ``_SEED_FALLBACK_IPS`` here, but the
|
||||||
|
seed addresses are not routable on every network. When DoH confirms
|
||||||
|
the system IP, that IP is the best candidate we have and should be
|
||||||
|
used as the fallback target.
|
||||||
|
"""
|
||||||
self._patch_doh(monkeypatch, {
|
self._patch_doh(monkeypatch, {
|
||||||
"https://dns.google": (200, _doh_answer("149.154.166.110")),
|
"https://dns.google": (200, _doh_answer("149.154.166.110")),
|
||||||
"https://cloudflare-dns.com": (200, _doh_answer("149.154.166.110")),
|
"https://cloudflare-dns.com": (200, _doh_answer("149.154.166.110")),
|
||||||
}, system_dns_ips=["149.154.166.110"])
|
}, system_dns_ips=["149.154.166.110"])
|
||||||
|
|
||||||
ips = await tnet.discover_fallback_ips()
|
ips = await tnet.discover_fallback_ips()
|
||||||
assert ips == tnet._SEED_FALLBACK_IPS
|
assert ips == ["149.154.166.110"]
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_cloudflare_gets_accept_header(self, monkeypatch):
|
async def test_cloudflare_gets_accept_header(self, monkeypatch):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue