fix(handler/http2): apply traffic limiter + stats to forward proxy path, decouple auth I/O, add idle timeout

- Wrap non-CONNECT upstream with traffic_wrapper and stats_wrapper (was only on CONNECT path)
- Write 503 error response on forwardRequest failure instead of silent connection close
- Decouple resp.Header from w.Header() to prevent metadata header leakage/doubling in 407 responses
- Return pipeTo signal from authenticate instead of dialing inline (matches http handler pattern)
- Add idleTimeout metadata field and pass to xnet.Pipe for CONNECT tunnels
- Add 4 tests for probe resistance host forwarding and knock bypass
This commit is contained in:
ginuerzh
2026-05-30 20:06:56 +08:00
parent 6ccaae0573
commit e543c0b0fd
6 changed files with 144 additions and 30 deletions
+47
View File
@@ -156,3 +156,50 @@ func TestRoundTrip_AuthFailed(t *testing.T) {
t.Errorf("err = %v, want ErrAuthFailed", err)
}
}
func TestRoundTrip_ProbeResistanceHost(t *testing.T) {
decoy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("decoy response"))
}))
defer decoy.Close()
h := newTestHandler(handler.AutherOption(&testAuther{ok: false}))
decoyAddr := strings.TrimPrefix(decoy.URL, "http://")
h.md.probeResistance = &probeResistance{Type: "host", Value: decoyAddr}
h.Init(testMD(map[string]any{}))
req := httptest.NewRequest(http.MethodGet, "http://example.com/", nil)
w := httptest.NewRecorder()
ro := &xrecorder.HandlerRecorderObject{}
err := h.roundTrip(context.Background(), w, req, ro, &testLogger{})
if err != ErrAuthFailed {
t.Errorf("err = %v, want ErrAuthFailed", err)
}
if w.Code != http.StatusOK {
t.Errorf("decoy status = %d, want %d", w.Code, http.StatusOK)
}
if w.Body.String() != "decoy response" {
t.Errorf("decoy body = %q, want %q", w.Body.String(), "decoy response")
}
}
func TestRoundTrip_ProbeResistanceHostDialError(t *testing.T) {
h := newTestHandler(handler.AutherOption(&testAuther{ok: false}))
h.md.probeResistance = &probeResistance{Type: "host", Value: "127.0.0.1:1"} // unreachable
h.Init(testMD(map[string]any{}))
req := httptest.NewRequest(http.MethodGet, "http://example.com/", nil)
w := httptest.NewRecorder()
ro := &xrecorder.HandlerRecorderObject{}
err := h.roundTrip(context.Background(), w, req, ro, &testLogger{})
if err != ErrAuthFailed {
t.Errorf("err = %v, want ErrAuthFailed", err)
}
// Should get a 503 on dial failure.
if w.Code != http.StatusServiceUnavailable {
t.Errorf("status = %d, want %d", w.Code, http.StatusServiceUnavailable)
}
}