Xray-core/transport/internet/websocket/dialer.go

186 lines
5.1 KiB
Go
Raw Normal View History

2020-11-25 11:01:53 +00:00
package websocket
import (
"context"
2021-03-23 09:25:35 +00:00
_ "embed"
2021-03-14 07:10:10 +00:00
"encoding/base64"
"io"
gonet "net"
2020-11-25 11:01:53 +00:00
"time"
"github.com/gorilla/websocket"
2020-12-04 01:36:16 +00:00
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
2020-12-04 01:36:16 +00:00
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/browser_dialer"
"github.com/xtls/xray-core/transport/internet/stat"
2020-12-04 01:36:16 +00:00
"github.com/xtls/xray-core/transport/internet/tls"
2020-11-25 11:01:53 +00:00
)
// Dial dials a WebSocket connection to the given destination.
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
errors.LogInfo(ctx, "creating connection to ", dest)
2021-03-14 07:10:10 +00:00
var conn net.Conn
if streamSettings.ProtocolSettings.(*Config).Ed > 0 {
ctx, cancel := context.WithCancel(ctx)
conn = &delayDialConn{
dialed: make(chan bool, 1),
cancel: cancel,
ctx: ctx,
dest: dest,
streamSettings: streamSettings,
}
} else {
var err error
if conn, err = dialWebSocket(ctx, dest, streamSettings, nil); err != nil {
return nil, errors.New("failed to dial WebSocket").Base(err)
2021-03-14 07:10:10 +00:00
}
2020-11-25 11:01:53 +00:00
}
return stat.Connection(conn), nil
2020-11-25 11:01:53 +00:00
}
func init() {
common.Must(internet.RegisterTransportDialer(protocolName, Dial))
}
2021-03-14 07:10:10 +00:00
func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig, ed []byte) (net.Conn, error) {
2020-11-25 11:01:53 +00:00
wsSettings := streamSettings.ProtocolSettings.(*Config)
dialer := &websocket.Dialer{
NetDial: func(network, addr string) (net.Conn, error) {
return internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
},
ReadBufferSize: 4 * 1024,
WriteBufferSize: 4 * 1024,
HandshakeTimeout: time.Second * 8,
}
protocol := "ws"
tConfig := tls.ConfigFromStreamSettings(streamSettings)
if tConfig != nil {
2020-11-25 11:01:53 +00:00
protocol = "wss"
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) (gonet.Conn, error) {
// Like the NetDial in the dialer
pconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
errors.LogErrorInner(ctx, err, "failed to dial to "+addr)
return nil, err
}
// TLS and apply the handshake
cn := tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn)
if err := cn.WebsocketHandshakeContext(ctx); err != nil {
errors.LogErrorInner(ctx, err, "failed to dial to "+addr)
return nil, err
}
if !tlsConfig.InsecureSkipVerify {
if err := cn.VerifyHostname(tlsConfig.ServerName); err != nil {
errors.LogErrorInner(ctx, err, "failed to dial to "+addr)
return nil, err
}
}
return cn, nil
}
}
2020-11-25 11:01:53 +00:00
}
host := dest.NetAddr()
if (protocol == "ws" && dest.Port == 80) || (protocol == "wss" && dest.Port == 443) {
host = dest.Address.String()
}
uri := protocol + "://" + host + wsSettings.GetNormalizedPath()
if browser_dialer.HasBrowserDialer() {
conn, err := browser_dialer.DialWS(uri, ed)
if err != nil {
2021-03-23 09:25:35 +00:00
return nil, err
}
return NewConnection(conn, conn.RemoteAddr(), nil, wsSettings.HeartbeatPeriod), nil
2021-03-23 09:25:35 +00:00
}
2021-03-14 07:10:10 +00:00
header := wsSettings.GetRequestHeader()
// See dialer.DialContext()
header.Set("Host", wsSettings.Host)
if header.Get("Host") == "" && tConfig != nil {
header.Set("Host", tConfig.ServerName)
}
if header.Get("Host") == "" {
header.Set("Host", dest.Address.String())
}
2021-03-14 07:10:10 +00:00
if ed != nil {
// RawURLEncoding is support by both V2Ray/V2Fly and XRay.
header.Set("Sec-WebSocket-Protocol", base64.RawURLEncoding.EncodeToString(ed))
2021-03-14 07:10:10 +00:00
}
conn, resp, err := dialer.DialContext(ctx, uri, header)
2020-11-25 11:01:53 +00:00
if err != nil {
var reason string
if resp != nil {
reason = resp.Status
}
return nil, errors.New("failed to dial to (", uri, "): ", reason).Base(err)
2020-11-25 11:01:53 +00:00
}
return NewConnection(conn, conn.RemoteAddr(), nil, wsSettings.HeartbeatPeriod), nil
2021-03-14 07:10:10 +00:00
}
type delayDialConn struct {
net.Conn
closed bool
dialed chan bool
cancel context.CancelFunc
ctx context.Context
dest net.Destination
streamSettings *internet.MemoryStreamConfig
}
func (d *delayDialConn) Write(b []byte) (int, error) {
if d.closed {
return 0, io.ErrClosedPipe
}
if d.Conn == nil {
ed := b
if len(ed) > int(d.streamSettings.ProtocolSettings.(*Config).Ed) {
ed = nil
}
var err error
if d.Conn, err = dialWebSocket(d.ctx, d.dest, d.streamSettings, ed); err != nil {
d.Close()
return 0, errors.New("failed to dial WebSocket").Base(err)
2021-03-14 07:10:10 +00:00
}
d.dialed <- true
if ed != nil {
return len(ed), nil
}
}
return d.Conn.Write(b)
}
func (d *delayDialConn) Read(b []byte) (int, error) {
if d.closed {
return 0, io.ErrClosedPipe
}
if d.Conn == nil {
select {
case <-d.ctx.Done():
return 0, io.ErrUnexpectedEOF
case <-d.dialed:
}
}
return d.Conn.Read(b)
}
func (d *delayDialConn) Close() error {
d.closed = true
d.cancel()
if d.Conn == nil {
return nil
}
return d.Conn.Close()
2020-11-25 11:01:53 +00:00
}