Files
netbird/proxy/internal/roundtrip/multi.go
mlsmaycon dd90c0d180 chore(proxy/roundtrip): strip upstream-request debug logging from MultiTransport
The MultiTransport's job is per-request dispatch between the embedded
NetBird transport and the stdlib transport based on the direct_upstream
context flag — about 25 lines of code. The header/body debug logging
that was bundled in pulls in:

- io.ReadAll on every request body, even when log level is above debug.
  Forces buffering of streaming POSTs (LLM completions, file uploads)
  before they reach the upstream transport.
- A header redaction list and a body-snippet cap that duplicate concerns
  already covered by netbird.go's per-roundtrip log.

netbird.go already emits method/host/url/account/duration/status/err at
debug level on every roundtrip; nothing in the private-service feature
needs the extra header+body dump.

- Drop logUpstreamRequest, formatHeaders, redactHeaderValue,
  snapshotRequestBody, and the upstreamLogBodyMax constant.
- Drop the logger field and the trailing nil arg from NewMultiTransport;
  proxy/server.go and the tests updated accordingly.
2026-05-21 11:02:41 +02:00

69 lines
2.5 KiB
Go

package roundtrip
import (
"crypto/tls"
"net"
"net/http"
"time"
)
// MultiTransport dispatches each request to either the embedded NetBird
// http.RoundTripper or a stdlib http.Transport based on a per-request
// context flag set by the reverse-proxy rewrite step. When the flag is
// absent (the default for every existing target), requests follow the
// embedded NetBird path — current behaviour, preserved.
//
// The stdlib branch is used when a target was configured with
// direct_upstream=true. It dials via the host's network stack, which is
// what private (`netbird proxy`) deployments and centralised proxies
// fronting host-reachable upstreams (public APIs, LAN services,
// localhost sidecars) want.
type MultiTransport struct {
embedded http.RoundTripper
direct *http.Transport
insecure *http.Transport
}
// NewMultiTransport wires both branches. embedded is the existing NetBird
// roundtripper; the direct branches are constructed here with sensible
// defaults that mirror Go's stdlib defaults plus a dial-timeout wrapper
// honouring the per-request value attached via types.WithDialTimeout.
// Pass embedded=nil to disable the WG branch entirely (every request
// will route direct, regardless of the context flag).
func NewMultiTransport(embedded http.RoundTripper) *MultiTransport {
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
direct := &http.Transport{
DialContext: dialWithTimeout(dialer.DialContext),
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
insecure := direct.Clone()
insecure.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // matches the embedded NetBird transport's per-target opt-in
return &MultiTransport{
embedded: embedded,
direct: direct,
insecure: insecure,
}
}
// RoundTrip dispatches by reading the direct-upstream flag from the request
// context. When set, the request is forwarded via the stdlib transport,
// honouring the existing per-request skip-TLS-verify flag. Otherwise it
// goes through the embedded NetBird roundtripper.
func (m *MultiTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if DirectUpstreamFromContext(req.Context()) || m.embedded == nil {
if skipTLSVerifyFromContext(req.Context()) {
return m.insecure.RoundTrip(req)
}
return m.direct.RoundTrip(req)
}
return m.embedded.RoundTrip(req)
}