From c4cfd3dd2c8456af71420f121d3a16704d0bd46b Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Tue, 13 Oct 2026 10:53:41 +0800 Subject: [PATCH] test gorilla websocket --- go.mod | 2 +- go.sum | 4 +- transport/internet/browser_dialer/dialer.go | 31 ++-- .../internet/splithttp/browser_client.go | 3 +- transport/internet/websocket/connection.go | 133 +++++++----------- transport/internet/websocket/dialer.go | 48 ++++--- transport/internet/websocket/hub.go | 23 +-- 7 files changed, 105 insertions(+), 139 deletions(-) diff --git a/go.mod b/go.mod index 554a3bb8b..bb4957aa6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/golang/mock v1.7.0-rc.1 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.3 + github.com/coder/websocket v1.8.15 github.com/klauspost/cpuid/v2 v2.4.0 github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 github.com/miekg/dns v1.1.73 diff --git a/go.sum b/go.sum index 871d83571..75a9691eb 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cloudflare/circl v1.6.5 h1:O64F26HEqNhznd/hrC5KZXVKYuKM2rx4deZDTc4ihQA= github.com/cloudflare/circl v1.6.5/go.mod h1:h5LNyxAc5nTue9DS5jT+48en2PSDYt3zdGnz5OstK6c= +github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA= +github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474= @@ -23,8 +25,6 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI= github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c= github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= diff --git a/transport/internet/browser_dialer/dialer.go b/transport/internet/browser_dialer/dialer.go index ee3828020..673aca481 100644 --- a/transport/internet/browser_dialer/dialer.go +++ b/transport/internet/browser_dialer/dialer.go @@ -8,9 +8,8 @@ import ( "encoding/json" "net/http" "sync" - "time" - "github.com/gorilla/websocket" + "github.com/coder/websocket" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/uuid" @@ -33,15 +32,6 @@ var ( mu sync.Mutex ) -var upgrader = &websocket.Upgrader{ - ReadBufferSize: 0, - WriteBufferSize: 0, - HandshakeTimeout: time.Second * 4, - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - // Used by external projects when using xray as a go module func Reload() { addr := platform.NewEnvFlag(platform.BrowserDialerAddress).GetValue(func() string { return "" }) @@ -60,7 +50,7 @@ func Reload() { for len(conns) > 0 { select { case c := <-conns: - c.Close() + c.CloseNow() default: } } @@ -77,7 +67,8 @@ func Reload() { Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/websocket" { if r.URL.Query().Get("token") == csrfToken { - if conn, err := upgrader.Upgrade(w, r, nil); err == nil { + if conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{InsecureSkipVerify: true}); err == nil { + conn.SetReadLimit(-1) conns <- conn } else { errors.LogError(context.Background(), "Browser dialer http upgrade unexpected error") @@ -177,7 +168,7 @@ func dialWithBody(method string, uri string, headers http.Header, cookies []*htt return err } - err = conn.WriteMessage(websocket.BinaryMessage, payload) + err = conn.Write(context.Background(), websocket.MessageBinary, payload) if err != nil { return err } @@ -187,7 +178,7 @@ func dialWithBody(method string, uri string, headers http.Header, cookies []*htt return err } - conn.Close() + conn.CloseNow() return nil } @@ -200,8 +191,8 @@ func dialTask(task task) (*websocket.Conn, error) { var conn *websocket.Conn for { conn = <-conns - if conn.WriteMessage(websocket.TextMessage, data) != nil { - conn.Close() + if conn.Write(context.Background(), websocket.MessageText, data) != nil { + conn.CloseNow() } else { break } @@ -215,11 +206,11 @@ func dialTask(task task) (*websocket.Conn, error) { } func CheckOK(conn *websocket.Conn) error { - if _, p, err := conn.ReadMessage(); err != nil { - conn.Close() + if _, p, err := conn.Read(context.Background()); err != nil { + conn.CloseNow() return err } else if s := string(p); s != "ok" { - conn.Close() + conn.CloseNow() return errors.New(s) } diff --git a/transport/internet/splithttp/browser_client.go b/transport/internet/splithttp/browser_client.go index a70447f2d..cdfbd70d9 100644 --- a/transport/internet/splithttp/browser_client.go +++ b/transport/internet/splithttp/browser_client.go @@ -39,7 +39,8 @@ func (c *BrowserDialerClient) OpenStream(ctx context.Context, url string, sessio return nil, dummyAddr, dummyAddr, err } - return websocket.NewConnection(conn, dummyAddr, nil, 0), conn.RemoteAddr(), conn.LocalAddr(), nil + wrapped := websocket.NewConnection(conn, nil, nil, 0) + return wrapped, wrapped.RemoteAddr(), wrapped.LocalAddr(), nil } func (c *BrowserDialerClient) PostPacket(ctx context.Context, url string, sessionId string, seqStr string, payload buf.MultiBuffer) error { diff --git a/transport/internet/websocket/connection.go b/transport/internet/websocket/connection.go index 26082fc68..8ef1c3e8e 100644 --- a/transport/internet/websocket/connection.go +++ b/transport/internet/websocket/connection.go @@ -1,124 +1,91 @@ package websocket import ( + "context" "io" "net" "time" - "github.com/gorilla/websocket" + "github.com/coder/websocket" "github.com/xtls/xray-core/common/buf" - "github.com/xtls/xray-core/common/errors" - "github.com/xtls/xray-core/common/serial" ) var _ buf.Writer = (*connection)(nil) -// connection is a wrapper for net.Conn over WebSocket connection. -// remoteAddr is used to pass "virtual" remote IP addresses in X-Forwarded-For. -// so we shouldn't directly read it form conn. type connection struct { - conn *websocket.Conn + net.Conn reader io.Reader remoteAddr net.Addr + localAddr net.Addr } func NewConnection(conn *websocket.Conn, remoteAddr net.Addr, extraReader io.Reader, heartbeatPeriod uint32) *connection { + ctx, cancel := context.WithCancel(context.Background()) + c := &connection{Conn: websocket.NetConn(ctx, conn, websocket.MessageBinary), reader: extraReader, remoteAddr: remoteAddr} + if remoteAddr == nil { + c.remoteAddr = c.Conn.RemoteAddr() + } + c.Conn = &cancelConn{Conn: c.Conn, cancel: cancel} if heartbeatPeriod != 0 { go func() { + ticker := time.NewTicker(time.Duration(heartbeatPeriod) * time.Second) + defer ticker.Stop() for { - time.Sleep(time.Duration(heartbeatPeriod) * time.Second) - if err := conn.WriteControl(websocket.PingMessage, []byte{}, time.Time{}); err != nil { - break + select { + case <-ctx.Done(): + return + case <-ticker.C: + pingCtx, stop := context.WithTimeout(ctx, time.Duration(heartbeatPeriod)*time.Second) + err := conn.Ping(pingCtx) + stop() + if err != nil { + c.Close() + return + } } } }() } - - return &connection{ - conn: conn, - remoteAddr: remoteAddr, - reader: extraReader, - } + return c +} + +type cancelConn struct { + net.Conn + cancel context.CancelFunc +} + +func (c *cancelConn) Close() error { + defer c.cancel() + return c.Conn.Close() } -// Read implements net.Conn.Read() func (c *connection) Read(b []byte) (int, error) { - for { - reader, err := c.getReader() - if err != nil { - return 0, err - } - - nBytes, err := reader.Read(b) - if errors.Cause(err) == io.EOF { - c.reader = nil - continue - } - return nBytes, err - } -} - -func (c *connection) getReader() (io.Reader, error) { if c.reader != nil { - return c.reader, nil + n, err := c.reader.Read(b) + if err == io.EOF { + c.reader = nil + if n > 0 { + return n, nil + } + } else { + return n, err + } } - - _, reader, err := c.conn.NextReader() - if err != nil { - return nil, err - } - c.reader = reader - return reader, nil + return c.Conn.Read(b) } -// Write implements io.Writer. -func (c *connection) Write(b []byte) (int, error) { - if err := c.conn.WriteMessage(websocket.BinaryMessage, b); err != nil { - return 0, err +func (c *connection) LocalAddr() net.Addr { + if c.localAddr != nil { + return c.localAddr } - return len(b), nil + return c.Conn.LocalAddr() } +func (c *connection) RemoteAddr() net.Addr { return c.remoteAddr } + func (c *connection) WriteMultiBuffer(mb buf.MultiBuffer) error { mb = buf.Compact(mb) mb, err := buf.WriteMultiBuffer(c, mb) buf.ReleaseMulti(mb) return err } - -func (c *connection) Close() error { - var errs []interface{} - if err := c.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second*5)); err != nil { - errs = append(errs, err) - } - if err := c.conn.Close(); err != nil { - errs = append(errs, err) - } - if len(errs) > 0 { - return errors.New("failed to close connection").Base(errors.New(serial.Concat(errs...))) - } - return nil -} - -func (c *connection) LocalAddr() net.Addr { - return c.conn.LocalAddr() -} - -func (c *connection) RemoteAddr() net.Addr { - return c.remoteAddr -} - -func (c *connection) SetDeadline(t time.Time) error { - if err := c.SetReadDeadline(t); err != nil { - return err - } - return c.SetWriteDeadline(t) -} - -func (c *connection) SetReadDeadline(t time.Time) error { - return c.conn.SetReadDeadline(t) -} - -func (c *connection) SetWriteDeadline(t time.Time) error { - return c.conn.SetWriteDeadline(t) -} diff --git a/transport/internet/websocket/dialer.go b/transport/internet/websocket/dialer.go index 0e04505b5..893936709 100644 --- a/transport/internet/websocket/dialer.go +++ b/transport/internet/websocket/dialer.go @@ -5,9 +5,10 @@ import ( _ "embed" "encoding/base64" "io" + "net/http" "time" - "github.com/gorilla/websocket" + "github.com/coder/websocket" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" @@ -46,23 +47,22 @@ func init() { func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig, ed []byte) (net.Conn, error) { wsSettings := streamSettings.ProtocolSettings.(*Config) - dialer := &websocket.Dialer{ - NetDial: func(network, addr string) (net.Conn, error) { + var localAddr, remoteAddr net.Addr + dialer := &http.Transport{ + DialContext: func(dialCtx context.Context, network, addr string) (net.Conn, error) { var conn net.Conn var err error if streamSettings.FinalMask != nil { - conn, err = streamSettings.FinalMask.DialTCP(ctx, dest) + conn, err = streamSettings.FinalMask.DialTCP(dialCtx, dest) } else { - conn, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings) + conn, err = internet.DialSystem(dialCtx, dest, streamSettings.SocketSettings) } if err != nil { return nil, errors.New("failed to dial to dest").Base(err) } + localAddr, remoteAddr = conn.LocalAddr(), conn.RemoteAddr() return conn, err }, - ReadBufferSize: 4 * 1024, - WriteBufferSize: 4 * 1024, - HandshakeTimeout: time.Second * 8, } protocol := "ws" @@ -73,14 +73,14 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in tlsConfig := tConfig.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1")) dialer.TLSClientConfig = tlsConfig if fingerprint := tls.GetFingerprint(tConfig.Fingerprint); fingerprint != nil { - dialer.NetDialTLSContext = func(_ context.Context, _, addr string) (net.Conn, error) { + dialer.DialTLSContext = func(dialCtx context.Context, _, addr string) (net.Conn, error) { // Like the NetDial in the dialer var pconn net.Conn var err error if streamSettings.FinalMask != nil { - pconn, err = streamSettings.FinalMask.DialTCP(ctx, dest) + pconn, err = streamSettings.FinalMask.DialTCP(dialCtx, dest) } else { - pconn, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings) + pconn, err = internet.DialSystem(dialCtx, dest, streamSettings.SocketSettings) } if err != nil { return nil, errors.New("failed to dial to dest").Base(err) @@ -88,16 +88,19 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in // TLS and apply the handshake cn := tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn) - if err := cn.WebsocketHandshakeContext(ctx); err != nil { + if err := cn.WebsocketHandshakeContext(dialCtx); err != nil { + pconn.Close() errors.LogErrorInner(ctx, err, "failed to dial to "+addr) return nil, err } if !tlsConfig.InsecureSkipVerify { if err := cn.VerifyHostname(tlsConfig.ServerName); err != nil { + pconn.Close() errors.LogErrorInner(ctx, err, "failed to dial to "+addr) return nil, err } } + localAddr, remoteAddr = cn.LocalAddr(), cn.RemoteAddr() return cn, nil } } @@ -106,7 +109,7 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in if browser_dialer.HasBrowserDialer() { // For Browser Dialer's optimized IP and non-standard port host := wsSettings.Host - if host == "" && tConfig.ServerName != "" { + if host == "" && tConfig != nil && tConfig.ServerName != "" { host = tConfig.ServerName } if host == "" { @@ -122,7 +125,7 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in return nil, err } - return NewConnection(conn, conn.RemoteAddr(), nil, wsSettings.HeartbeatPeriod), nil + return NewConnection(conn, nil, nil, wsSettings.HeartbeatPeriod), nil } host := dest.Address.String() @@ -145,7 +148,18 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in header.Set("Sec-WebSocket-Protocol", base64.RawURLEncoding.EncodeToString(ed)) } - conn, resp, err := dialer.DialContext(ctx, uri, header) + subprotocols := []string(nil) + if protocol := header.Get("Sec-WebSocket-Protocol"); protocol != "" { + subprotocols = []string{protocol} + header.Del("Sec-WebSocket-Protocol") + } + hostHeader := header.Get("Host") + header.Del("Host") + dialCtx, cancel := context.WithTimeout(ctx, 8*time.Second) + defer cancel() + conn, resp, err := websocket.Dial(dialCtx, uri, &websocket.DialOptions{ + HTTPClient: &http.Client{Transport: dialer}, HTTPHeader: header, Host: hostHeader, Subprotocols: subprotocols, + }) if err != nil { var reason string if resp != nil { @@ -154,7 +168,9 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in return nil, errors.New("failed to dial to (", uri, "): ", reason).Base(err) } - return NewConnection(conn, conn.RemoteAddr(), nil, wsSettings.HeartbeatPeriod), nil + wrapped := NewConnection(conn, remoteAddr, nil, wsSettings.HeartbeatPeriod) + wrapped.localAddr = localAddr + return wrapped, nil } type delayDialConn struct { diff --git a/transport/internet/websocket/hub.go b/transport/internet/websocket/hub.go index db1cc7c36..0f3705335 100644 --- a/transport/internet/websocket/hub.go +++ b/transport/internet/websocket/hub.go @@ -11,7 +11,7 @@ import ( "sync" "time" - "github.com/gorilla/websocket" + "github.com/coder/websocket" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" @@ -29,15 +29,6 @@ type requestHandler struct { var replacer = strings.NewReplacer("+", "-", "/", "_", "=", "") -var upgrader = &websocket.Upgrader{ - ReadBufferSize: 0, - WriteBufferSize: 0, - HandshakeTimeout: time.Second * 4, - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { if len(h.host) > 0 && !internet.IsValidHTTPHost(request.Host, h.host) { errors.LogInfo(context.Background(), "failed to validate host, request:", request.Host, ", config:", h.host) @@ -51,28 +42,28 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req } var extraReader io.Reader - responseHeader := http.Header{} + acceptOptions := &websocket.AcceptOptions{InsecureSkipVerify: true} if str := request.Header.Get("Sec-WebSocket-Protocol"); str != "" { if ed, err := base64.RawURLEncoding.DecodeString(replacer.Replace(str)); err == nil && len(ed) > 0 { extraReader = bytes.NewReader(ed) - responseHeader.Set("Sec-WebSocket-Protocol", str) + acceptOptions.Subprotocols = []string{str} } } - conn, err := upgrader.Upgrade(writer, request, responseHeader) + conn, err := websocket.Accept(writer, request, acceptOptions) if err != nil { errors.LogInfoInner(context.Background(), err, "failed to convert to WebSocket connection") return } - remoteAddr := conn.RemoteAddr() + remoteAddr, _ := net.ResolveTCPAddr("tcp", request.RemoteAddr) var trustedXFF []string if h.socketSettings != nil { trustedXFF = h.socketSettings.TrustedXForwardedFor } - remoteAddr = http_proto.ApplyTrustedXForwardedFor(request.Header, trustedXFF, remoteAddr) + effectiveRemoteAddr := http_proto.ApplyTrustedXForwardedFor(request.Header, trustedXFF, remoteAddr) - h.ln.addConn(NewConnection(conn, remoteAddr, extraReader, h.ln.config.HeartbeatPeriod)) + h.ln.addConn(NewConnection(conn, effectiveRemoteAddr, extraReader, h.ln.config.HeartbeatPeriod)) } type Listener struct {