dnsserver: use http.LocalAddrContextKey for DoH local address (#8149)

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>
This commit is contained in:
Cedric Wang
2026-06-05 18:31:53 -07:00
committed by GitHub
parent 408fdf0812
commit 3718f0cc81
2 changed files with 4 additions and 10 deletions

View File

@@ -52,9 +52,6 @@ func (l *loggerAdapter) Write(p []byte) (n int, err error) {
// Plugins can access the original HTTP request to retrieve headers, client IP, and metadata. // Plugins can access the original HTTP request to retrieve headers, client IP, and metadata.
type HTTPRequestKey struct{} type HTTPRequestKey struct{}
// connAddrKey is the context key for the per-connection local address set by ConnContext.
type connAddrKey struct{}
// NewServerHTTPS returns a new CoreDNS HTTPS server and compiles all plugins in to it. // NewServerHTTPS returns a new CoreDNS HTTPS server and compiles all plugins in to it.
func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) { func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) {
s, err := NewServer(addr, group) s, err := NewServer(addr, group)
@@ -93,9 +90,6 @@ func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) {
WriteTimeout: s.WriteTimeout, WriteTimeout: s.WriteTimeout,
IdleTimeout: s.IdleTimeout, IdleTimeout: s.IdleTimeout,
ErrorLog: stdlog.New(&loggerAdapter{}, "", 0), ErrorLog: stdlog.New(&loggerAdapter{}, "", 0),
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
return context.WithValue(ctx, connAddrKey{}, c.LocalAddr())
},
} }
maxConnections := DefaultHTTPSMaxConnections maxConnections := DefaultHTTPSMaxConnections
if len(group) > 0 && group[0] != nil && group[0].MaxHTTPSConnections != nil { if len(group) > 0 && group[0] != nil && group[0].MaxHTTPSConnections != nil {
@@ -176,9 +170,9 @@ func (s *ServerHTTPS) Stop() error {
return nil return nil
} }
// localAddr returns the per-connection local address from context, or s.listenAddr as fallback. // localAddr returns the per-connection local address, or s.listenAddr as fallback.
func (s *ServerHTTPS) localAddr(r *http.Request) net.Addr { func (s *ServerHTTPS) localAddr(r *http.Request) net.Addr {
if addr, ok := r.Context().Value(connAddrKey{}).(net.Addr); ok { if addr, ok := r.Context().Value(http.LocalAddrContextKey).(net.Addr); ok {
return addr return addr
} }
return s.listenAddr return s.listenAddr

View File

@@ -196,7 +196,7 @@ func TestDoHWriterLaddrFromConnContext(t *testing.T) {
ppDst := &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 443} ppDst := &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 443}
r := httptest.NewRequest(http.MethodPost, "/dns-query", io.NopCloser(bytes.NewReader(buf))) r := httptest.NewRequest(http.MethodPost, "/dns-query", io.NopCloser(bytes.NewReader(buf)))
ctx := context.WithValue(r.Context(), connAddrKey{}, ppDst) ctx := context.WithValue(r.Context(), http.LocalAddrContextKey, ppDst)
r = r.WithContext(ctx) r = r.WithContext(ctx)
w := httptest.NewRecorder() w := httptest.NewRecorder()
@@ -230,7 +230,7 @@ func TestDoHWriterLaddrFallback(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// No connAddrKey in context; should fall back to s.listenAddr. // No LocalAddrContextKey in context; should fall back to s.listenAddr.
r := httptest.NewRequest(http.MethodPost, "/dns-query", io.NopCloser(bytes.NewReader(buf))) r := httptest.NewRequest(http.MethodPost, "/dns-query", io.NopCloser(bytes.NewReader(buf)))
w := httptest.NewRecorder() w := httptest.NewRecorder()