mirror of
https://github.com/e1732a364fed/v2ray_simple.git
synced 2025-10-11 11:40:08 +08:00

grpcSimple包的服务端和客户端现在都已完成,且兼容v2ray等内核。 grpcSimple包 简洁、高效,更加科学。暂不支持multiMode。 若 grpc_full 给出,则使用grpc包,否则默认使用 grpcSimple包。 若 noquic给出,则不使用 quic,否则 默认使用 quic。 修复 ws early 失效问题;
171 lines
5.5 KiB
Go
171 lines
5.5 KiB
Go
package ws
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/base64"
|
||
"io"
|
||
"net"
|
||
"net/http"
|
||
|
||
"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"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// 2048 /3 = 682.6666 ,
|
||
// 683 * 4 = 2732, 若你不信,运行 we_test.go中的 TestBase64Len
|
||
const MaxEarlyDataLen_Base64 = 2732
|
||
|
||
type Server struct {
|
||
//upgrader *ws.Upgrader
|
||
UseEarlyData bool
|
||
Thepath string
|
||
|
||
headers map[string][]string
|
||
}
|
||
|
||
// 这里默认: 传入的path必须 以 "/" 为前缀. 本函数 不对此进行任何检查.
|
||
func NewServer(path string, headers map[string][]string, UseEarlyData bool) *Server {
|
||
|
||
return &Server{
|
||
//upgrader: upgrader,
|
||
Thepath: path,
|
||
headers: headers,
|
||
UseEarlyData: UseEarlyData,
|
||
}
|
||
}
|
||
|
||
func (s *Server) GetPath() string {
|
||
return s.Thepath
|
||
}
|
||
|
||
func (*Server) IsMux() bool {
|
||
return false
|
||
}
|
||
|
||
func (*Server) IsSuper() bool {
|
||
return false
|
||
}
|
||
|
||
// Handshake 用于 websocket的 Server 监听端,建立握手. 用到了 gobwas/ws.Upgrader.
|
||
//
|
||
// 返回可直接用于读写 websocket 二进制数据的 net.Conn
|
||
func (s *Server) Handshake(optionalFirstBuffer *bytes.Buffer, underlay net.Conn) (net.Conn, error) {
|
||
|
||
theWrongPath := ""
|
||
var thePotentialEarlyData []byte
|
||
|
||
var theUpgrader *ws.Upgrader = &ws.Upgrader{
|
||
|
||
//因为我们vs的架构,先统一监听tcp;然后再调用Handshake函数
|
||
// 所以我们不能直接用http.Handle, 这也彰显了 用 gobwas/ws 包的好处
|
||
// 给Upgrader提供的 OnRequest 专门用于过滤 path, 也不需要我们的 httpLayer 去过滤
|
||
|
||
// 我们的 httpLayer 的 过滤方法仍然是最安全的,可以杜绝 所有非法数据;
|
||
// 而 ws.Upgrader.Upgrade 使用了 readLine 函数。如果客户提供一个非法的超长的一行的话,它就会陷入泥淖
|
||
// 这个以后 可以先用 httpLayer的过滤方法,过滤掉后,再用 MultiReader组装回来,提供给 upgrader.Upgrade
|
||
// ReadBufferSize默认是 4096,已经够大
|
||
|
||
OnRequest: func(uri []byte) error {
|
||
struri := string(uri)
|
||
if struri != s.Thepath {
|
||
|
||
theWrongPath = struri
|
||
|
||
//return utils.NewDataErr("ws path not match", nil, struri[:min])
|
||
//发现这个错误除了在程序里返回外,还会直接显示到 浏览器上!这会被探测到的。
|
||
// 所以只能显示标准http错误, 然后通过闭包的方式 把path信息传递到外部.
|
||
if ce := utils.CanLogWarn("ws path not match"); ce != nil {
|
||
min := len(s.Thepath)
|
||
if len(struri) < min {
|
||
min = len(struri)
|
||
}
|
||
//log.Println("ws path not match", struri[:min])
|
||
ce.Write(zap.String("wrong path", struri[:min]))
|
||
}
|
||
return ws.RejectConnectionError(ws.RejectionStatus(http.StatusBadRequest))
|
||
}
|
||
return nil
|
||
},
|
||
}
|
||
|
||
if len(s.headers) > 0 {
|
||
theUpgrader.Header = ws.HandshakeHeaderHTTP(s.headers)
|
||
}
|
||
|
||
if s.UseEarlyData {
|
||
|
||
//xray和v2ray中,使用了 header中的
|
||
// Sec-WebSocket-Protocol 字段 来传输 earlydata,来实现 0-rtt;我们为了兼容同样用此字段
|
||
// (websocket标准是没有定义 0-rtt的方法的,但是ws的握手包头部是可以自定义header的)
|
||
|
||
// gobwas 的upgrader 用 ProtocolCustom 这个函数来检查 protocol的内容
|
||
// 它会遍历客户端给出的所有 protocol,然后选择一个来返回
|
||
|
||
//我们若提供了此函数,则必须返回true,否则 gobwas会返回 ErrMalformedRequest 错误
|
||
theUpgrader.ProtocolCustom = func(b []byte) (string, bool) {
|
||
//如果不提供custom方法的话,gobwas会使用 httphead.ScanTokens 来扫描所有的token
|
||
// 其实就是扫描逗号分隔 的 字符串
|
||
|
||
//但是因为我们是 earlydata,所以没有逗号,全部都是 base64 编码的内容, 所以直接读然后解码即可
|
||
|
||
//还有要注意的是,因为这个是回调函数,所以需要是闭包 才能向我们实际连接储存数据,所以是无法直接放到通用的upgrader里的
|
||
|
||
if len(b) > MaxEarlyDataLen_Base64 {
|
||
return "", true
|
||
}
|
||
bs, err := base64.RawURLEncoding.DecodeString(string(b))
|
||
if err != nil {
|
||
// 传来的并不是base64数据,可能是其它访问我们网站websocket的情况,但是一般我们path复杂都会过滤掉,所以直接认为这是非法的
|
||
return "", false
|
||
}
|
||
//if len(bs) != 0 && utils.CanLogDebug() {
|
||
// log.Println("Got New ws earlydata", len(bs), bs)
|
||
//}
|
||
thePotentialEarlyData = bs
|
||
return "", true
|
||
}
|
||
|
||
}
|
||
|
||
var theReader io.Reader
|
||
if optionalFirstBuffer != nil {
|
||
theReader = io.MultiReader(optionalFirstBuffer, underlay)
|
||
} else {
|
||
theReader = underlay
|
||
}
|
||
|
||
rw := utils.RW{Reader: theReader, Writer: underlay}
|
||
|
||
_, err := theUpgrader.Upgrade(rw)
|
||
if err != nil {
|
||
if len(theWrongPath) > 0 {
|
||
//ws的Method必为Get, 否则 gobwas 会返回 ErrHandshakeBadMethod
|
||
return nil, &httpLayer.RequestErr{Path: theWrongPath}
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
//log.Println("thePotentialEarlyData", len(thePotentialEarlyData))
|
||
|
||
theConn := &Conn{
|
||
Conn: underlay,
|
||
underlayIsBasic: netLayer.IsBasicConn(underlay),
|
||
state: ws.StateServerSide,
|
||
//w: wsutil.NewWriter(underlay, ws.StateServerSide, ws.OpBinary),
|
||
r: wsutil.NewServerSideReader(underlay),
|
||
}
|
||
//不想客户端;服务端是不怕客户端在握手阶段传来任何多余数据的
|
||
// 因为我们还没实现 0-rtt
|
||
theConn.r.OnIntermediate = wsutil.ControlFrameHandler(underlay, ws.StateServerSide)
|
||
|
||
if len(thePotentialEarlyData) > 0 {
|
||
theConn.serverEndGotEarlyData = thePotentialEarlyData
|
||
}
|
||
|
||
return theConn, nil
|
||
}
|