From ba7adcab8f48a6d0a29a80b01befd1941c05d390 Mon Sep 17 00:00:00 2001 From: Shani Pathak Date: Mon, 22 Jun 2026 21:04:02 +0530 Subject: [PATCH] fix(websocket): send tunnel payload as binary frames (#5363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ws/wss transport carries a raw byte stream (yamux), but the golang.org/x/net/websocket Conn defaults to text frames (PayloadType TextFrame). Per RFC 6455 ยง5.6 a text frame must contain valid UTF-8, so RFC-compliant intermediaries (API gateways / reverse proxies) validate the payload and close the connection when the binary tunnel data is not valid UTF-8. This goes unnoticed peer-to-peer because x/net/websocket does not validate UTF-8 on read, but it breaks the connection through a compliant validating proxy. Set PayloadType to BinaryFrame on both the server listener and the client dialer so the tunnel is framed as binary. --- pkg/util/net/dial.go | 5 +++++ pkg/util/net/websocket.go | 5 +++++ 2 files changed, 10 insertions(+) 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)