mirror of
https://github.com/e1732a364fed/v2ray_simple.git
synced 2026-04-23 00:47:06 +08:00
224 lines
6.4 KiB
Go
224 lines
6.4 KiB
Go
package ws
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"net/url"
|
|
|
|
"github.com/e1732a364fed/v2ray_simple/advLayer"
|
|
"github.com/e1732a364fed/v2ray_simple/httpLayer"
|
|
"github.com/e1732a364fed/v2ray_simple/netLayer"
|
|
"github.com/e1732a364fed/v2ray_simple/utils"
|
|
"github.com/gobwas/ws"
|
|
"github.com/gobwas/ws/wsutil"
|
|
)
|
|
|
|
// implements advLayer.SingleClient
|
|
type Client struct {
|
|
Creator
|
|
requestURL *url.URL //因为调用gobwas/ws.Dialer.Upgrade 时要传入url,所以我们直接提供包装好的即可
|
|
path string
|
|
UseEarlyData bool
|
|
|
|
headers *httpLayer.HeaderPreset
|
|
}
|
|
|
|
// 这里默认,传入的path必须 以 "/" 为前缀. 若path为空,本函数 将自动使用 "/"
|
|
func NewClient(hostAddr, path string, headers *httpLayer.HeaderPreset, isEarly bool) (*Client, error) {
|
|
if path == "" {
|
|
path = "/"
|
|
}
|
|
u, err := url.Parse("http://" + hostAddr + path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Client{
|
|
requestURL: u,
|
|
path: path,
|
|
headers: headers,
|
|
UseEarlyData: isEarly,
|
|
}, nil
|
|
}
|
|
|
|
func (*Client) GetCreator() advLayer.Creator {
|
|
return Creator{}
|
|
}
|
|
|
|
func (c *Client) GetPath() string {
|
|
return c.path
|
|
}
|
|
|
|
func (c *Client) IsEarly() bool {
|
|
return c.UseEarlyData
|
|
}
|
|
|
|
// 与服务端进行 websocket握手,并返回可直接用于读写 websocket 二进制数据的 net.Conn
|
|
func (c *Client) Handshake(underlay net.Conn, firstPayloadLen int) (net.Conn, error) {
|
|
|
|
if c.IsEarly() && firstPayloadLen > 0 && firstPayloadLen <= MaxEarlyDataLen {
|
|
// 我们要先返回一个 Conn, 然后读取到内层的 vless等协议的握手后,再进行实际的 ws握手
|
|
return &EarlyDataConn{
|
|
Conn: underlay,
|
|
|
|
requestURL: c.requestURL,
|
|
firstHandshakeOkChan: make(chan int, 1),
|
|
dialer: &ws.Dialer{
|
|
NetDial: func(ctx context.Context, net, addr string) (net.Conn, error) {
|
|
return underlay, nil
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
//实测默认的4096过小,因为 实测 tls握手的serverHello 就有可能超过了4096,
|
|
// 但是仔细思考,发现tls握手是在websocket的外部发生的,而我们传输的是数据的内层tls握手,那么就和Dialer没关系了,dialer只是负责读最初的握手部分;
|
|
// 所以我们就算要配置buffer尺寸,也不是在这里配置,而是要配置 theConn.w 的buffer
|
|
|
|
d := ws.Dialer{
|
|
NetDial: func(ctx context.Context, net, addr string) (net.Conn, error) {
|
|
return underlay, nil
|
|
},
|
|
// 默认不给出Protocols的话, gobwas就不会发送这个header, 另一端也收不到此header
|
|
|
|
}
|
|
|
|
if c.headers != nil && c.headers.Request != nil && len(c.headers.Request.Headers) > 0 {
|
|
d.Header = ws.HandshakeHeaderHTTP(c.headers.Request.Headers)
|
|
|
|
//实测Header里的Connection会被用到。
|
|
}
|
|
|
|
br, _, err := d.Upgrade(underlay, c.requestURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
//之所以返回值中有br,是因为服务器可能紧接着向我们迅猛地发送数据;
|
|
//
|
|
// 但是,我们代理的方法是先等待握手成功再传递数据,而且每次都是客户端先传输数据,
|
|
// 所以我们的用例中,br一定是nil
|
|
|
|
theConn := &Conn{
|
|
Conn: underlay,
|
|
state: ws.StateClientSide,
|
|
underlayIsTCP: netLayer.IsTCP(underlay) != nil,
|
|
underlayIsBasic: netLayer.IsBasicConn(underlay),
|
|
}
|
|
|
|
// 根据 gobwas/ws的代码,在服务器没有返回任何数据时,br为nil
|
|
if br == nil {
|
|
theConn.r = wsutil.NewClientSideReader(underlay)
|
|
|
|
theConn.r.OnIntermediate = wsutil.ControlFrameHandler(underlay, ws.StateClientSide)
|
|
// OnIntermediate 会在 r.NextFrame 里被调用. 如果我们不在这里提供,就要每次都在Read里操作,多此一举
|
|
|
|
return theConn, nil
|
|
}
|
|
|
|
//从bufio.Reader中提取出剩余读到的部分, 与underlay生成一个MultiReader
|
|
|
|
additionalDataNum := br.Buffered()
|
|
bs, _ := br.Peek(additionalDataNum)
|
|
|
|
wholeR := io.MultiReader(bytes.NewBuffer(bs), underlay)
|
|
|
|
theConn.r = wsutil.NewClientSideReader(wholeR)
|
|
theConn.r.OnIntermediate = wsutil.ControlFrameHandler(underlay, ws.StateClientSide)
|
|
|
|
return theConn, nil
|
|
}
|
|
|
|
type EarlyDataConn struct {
|
|
net.Conn
|
|
dialer *ws.Dialer
|
|
realWsConn net.Conn
|
|
|
|
notFirst bool
|
|
requestURL *url.URL
|
|
firstHandshakeOkChan chan int
|
|
|
|
notFirstRead bool
|
|
}
|
|
|
|
// 第一次会获取到 内部的包头, 然后我们在这里才开始执行ws的握手
|
|
// 这是verysimple的架构造成的. ws层后面跟着的应该就是代理层 的 Handshake调用,它会写入一次包头
|
|
// 我们就是利用这个特征, 把vless包头 和 之前给出的earlydata绑在一起,进行base64编码然后进行ws握手
|
|
func (edc *EarlyDataConn) Write(p []byte) (int, error) {
|
|
|
|
if !edc.notFirst {
|
|
edc.notFirst = true
|
|
|
|
outBuf := utils.GetBuf()
|
|
encoder := base64.NewEncoder(base64.RawURLEncoding, outBuf)
|
|
|
|
_, encerr := io.Copy(encoder, bytes.NewReader(p))
|
|
if encerr != nil {
|
|
utils.PutBuf(outBuf)
|
|
|
|
close(edc.firstHandshakeOkChan)
|
|
return 0, utils.ErrInErr{ErrDesc: "Err when encode early data", ErrDetail: encerr}
|
|
}
|
|
encoder.Close()
|
|
|
|
edc.dialer.Protocols = []string{outBuf.String()}
|
|
|
|
br, _, err := edc.dialer.Upgrade(edc.Conn, edc.requestURL)
|
|
if err != nil {
|
|
utils.PutBuf(outBuf)
|
|
|
|
close(edc.firstHandshakeOkChan)
|
|
return 0, err
|
|
}
|
|
|
|
utils.PutBuf(outBuf)
|
|
|
|
theConn := &Conn{
|
|
Conn: edc.Conn,
|
|
state: ws.StateClientSide,
|
|
underlayIsTCP: netLayer.IsTCP(edc.Conn) != nil,
|
|
underlayIsBasic: netLayer.IsBasicConn(edc.Conn),
|
|
}
|
|
|
|
//实测总是 br==nil,就算发送了earlydata也是如此;不过理论上有可能粘包,只要远程目标服务器的响应够快
|
|
|
|
if br == nil {
|
|
theConn.r = wsutil.NewClientSideReader(edc.Conn)
|
|
|
|
theConn.r.OnIntermediate = wsutil.ControlFrameHandler(edc.Conn, ws.StateClientSide)
|
|
|
|
} else {
|
|
|
|
additionalDataNum := br.Buffered()
|
|
bs, _ := br.Peek(additionalDataNum)
|
|
|
|
wholeR := io.MultiReader(bytes.NewBuffer(bs), edc.Conn)
|
|
|
|
theConn.r = wsutil.NewClientSideReader(wholeR)
|
|
theConn.r.OnIntermediate = wsutil.ControlFrameHandler(edc.Conn, ws.StateClientSide)
|
|
|
|
}
|
|
|
|
edc.realWsConn = theConn
|
|
edc.firstHandshakeOkChan <- 1
|
|
return len(p), nil
|
|
|
|
} //if !edc.notFirst {
|
|
|
|
return edc.realWsConn.Write(p)
|
|
}
|
|
|
|
func (edc *EarlyDataConn) Read(p []byte) (int, error) {
|
|
if !edc.notFirstRead {
|
|
_, ok := <-edc.firstHandshakeOkChan
|
|
if !ok {
|
|
return 0, errors.New("failed in EarlyDataConn read because handshake failed")
|
|
}
|
|
edc.notFirstRead = true
|
|
|
|
}
|
|
return edc.realWsConn.Read(p)
|
|
}
|