Files
v2ray_simple/advLayer/ws/server.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

171 lines
5.5 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"
"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
}