Skip to content

fix(server_https): use http.LocalAddrContextKey for DoH local address and avoid blocking accept loop under PROXY protocol#8149

Merged
yongtang merged 1 commit into
coredns:masterfrom
zongqi-wang:fix-doh-conncontext-blocking-localaddr
Jun 6, 2026
Merged

fix(server_https): use http.LocalAddrContextKey for DoH local address and avoid blocking accept loop under PROXY protocol#8149
yongtang merged 1 commit into
coredns:masterfrom
zongqi-wang:fix-doh-conncontext-blocking-localaddr

Conversation

@zongqi-wang

@zongqi-wang zongqi-wang commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

What this fixes

This fixes my original PR #8005

NewServerHTTPS resolved the per-connection local address in a custom http.Server.ConnContext callback:

ConnContext: func(ctx context.Context, c net.Conn) context.Context {
    return context.WithValue(ctx, connAddrKey{}, c.LocalAddr())
},

ConnContext runs synchronously in the http.Server accept loop, before the per-connection serving goroutine starts. For a plain socket c.LocalAddr() is cheap, but when the listener is wrapped by go-proxyproto, Conn.LocalAddr() calls ensureHeaderProcessed(), which reads the PROXY header off the connection (sync.Once). If the header has not arrived yet, that read blocks inside the accept loop and head-of-line-blocks acceptance of every other connection, so connections last longer than they should.

The fix

net/http and http2 already populate http.LocalAddrContextKey from the connection in the per-connection serving goroutine, not the accept loop:

Path Where Source
HTTP/1.1 c.serve() goroutine net/http server.go:1901
HTTP/2 (stdlib bundle) serving goroutine net/http h2_bundle.go:4552
HTTP/2 (x/net) serverConnBaseContext golang.org/x/net/http2 server_common.go:216

All resolve c.LocalAddr() on the same tls.Conn → … → proxyproto.Conn chain, so the value under http.LocalAddrContextKey is byte-identical to what the custom callback produced — the PPv2 destination address — and it is set off the accept loop on both the HTTP/1.1 and HTTP/2 paths.

So this drops the custom ConnContext callback and the connKey type and reads the framework key directly:

func (s *ServerHTTPS) localAddr(r *http.Request) net.Addr {
    if addr, ok := r.Context().Value(http.LocalAddrContextKey).(net.Addr); ok {
        return addr
    }
    return s.listenAddr
}

The client address is unaffected: it arrives via r.RemoteAddr, which the framework populates natively from c.RemoteAddr() on both paths.

Testing

  • go build ./core/dnsserver/ — ok
  • go vet ./core/dnsserver/ — clean
  • gofmt — clean
  • go test ./core/dnsserver/ — ok
  • go test -race ./core/dnsserver/ — ok

TestDoHWriterLaddrFromConnContext / TestDoHWriterLaddrFallback stash the address under http.LocalAddrContextKey instead of the removed custom key.

@zongqi-wang zongqi-wang force-pushed the fix-doh-conncontext-blocking-localaddr branch from 7f7373d to 8bf74af Compare June 4, 2026 19:16
@zongqi-wang zongqi-wang changed the title dnsserver: resolve DoH local address lazily to avoid blocking the accept loop under PROXY protocol dnsserver: use http.LocalAddrContextKey for DoH local address (avoid blocking accept loop under PROXY protocol) Jun 4, 2026
The DoH server resolved the per-connection local address in a custom
http.Server.ConnContext callback. ConnContext runs synchronously in the
http.Server accept loop, so calling c.LocalAddr() there is a problem when
the listener is proxyproto-wrapped: LocalAddr() triggers the PROXY-header
read, which blocks the accept loop until the header arrives and
head-of-line-blocks acceptance of every other connection.

net/http and http2 already populate http.LocalAddrContextKey from the
connection in the per-connection serving goroutine (net/http server.go,
http2 server_common.go / h2_bundle.go), resolved through the same
tls.Conn -> proxyproto.Conn chain. For a proxyproto connection that value
is the PROXY header's destination address -- byte-identical to what the
custom callback produced -- and it is set off the accept loop on both the
HTTP/1.1 and HTTP/2 paths.

Drop the custom ConnContext callback and the connKey type, and read
http.LocalAddrContextKey in localAddr() instead. The client address is
unaffected: it arrives via r.RemoteAddr, which the framework populates
natively.

Signed-off-by: zongqi-wang <wangzongqi@msn.com>
@zongqi-wang zongqi-wang force-pushed the fix-doh-conncontext-blocking-localaddr branch from 8bf74af to 6352d19 Compare June 4, 2026 19:20
@zongqi-wang zongqi-wang changed the title dnsserver: use http.LocalAddrContextKey for DoH local address (avoid blocking accept loop under PROXY protocol) DoH ProxyProtocol: use http.LocalAddrContextKey for DoH local address (avoid blocking accept loop under PROXY protocol) Jun 4, 2026
@zongqi-wang zongqi-wang changed the title DoH ProxyProtocol: use http.LocalAddrContextKey for DoH local address (avoid blocking accept loop under PROXY protocol) server_https: use http.LocalAddrContextKey for DoH local address (avoid blocking accept loop under PROXY protocol) Jun 4, 2026
@zongqi-wang zongqi-wang changed the title server_https: use http.LocalAddrContextKey for DoH local address (avoid blocking accept loop under PROXY protocol) fix(server_https): use http.LocalAddrContextKey for DoH local address (avoid blocking accept loop under PROXY protocol) Jun 4, 2026
@zongqi-wang zongqi-wang changed the title fix(server_https): use http.LocalAddrContextKey for DoH local address (avoid blocking accept loop under PROXY protocol) fix(server_https): use http.LocalAddrContextKey for DoH local address and avoid blocking accept loop under PROXY protocol Jun 4, 2026
@zongqi-wang zongqi-wang mentioned this pull request Jun 4, 2026
@yongtang yongtang merged commit 3718f0c into coredns:master Jun 6, 2026
13 checks passed
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.

2 participants