From 91d2fe456c5f1c3c10aab5f3b49ac071eee3bd8d Mon Sep 17 00:00:00 2001 From: RuoJi6 Date: Sat, 24 Oct 2026 23:42:30 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Repeater=20=E7=9B=B4=E8=BF=9E=E6=8B=A8?= =?UTF-8?q?=E5=8F=B7=E6=94=B9=E7=94=B1=E8=AF=B7=E6=B1=82=E8=B6=85=E6=97=B6?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=EF=BC=8C=E8=A1=A5=E5=85=85=20DNS=20=E9=87=8D?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E4=B8=8E=20HTTP/2=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=EF=BC=88Codex=20gpt-6.1-sol=EF=BC=89(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/repeater.go | 5 +++-- server/repeater_test.go | 45 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/server/repeater.go b/server/repeater.go index 87d0c80..623f724 100644 --- a/server/repeater.go +++ b/server/repeater.go @@ -241,9 +241,10 @@ func (s *Server) repeaterTransport(useRecordingProxy, insecure bool) http.RoundT transport.DialTLSContext = nil transport.DialTLS = nil transport.Dial = nil + // Let the request context enforce timeout_ms for the entire dial. transport.DialContext = (&net.Dialer{ - Timeout: 15 * time.Second, KeepAlive: 30 * time.Second, - Control: repeaterBlockSensitiveDial, + KeepAlive: 30 * time.Second, + Control: repeaterBlockSensitiveDial, }).DialContext } if insecure { diff --git a/server/repeater_test.go b/server/repeater_test.go index ac43887..c6ef2fd 100644 --- a/server/repeater_test.go +++ b/server/repeater_test.go @@ -2,6 +2,8 @@ package server import ( "bytes" + "context" + "crypto/tls" "crypto/x509" "encoding/json" "errors" @@ -14,6 +16,7 @@ import ( "os" "strings" "testing" + "time" "github.com/Autumn-27/artex/traffic" ) @@ -119,6 +122,45 @@ func TestRepeaterBlockSensitiveDial(t *testing.T) { } } +func TestRepeaterTransportBlocksSensitiveDial(t *testing.T) { + t.Setenv(repeaterAllowLocalEnv, "") + tr := (&Server{}).repeaterTransport(false, false).(*http.Transport) + defer tr.CloseIdleConnections() + if tr.DialContext == nil { + t.Fatal("direct transport has no guarded DialContext") + } + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + conn, err := tr.DialContext(ctx, "tcp", "127.0.0.1:1") + if conn != nil { + conn.Close() + } + if err == nil || !strings.Contains(err.Error(), "拒绝访问本机/链路本地地址 127.0.0.1") { + t.Fatalf("dial should fail at the sensitive-address guard, got %v", err) + } +} + +func TestRepeaterDirectHTTP2(t *testing.T) { + t.Setenv(repeaterAllowLocalEnv, "1") + srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.ProtoMajor != 2 { + t.Errorf("request protocol=%s, want HTTP/2", r.Proto) + } + _, _ = io.WriteString(w, "h2 response") + })) + srv.EnableHTTP2 = true + srv.TLS = &tls.Config{NextProtos: []string{"h2"}} + srv.StartTLS() + defer srv.Close() + + code, out, raw := repeaterTestSend(t, &Server{}, map[string]any{ + "url": srv.URL, "use_recording_proxy": false, "insecure_skip_verify": true, + }) + if code != http.StatusOK || out.Error != "" || out.Status != http.StatusOK || out.Proto != "HTTP/2.0" || out.Body != "h2 response" { + t.Fatalf("HTTP/2 response status=%d raw=%s", code, raw) + } +} + func TestRepeaterRoundTripHeadersAndBody(t *testing.T) { t.Setenv(repeaterAllowLocalEnv, "1") var got http.Request @@ -264,6 +306,9 @@ func TestRepeaterProxyTransportConstruction(t *testing.T) { if tr.TLSClientConfig == nil || tr.TLSClientConfig.RootCAs == nil || !tr.TLSClientConfig.RootCAs.Equal(wantRoots) { t.Fatal("recording proxy CA was not loaded into transport RootCAs") } + if s.repeaterTransport(true, false).(*http.Transport).ForceAttemptHTTP2 { + t.Fatal("recording proxy transport must not force HTTP/2") + } } func TestRepeaterRedactProxyCredentials(t *testing.T) {