Files
v2ray_simple/advLayer/ws/client.go
e1732a364fed cc758dec66 全面修订代码;完成 grpcSimple包;使用 tag选择编译quic 和 grpc
grpcSimple包的服务端和客户端现在都已完成,且兼容v2ray等内核。
grpcSimple包 简洁、高效,更加科学。暂不支持multiMode。

若 grpc_full 给出,则使用grpc包,否则默认使用 grpcSimple包。
若 noquic给出,则不使用 quic,否则 默认使用 quic。

修复 ws early 失效问题;
2022-04-28 05:41:56 +08:00

216 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ws
import (
"bytes"
"context"
"encoding/base64"
"errors"
"io"
"net"
"net/url"
"github.com/e1732a364fed/v2ray_simple/netLayer"
"github.com/e1732a364fed/v2ray_simple/utils"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
)
//implements advLayer.Client
type Client struct {
requestURL *url.URL //因为调用gobwas/ws.Dialer.Upgrade 时要传入url所以我们直接提供包装好的即可
path string
UseEarlyData bool
headers map[string][]string
}
// 这里默认传入的path必须 以 "/" 为前缀. 本函数 不对此进行任何检查
func NewClient(hostAddr, path string, headers map[string][]string, isEarly bool) (*Client, error) {
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 (c *Client) GetPath() string {
return c.path
}
func (c *Client) IsSuper() bool {
return false
}
func (c *Client) IsMux() bool {
return false
}
func (c *Client) IsEarly() bool {
return c.UseEarlyData
}
//与服务端进行 websocket握手并返回可直接用于读写 websocket 二进制数据的 net.Conn
func (c *Client) Handshake(underlay net.Conn, ed []byte) (net.Conn, error) {
if len(ed) > 0 {
// 我们要先返回一个 Conn, 然后读取到内层的 vless等协议的握手后再进行实际的 ws握手
return &EarlyDataConn{
Conn: underlay,
earlyData: ed,
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 len(c.headers) > 0 {
d.Header = ws.HandshakeHeaderHTTP(c.headers)
}
br, _, err := d.Upgrade(underlay, c.requestURL)
if err != nil {
return nil, err
}
//之所以返回值中有br是因为服务器可能紧接着向我们迅猛地发送数据;
//
// 但是,我们代理的方法是先等待握手成功再传递数据,而且每次都是客户端先传输数据,
// 所以我们的用例中br一定是nil
theConn := &Conn{
Conn: underlay,
state: ws.StateClientSide,
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
earlyData []byte
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)
multiReader := io.MultiReader(bytes.NewReader(p), bytes.NewReader(edc.earlyData))
_, encerr := io.Copy(encoder, multiReader)
if encerr != nil {
close(edc.firstHandshakeOkChan)
return 0, utils.ErrInErr{ErrDesc: "encode early data err", ErrDetail: encerr}
}
encoder.Close()
edc.dialer.Protocols = []string{outBuf.String()}
br, _, err := edc.dialer.Upgrade(edc.Conn, edc.requestURL)
if err != nil {
close(edc.firstHandshakeOkChan)
return 0, err
}
utils.PutBuf(outBuf)
theConn := &Conn{
Conn: edc.Conn,
state: ws.StateClientSide,
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("EarlyDataConn read failed because handshake failed")
}
edc.notFirstRead = true
}
return edc.realWsConn.Read(p)
}