Skip to content

fix(proxy): return 502 when forwarding to upstream fails - #216

Open
a-palamarchuk wants to merge 1 commit into
coder:mainfrom
a-palamarchuk:fix/upstream-error-bad-gateway
Open

a-palamarchuk wants to merge 1 commit into
coder:mainfrom
a-palamarchuk:fix/upstream-error-bad-gateway

Conversation

@a-palamarchuk

Copy link
Copy Markdown

Problem

When an allowed request cannot be forwarded, forwardRequest logs the error and returns without writing a response. This happens, for example, when DNS fails, the connection is refused, or the upstream TLS handshake fails.

  • Transparent mode and explicit-proxy HTTP: the connection closes with no response, so curl prints Empty reply from server. The agent can't tell a firewall block from a network problem.
  • CONNECT tunnel (explicit-proxy HTTPS, used by landjail): handleCONNECTTunnel loops back to http.ReadRequest while the client is still waiting for its response. The request hangs until the client gives up.

Change

  • New writeBadGatewayResponse, modeled on writeBlockedResponse. It writes 502 Bad Gateway with a plain-text body naming the host and the forwarding error.
  • forwardRequest calls it on the three failures that happen before any response bytes are written: building the request, client.Do, and reading the upstream body. On the last one it also closes the upstream body.
  • The response doesn't set Connection: close, so a CONNECT tunnel stays usable after a failed request.

Testing

New TestProxyServerUpstreamUnreachable points an allowed host at a loopback port that refuses connections. It covers transparent HTTP, transparent HTTPS, explicit-proxy HTTP, and a CONNECT tunnel. The tunnel case sends two requests on one tunnel to show that it survives a failure, with a read deadline so a regression fails instead of hanging. On main, the first three subtests fail with EOF and the tunnel subtest times out. On this branch all four pass, including 10 runs under -race.

End to end with landjail, allowing a name that never resolves. e2e.sh runs curl -sS --max-time 10 against the HTTP and HTTPS URLs and prints the status and elapsed time:

$ boundary --jail-type landjail --proxy-port 18080 --allow 'domain=nonexistent.invalid' -- sh e2e.sh

===== main
  curl http://nonexistent.invalid/
    curl: (52) Empty reply from server
        status=000
    time=0s
  curl https://nonexistent.invalid/
    curl: (28) Operation timed out after 10001 milliseconds with 0 bytes received
        status=000
    time=10s

===== this branch
  curl http://nonexistent.invalid/
    Boundary could not forward the request to nonexistent.invalid: Get "http://nonexistent.invalid/": dial tcp: lookup nonexistent.invalid on 127.0.0.53:53: no such host
        status=502
    time=0s
  curl https://nonexistent.invalid/
    Boundary could not forward the request to nonexistent.invalid: Get "https://nonexistent.invalid/": dial tcp: lookup nonexistent.invalid on 127.0.0.53:53: no such host
        status=502
    time=1s

Note: #212 (keep-alive) also edits forwardRequest, and the two branches merge cleanly. #212 sets resp.Close on the 403 response; whichever PR lands second should do the same for this 502 on the one-shot transparent paths.

AI disclosure

I used Claude Code to trace the bug and draft the fix and tests. I reviewed the diff and ran the tests and the reproduction above myself.

When an allowed request could not be forwarded (for example, DNS failure
or connection refused), forwardRequest logged the error and returned
without writing a response. Transparent and explicit HTTP clients saw an
empty reply. Inside a CONNECT tunnel the client waited for a response
while the proxy waited for the next request, so the request hung until
the client timed out.

Write a 502 Bad Gateway with the reason whenever forwarding fails before
a response has been written.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant