stamparm/maltrail · error

empty response from

Error message

empty response from '%s'

What it means

During the geo-IP database update, update_geo() downloads each RIR delegated-stats file listed in RIR_DELEGATED_URLS via retrieve_content(). If a URL returns empty content (no data), it raises "empty response from '%s'" naming the failing URL, because an empty delegated file cannot be parsed into geo ranges.

Solutions

  1. Re-run the update later — RIR endpoints occasionally serve transient empty responses; add retry with backoff around retrieve_content for each URL.
  2. Check network egress: verify the failing URL loads from the host (curl -sIL <url>) and that no proxy, captive portal, or WAF strips the body.
  3. Review retrieve_content for silent error swallowing (exceptions converted to '') and make it raise the underlying HTTP/connection error so the failure is diagnosable.
  4. Run the update with outbound HTTPS access and a current CA bundle; pin/verify the RIR URLs are still valid (RIRs occasionally migrate delegated-file locations).
  5. Optionally fail soft: log the specific RIR and continue with the last successfully updated database instead of aborting the whole update.

Example fix

# before
content = retrieve_content(url)
if not content:
    raise Exception("empty response from '%s'" % url)

# after
content = None
for attempt in range(3):
    content = retrieve_content(url)
    if content:
        break
    time.sleep(2 ** attempt)
if not content:
    raise Exception("empty response from '%s'" % url)
Defensive patterns

Strategy: retry

Validate before calling

content = retrieve_content(url)
if content is None or len(content) == 0:
    # fail fast before update_geo's internal raise; retry or skip this RIR
    retry_download(url)

Type guard

def is_nonempty_str(s):
    return isinstance(s, (str, bytes)) and len(s) > 0

Try / catch

try:
    update_geo()
except Exception as e:
    if str(e).startswith("empty response from"):
        log.warning("RIR endpoint returned empty body: %s — retrying later", e)
    else:
        raise

Prevention

When it happens

Trigger: retrieve_content(url) returned ''/None for one of the RIR_DELEGATED_URLS endpoints — server returned an empty body, a redirect/proxy stripped the payload, a captive portal or WAF blocked the request, or a network layer returned success with zero bytes.

Common situations: Running updates behind corporate proxies/firewalls that return empty responses; RIR servers (RIPE/ARIN/APNIC/LACNIC/AFRINIC) temporarily unavailable or rate-limiting; TLS interception appliances swallowing bodies; intermittent network failures on scheduled update_timer runs or at startup via main.

Related errors


AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13). Data as JSON: /api/errors/180aefe066d3fb54. Report an issue: GitHub.

Appendix: source

Thrown at core/update.py:976

        if not os.path.isdir(USERS_DIR):
            os.makedirs(USERS_DIR, 0o755)
    except Exception as ex:
        sys.exit("[!] something went wrong during creation of directory '%s' ('%s')" % (USERS_DIR, ex))

    _chown(USERS_DIR)

    if not (force or not os.path.isfile(GEO_IP2CC_FILE) or (time.time() - os.stat(GEO_IP2CC_FILE).st_mtime) >= FRESH_GEO_DELTA_DAYS * 24 * 3600):
        _chown(GEO_IP2CC_FILE)
        return

    print("[i] updating geolocation (IP->country) database...")

    v4, v6 = [], []
    try:
        for url in RIR_DELEGATED_URLS:
            content = retrieve_content(url)
            if not content:
                raise Exception("empty response from '%s'" % url)
            if isinstance(content, bytes):
                content = content.decode("latin-1", "ignore")
            for line in content.splitlines():
                parts = line.split('|')
                if len(parts) < 7 or parts[2] not in ("ipv4", "ipv6") or parts[6] not in ("allocated", "assigned"):
                    continue
                cc = parts[1]
                if len(cc) != 2 or not cc.isalpha():
                    continue
                try:
                    if parts[2] == "ipv4":
                        start = addr_to_int(parts[3]); v4.append((start, start + int(parts[4]) - 1, cc.upper()))
                    else:  # ipv6: parts[4] is the prefix length
                        start = _ip6_to_int(parts[3]); v6.append((start, start + (1 << (128 - int(parts[4]))) - 1, cc.upper()))
                except Exception:
                    continue
    except Exception as ex:
        print("[x] something went wrong during retrieval of RIR delegation stats ('%s')" % ex)

View on GitHub (pinned to 77cfb06d76)