diff --git a/pkg/util/net/dial.go b/pkg/util/net/dial.go index 1a3859ed..b0a5e437 100644 --- a/pkg/util/net/dial.go +++ b/pkg/util/net/dial.go @@ -45,6 +45,11 @@ func DialHookWebsocket(protocol string, host string) libnet.AfterHookFunc { if err != nil { return nil, nil, err } + // The tunnel payload is a raw byte stream (yamux), not UTF-8 text. + // Send it as binary frames; otherwise RFC 6455-compliant intermediaries + // (e.g. API gateways/reverse proxies) UTF-8-validate the default text + // frames and close the connection on invalid bytes. + conn.PayloadType = websocket.BinaryFrame return ctx, conn, nil } } diff --git a/pkg/util/net/websocket.go b/pkg/util/net/websocket.go index 6c2f39c4..57641b31 100644 --- a/pkg/util/net/websocket.go +++ b/pkg/util/net/websocket.go @@ -32,6 +32,11 @@ func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) { muxer := http.NewServeMux() muxer.Handle(FrpWebsocketPath, websocket.Handler(func(c *websocket.Conn) { + // The tunnel payload is a raw byte stream (yamux), not UTF-8 text. + // Send it as binary frames; otherwise RFC 6455-compliant intermediaries + // (e.g. API gateways/reverse proxies) UTF-8-validate the default text + // frames and close the connection on invalid bytes. + c.PayloadType = websocket.BinaryFrame notifyCh := make(chan struct{}) conn := WrapCloseNotifyConn(c, func(_ error) { close(notifyCh)