mirror of
https://github.com/fatedier/frp.git
synced 2026-07-16 17:29:16 +08:00
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.
75 lines
1.6 KiB
Go
75 lines
1.6 KiB
Go
package net
|
|
|
|
import (
|
|
"errors"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"golang.org/x/net/websocket"
|
|
)
|
|
|
|
var ErrWebsocketListenerClosed = errors.New("websocket listener closed")
|
|
|
|
const (
|
|
FrpWebsocketPath = "/~!frp"
|
|
)
|
|
|
|
type WebsocketListener struct {
|
|
ln net.Listener
|
|
acceptCh chan net.Conn
|
|
|
|
server *http.Server
|
|
}
|
|
|
|
// NewWebsocketListener to handle websocket connections
|
|
// ln: tcp listener for websocket connections
|
|
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
|
|
wl = &WebsocketListener{
|
|
ln: ln,
|
|
acceptCh: make(chan net.Conn),
|
|
}
|
|
|
|
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)
|
|
})
|
|
wl.acceptCh <- conn
|
|
<-notifyCh
|
|
}))
|
|
|
|
wl.server = &http.Server{
|
|
Addr: ln.Addr().String(),
|
|
Handler: muxer,
|
|
ReadHeaderTimeout: 60 * time.Second,
|
|
}
|
|
|
|
go func() {
|
|
_ = wl.server.Serve(ln)
|
|
}()
|
|
return
|
|
}
|
|
|
|
func (p *WebsocketListener) Accept() (net.Conn, error) {
|
|
c, ok := <-p.acceptCh
|
|
if !ok {
|
|
return nil, ErrWebsocketListenerClosed
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (p *WebsocketListener) Close() error {
|
|
return p.server.Close()
|
|
}
|
|
|
|
func (p *WebsocketListener) Addr() net.Addr {
|
|
return p.ln.Addr()
|
|
}
|