This repository has been archived by the owner on Mar 17, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 106
/
client.go
220 lines (168 loc) · 6.31 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
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必须 以 "/" 为前缀. 本函数 不对此进行任何检查
func NewClient(hostAddr, path string, headers *httpLayer.HeaderPreset, 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 (*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)
}