langchain-ai/langchain · error · SSRFBlockedError

DNS resolution returned no results

Error message

DNS resolution returned no results

What it means

After a successful `getaddrinfo` call, the async SSRF transport requires a non-empty address list; an empty result raises `SSRFBlockedError('DNS resolution returned no results')`. This is a defensive branch — `getaddrinfo` normally raises rather than returning empty — and indicates a resolver behaving oddly.

Source

Thrown at libs/core/langchain_core/_security/_transport.py:88

        if hostname.lower() in allowed:
            return await self._inner.handle_async_request(request)

        # 4. DNS resolution
        port = request.url.port or (443 if scheme == "https" else 80)
        try:
            addrinfo = await asyncio.to_thread(
                socket.getaddrinfo,
                hostname,
                port,
                type=socket.SOCK_STREAM,
            )
        except socket.gaierror as exc:
            msg = "DNS resolution failed"
            raise SSRFBlockedError(msg) from exc

        if not addrinfo:
            msg = "DNS resolution returned no results"
            raise SSRFBlockedError(msg)

        # 5. Validate ALL resolved IPs - any blocked means reject.
        for _family, _type, _proto, _canonname, sockaddr in addrinfo:
            ip_str: str = sockaddr[0]  # type: ignore[assignment]
            validate_resolved_ip(ip_str, self._policy)

        # 6. Pin to first resolved IP.
        pinned_ip = addrinfo[0][4][0]

        # 7. Rewrite URL to use pinned IP, preserving Host header and SNI.
        pinned_url = request.url.copy_with(host=pinned_ip)

        # Build extensions dict, adding sni_hostname for HTTPS so TLS
        # certificate validation uses the original hostname.
        extensions = dict(request.extensions)
        if scheme == "https":
            extensions["sni_hostname"] = hostname.encode("ascii")

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. If you mock or wrap `socket.getaddrinfo` in tests, return a realistic non-empty addrinfo list
  2. Check `getent hosts <hostname>` / `socket.getaddrinfo(host, port, type=socket.SOCK_STREAM)` in the failing environment to see what the resolver actually returns
  3. Eliminate custom resolver shims (LD_PRELOAD, nsswitch overrides) or fix them to return addresses
  4. Retry in case of transient resolver weirdness; escalate to infrastructure if persistent
Defensive patterns

Strategy: try-catch

Validate before calling

import socket

def resolver_healthy(host: str, port: int) -> bool:
    try:
        return len(socket.getaddrinfo(host, port, type=socket.SOCK_STREAM)) > 0
    except OSError:
        return False

Try / catch

try:
    resp = await client.get(url)
except SSRFBlockedError as e:
    if 'returned no results' in str(e):
        # resolver behaved abnormally; log and surface infra issue
        logger.error('resolver returned empty for %s', url)
    raise

Prevention

When it happens

Trigger: An exotic resolver/mocked `getaddrinfo` returning `[]`; unusual platform resolver behavior (some sandbox wrappers, glibc edge cases, or monkeypatched sockets in tests) yielding no addresses for the port/type combination.

Common situations: Test suites that patch `socket.getaddrinfo` incompletely; custom DNS shims or LD_PRELOAD resolvers; rare Nsswitch/mDNS configurations.

Understand the failure class

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/4c5034dc2d83e8ce. Report an issue: GitHub.