github.com/xraypb/Xray-core@v1.8.1/transport/internet/websocket/dialer.go (about)

     1  package websocket
     2  
     3  import (
     4  	"context"
     5  	_ "embed"
     6  	"encoding/base64"
     7  	"fmt"
     8  	"io"
     9  	gonet "net"
    10  	"net/http"
    11  	"os"
    12  	"time"
    13  
    14  	"github.com/djoeni/websocket"
    15  	"github.com/xraypb/Xray-core/common"
    16  	"github.com/xraypb/Xray-core/common/net"
    17  	"github.com/xraypb/Xray-core/common/session"
    18  	"github.com/xraypb/Xray-core/transport/internet"
    19  	"github.com/xraypb/Xray-core/transport/internet/stat"
    20  	"github.com/xraypb/Xray-core/transport/internet/tls"
    21  )
    22  
    23  //go:embed dialer.html
    24  var webpage []byte
    25  
    26  var conns chan *websocket.Conn
    27  
    28  func init() {
    29  	if addr := os.Getenv("XRAY_BROWSER_DIALER"); addr != "" {
    30  		conns = make(chan *websocket.Conn, 256)
    31  		go http.ListenAndServe(addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    32  			if r.URL.Path == "/websocket" {
    33  				if conn, err := upgrader.Upgrade(w, r, nil); err == nil {
    34  					conns <- conn
    35  				} else {
    36  					fmt.Println("unexpected error")
    37  				}
    38  			} else {
    39  				w.Write(webpage)
    40  			}
    41  		}))
    42  	}
    43  }
    44  
    45  // Dial dials a WebSocket connection to the given destination.
    46  func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
    47  	newError("creating connection to ", dest).WriteToLog(session.ExportIDToError(ctx))
    48  	var conn net.Conn
    49  	if streamSettings.ProtocolSettings.(*Config).Ed > 0 {
    50  		ctx, cancel := context.WithCancel(ctx)
    51  		conn = &delayDialConn{
    52  			dialed:         make(chan bool, 1),
    53  			cancel:         cancel,
    54  			ctx:            ctx,
    55  			dest:           dest,
    56  			streamSettings: streamSettings,
    57  		}
    58  	} else {
    59  		var err error
    60  		if conn, err = dialWebSocket(ctx, dest, streamSettings, nil); err != nil {
    61  			return nil, newError("failed to dial WebSocket").Base(err)
    62  		}
    63  	}
    64  	return stat.Connection(conn), nil
    65  }
    66  
    67  func init() {
    68  	common.Must(internet.RegisterTransportDialer(protocolName, Dial))
    69  }
    70  
    71  func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig, ed []byte) (net.Conn, error) {
    72  	wsSettings := streamSettings.ProtocolSettings.(*Config)
    73  
    74  	dialer := &websocket.Dialer{
    75  		NetDial: func(network, addr string) (net.Conn, error) {
    76  			return internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
    77  		},
    78  		ReadBufferSize:   4 * 1024,
    79  		WriteBufferSize:  4 * 1024,
    80  		HandshakeTimeout: time.Second * 8,
    81  	}
    82  
    83  	protocol := "ws"
    84  
    85  	if config := tls.ConfigFromStreamSettings(streamSettings); config != nil {
    86  		protocol = "wss"
    87  		tlsConfig := config.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1"))
    88  		dialer.TLSClientConfig = tlsConfig
    89  		if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil {
    90  			dialer.NetDialTLSContext = func(_ context.Context, _, addr string) (gonet.Conn, error) {
    91  				// Like the NetDial in the dialer
    92  				pconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
    93  				if err != nil {
    94  					newError("failed to dial to " + addr).Base(err).AtError().WriteToLog()
    95  					return nil, err
    96  				}
    97  				// TLS and apply the handshake
    98  				cn := tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn)
    99  				if err := cn.WebsocketHandshake(); err != nil {
   100  					newError("failed to dial to " + addr).Base(err).AtError().WriteToLog()
   101  					return nil, err
   102  				}
   103  				if !tlsConfig.InsecureSkipVerify {
   104  					if err := cn.VerifyHostname(tlsConfig.ServerName); err != nil {
   105  						newError("failed to dial to " + addr).Base(err).AtError().WriteToLog()
   106  						return nil, err
   107  					}
   108  				}
   109  				return cn, nil
   110  			}
   111  		}
   112  	}
   113  
   114  	host := dest.NetAddr()
   115  	if (protocol == "ws" && dest.Port == 80) || (protocol == "wss" && dest.Port == 443) {
   116  		host = dest.Address.String()
   117  	}
   118  	uri := protocol + "://" + host + wsSettings.GetNormalizedPath()
   119  
   120  	if conns != nil {
   121  		data := []byte(uri)
   122  		if ed != nil {
   123  			data = append(data, " "+base64.RawURLEncoding.EncodeToString(ed)...)
   124  		}
   125  		var conn *websocket.Conn
   126  		for {
   127  			conn = <-conns
   128  			if conn.WriteMessage(websocket.TextMessage, data) != nil {
   129  				conn.Close()
   130  			} else {
   131  				break
   132  			}
   133  		}
   134  		if _, p, err := conn.ReadMessage(); err != nil {
   135  			conn.Close()
   136  			return nil, err
   137  		} else if s := string(p); s != "ok" {
   138  			conn.Close()
   139  			return nil, newError(s)
   140  		}
   141  		return newConnection(conn, conn.RemoteAddr(), nil), nil
   142  	}
   143  
   144  	header := wsSettings.GetRequestHeader()
   145  	if ed != nil {
   146  		// RawURLEncoding is support by both V2Ray/V2Fly and XRay.
   147  		header.Set("Sec-WebSocket-Protocol", base64.RawURLEncoding.EncodeToString(ed))
   148  	}
   149  
   150  	conn, resp, err := dialer.Dial(uri, header)
   151  	if err != nil {
   152  		var reason string
   153  		if resp != nil {
   154  			reason = resp.Status
   155  		}
   156  		return nil, newError("failed to dial to (", uri, "): ", reason).Base(err)
   157  	}
   158  
   159  	return newConnection(conn, conn.RemoteAddr(), nil), nil
   160  }
   161  
   162  type delayDialConn struct {
   163  	net.Conn
   164  	closed         bool
   165  	dialed         chan bool
   166  	cancel         context.CancelFunc
   167  	ctx            context.Context
   168  	dest           net.Destination
   169  	streamSettings *internet.MemoryStreamConfig
   170  }
   171  
   172  func (d *delayDialConn) Write(b []byte) (int, error) {
   173  	if d.closed {
   174  		return 0, io.ErrClosedPipe
   175  	}
   176  	if d.Conn == nil {
   177  		ed := b
   178  		if len(ed) > int(d.streamSettings.ProtocolSettings.(*Config).Ed) {
   179  			ed = nil
   180  		}
   181  		var err error
   182  		if d.Conn, err = dialWebSocket(d.ctx, d.dest, d.streamSettings, ed); err != nil {
   183  			d.Close()
   184  			return 0, newError("failed to dial WebSocket").Base(err)
   185  		}
   186  		d.dialed <- true
   187  		if ed != nil {
   188  			return len(ed), nil
   189  		}
   190  	}
   191  	return d.Conn.Write(b)
   192  }
   193  
   194  func (d *delayDialConn) Read(b []byte) (int, error) {
   195  	if d.closed {
   196  		return 0, io.ErrClosedPipe
   197  	}
   198  	if d.Conn == nil {
   199  		select {
   200  		case <-d.ctx.Done():
   201  			return 0, io.ErrUnexpectedEOF
   202  		case <-d.dialed:
   203  		}
   204  	}
   205  	return d.Conn.Read(b)
   206  }
   207  
   208  func (d *delayDialConn) Close() error {
   209  	d.closed = true
   210  	d.cancel()
   211  	if d.Conn == nil {
   212  		return nil
   213  	}
   214  	return d.Conn.Close()
   215  }