diff --git a/main.go b/main.go index d5afd1e..29dbcd7 100644 --- a/main.go +++ b/main.go @@ -977,7 +977,12 @@ func tryRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, proxy_serve if tlsLayer.PDD { log.Println("SpliceRead R方向 退化……", wlcdc.R.GetFailReason()) } - io.Copy(wrc, wlc) + if netLayer.UseReadv { + netLayer.TryCopy(wrc, wlc) + } else { + io.Copy(wrc, wlc) + + } return } @@ -1084,7 +1089,12 @@ func tryRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, proxy_serve if tlsLayer.PDD { log.Println("SpliceRead W方向 退化……", wlcdc.W.GetFailReason()) } - io.Copy(wlc, wrc) + if netLayer.UseReadv { //就算不用splice, 一样可以用readv来在读那一端增强性能 + netLayer.TryCopy(wlc, wrc) + } else { + io.Copy(wlc, wrc) + + } return } diff --git a/netLayer/netlayer.go b/netLayer/netlayer.go index a818af8..7e5d017 100644 --- a/netLayer/netlayer.go +++ b/netLayer/netlayer.go @@ -7,3 +7,18 @@ Package netLayer contains definitions in network layer AND transport layer. */ package netLayer + +import "net" + +func IsBasicConn(r interface{}) bool { + switch r.(type) { + case *net.TCPConn: + return true + case *net.UDPConn: + return true + case *net.UnixConn: + return true + } + + return false +} diff --git a/netLayer/relay.go b/netLayer/relay.go index ea029f2..74ee813 100644 --- a/netLayer/relay.go +++ b/netLayer/relay.go @@ -40,6 +40,13 @@ func TryCopy(writeConn io.Writer, readConn io.Reader) (allnum int64, err error) var mr utils.MultiReader var buffers net.Buffers var rawConn syscall.RawConn + var isWriteConn_a_MultiWriter bool + var multiWriter utils.MultiWriter + isWriteConnBasic := IsBasicConn(writeConn) + + if !isWriteConnBasic { + multiWriter, isWriteConn_a_MultiWriter = writeConn.(utils.MultiWriter) + } if utils.CanLogDebug() { log.Println("TryCopy", reflect.TypeOf(readConn), "->", reflect.TypeOf(writeConn)) @@ -67,17 +74,29 @@ func TryCopy(writeConn io.Writer, readConn io.Reader) (allnum int64, err error) if utils.CanLogDebug() { log.Println("copying with readv") } + for { buffers, err = ReadFromMultiReader(rawConn, mr) if err != nil { return 0, err } - num, err2 := buffers.WriteTo(writeConn) + var num int64 + var err2 error + + //如vless协议,肯定走这里,因为 vless.UserConn 实现了 utils.MultiWriter + if isWriteConn_a_MultiWriter { + num, err2 = multiWriter.WriteBuffers(buffers) + + } else { + num, err2 = buffers.WriteTo(writeConn) + } + allnum += num if err2 != nil { err = err2 return } + ReleaseNetBuffers(buffers) } classic: diff --git a/proxy/proxy.go b/proxy/proxy.go index 33d756b..a00e604 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -113,7 +113,7 @@ type ProxyCommon interface { //use dc.Host, dc.Insecure, dc.Utls func prepareTLS_forClient(com ProxyCommon, dc *DialConf) error { - com.setTLS_Client(tlsLayer.NewTlsClient(dc.Host, dc.Insecure, dc.Utls)) + com.setTLS_Client(tlsLayer.NewClient(dc.Host, dc.Insecure, dc.Utls)) return nil } @@ -141,7 +141,7 @@ func prepareTLS_forProxyCommon_withURL(u *url.URL, isclient bool, com ProxyCommo if isclient { utlsStr := u.Query().Get("utls") useUtls := utlsStr != "" && utlsStr != "false" && utlsStr != "0" - com.setTLS_Client(tlsLayer.NewTlsClient(u.Host, insecure, useUtls)) + com.setTLS_Client(tlsLayer.NewClient(u.Host, insecure, useUtls)) } else { certFile := u.Query().Get("cert") diff --git a/proxy/vless/client.go b/proxy/vless/client.go index f405258..37ce3d5 100644 --- a/proxy/vless/client.go +++ b/proxy/vless/client.go @@ -172,10 +172,11 @@ func (c *Client) Handshake(underlay net.Conn, target *netLayer.Addr) (io.ReadWri utils.PutBuf(buf) return &UserConn{ - Conn: underlay, - uuid: *c.user, - version: c.version, - isUDP: target.Network == "udp", + Conn: underlay, + uuid: *c.user, + version: c.version, + isUDP: target.Network == "udp", + underlayIsBasic: netLayer.IsBasicConn(underlay), }, err } diff --git a/proxy/vless/server.go b/proxy/vless/server.go index bfd6ed7..98990e5 100644 --- a/proxy/vless/server.go +++ b/proxy/vless/server.go @@ -390,6 +390,7 @@ realPart: uuid: thisUUIDBytes, version: int(version), isUDP: addr.Network == "udp", + underlayIsBasic: netLayer.IsBasicConn(underlay), isServerEnd: true, }, addr, nil diff --git a/proxy/vless/vless.go b/proxy/vless/vless.go index 8ee8fea..e522155 100644 --- a/proxy/vless/vless.go +++ b/proxy/vless/vless.go @@ -30,12 +30,15 @@ const ( ) +//实现 net.Conn 以及 utils.MultiWriter type UserConn struct { net.Conn optionalReader io.Reader //在使用了缓存读取握手包头后,就产生了buffer中有剩余数据的可能性,此时就要使用MultiReader remainFirstBufLen int //记录读取握手包头时读到的buf的长度. 如果我们读超过了这个部分的话,实际上我们就可以不再使用 optionalReader 读取, 而是直接从Conn读取 + underlayIsBasic bool + uuid [16]byte convertedUUIDStr string version int @@ -62,6 +65,36 @@ func (uc *UserConn) GetIdentityStr() string { return uc.convertedUUIDStr } +func (c *UserConn) canDirectWrite() bool { + return c.version == 1 && !c.isUDP || c.version == 0 && !(c.isServerEnd && !c.isntFirstPacket) +} + +func (c *UserConn) WriteBuffers(buffers [][]byte) (int64, error) { + + if c.canDirectWrite() { + + //底层连接可以是 ws,或者 tls,或者 基本连接 + + //本作的 ws.Conn 实现了 utils.MultiWriter + + if c.underlayIsBasic { + nb := net.Buffers(buffers) + return nb.WriteTo(c.Conn) + + } else if mr, ok := c.Conn.(utils.MultiWriter); ok { + return mr.WriteBuffers(buffers) + } + } + + bigbs, dup := utils.MergeBuffers(buffers) + n, e := c.Write(bigbs) + if dup { + utils.PutPacket(bigbs) + } + return int64(n), e + +} + //如果是udp,则是多线程不安全的,如果是tcp,则安不安全看底层的链接。 // 这里规定,如果是UDP,则 每Write一遍,都要Write一个 完整的UDP 数据包 func (uc *UserConn) Write(p []byte) (int, error) { diff --git a/tlsLayer/client.go b/tlsLayer/client.go index 93113c9..47de1e2 100644 --- a/tlsLayer/client.go +++ b/tlsLayer/client.go @@ -10,22 +10,25 @@ import ( utls "github.com/refraction-networking/utls" ) +// 关于utls的简单分析,可参考 +//https://github.com/hahahrfool/v2ray_simple/discussions/7 + type Client struct { tlsConfig *tls.Config - useTls bool + use_uTls bool } -func NewTlsClient(host string, insecure bool, useTls bool) *Client { +func NewClient(host string, insecure bool, use_uTls bool) *Client { c := &Client{ tlsConfig: &tls.Config{ InsecureSkipVerify: insecure, ServerName: host, }, - useTls: useTls, + use_uTls: use_uTls, } - if useTls && utils.CanLogInfo() { + if use_uTls && utils.CanLogInfo() { log.Println("using utls and Chrome fingerprint for", host) } @@ -34,7 +37,7 @@ func NewTlsClient(host string, insecure bool, useTls bool) *Client { func (c *Client) Handshake(underlay net.Conn) (tlsConn *Conn, err error) { - if c.useTls { + if c.use_uTls { utlsConn := utls.UClient(underlay, &utls.Config{ InsecureSkipVerify: c.tlsConfig.InsecureSkipVerify, ServerName: c.tlsConfig.ServerName, diff --git a/utils/log.go b/utils/log.go index f26d9c0..1cd4d51 100644 --- a/utils/log.go +++ b/utils/log.go @@ -1,3 +1,4 @@ +// Package utils provides utils that needed by all sub-packages in verysimle package utils import "flag" diff --git a/utils/multi.go b/utils/multi.go new file mode 100644 index 0000000..f95e60f --- /dev/null +++ b/utils/multi.go @@ -0,0 +1,94 @@ +package utils + +import "log" + +// 该 MultiReader 的用例请参照 netLayer.ReadFromMultiReader , 在 netLayer/readv.go中 +//具体实现见 readv_*.go; 用 GetReadVReader() 函数来获取本平台的对应实现。 +type MultiReader interface { + Init([][]byte) + Read(fd uintptr) int32 + Clear() +} + +// 因为 net.Buffers 的 WriteTo方法只会查看其是否实现了net包私有的 writeBuffers 接口 +// 我们无法用WriteTo来给其它 代码提升性能;因此我们重新定义一个新的借口, 实现了 MultiWriter +// 接口的结构 我们就认为它会提升性能,比直接用 net.Buffers.WriteTo 要更强. +/* + 本接口 在代理中的用途,基本上只适合 加密层 能够做到分组加密 的情况; 因为如果不加密的话就是裸协议,直接就splice或者writev了,也不需要这么麻烦; + + 如果是tls的话,可能涉及自己魔改tls把私有函数暴露出来然后分组加密; + + 如果是vmess的话,倒是有可能的,不过我还没研究vmess的 加密细节; + + 而如果是ss 那种简单混淆 异或加密的话,则是完全可以的 + + 分组加密然后 一起用 writev 发送出去,可以降低网络延迟, 不过writev性能的提升可能是非常细微的, 也不必纠结这里. + + 如果考虑另一种情况,即需要加包头和包尾,则区别就很大了; + + WriteTo会调用N次Write,如果包装的话,会包装N 个包头和 包尾;而如果我们实现WriteBuffers方法, + 只需先写入包头,而在 最后一个 []byte 后加 包尾,那么就可以获得性能提升, + 我们只需增添两个新的 []byte 放在其前后即可, 然后再用 writev 一起发送出去 + + 那么实际上 websocket 的gobwas/ws 包在不开启缓存时,就是 每次Write都写一次包头的情况; + + 所以websocket很有必要实现 WriteBuffers 方法. + + 目前实现 的有 vless.UserConn 和 ws.Conn +*/ +type MultiWriter interface { + WriteBuffers([][]byte) (int64, error) +} + +func BuffersLen(bs [][]byte) (allnum int) { + if len(bs) < 1 { + return 0 + } + for _, b := range bs { + allnum += len(b) + } + return allnum +} + +func PrintBuffers(bs [][]byte) { + for i, b := range bs { + log.Println(i, b) + } +} + +// 如果 分配了新内存来 包含数据,则 duplicate ==true; 如果利用了原有的第一个[]byte, 则 duplicate==false +// 如果 duplicate==false, 不要 使用 PutPacket等方法放入Pool; +// 因为 在更上级的调用会试图去把 整个bs 放入pool; +func MergeBuffers(bs [][]byte) (result []byte, duplicate bool) { + if len(bs) < 1 { + return + } + b0 := bs[0] + if len(bs) == 1 { + return b0, false + } + allLen := BuffersLen(bs) + + if allLen <= cap(b0) { //所有的长度 小于第一个的cap,那么可以全放入第一个中;实际readv不会出现这种情况 + b0 = b0[:allLen] + cursor := len(b0) + for i := 1; i < len(bs); i++ { + cursor += copy(b0[cursor:], bs[i]) + } + return b0, false + } + + if allLen <= MaxBufLen { + result = GetPacket() + + } else { + result = make([]byte, allLen) //实际目前的readv实现也很难出现这种情况, 默认16个1500是不会出这种情况的 + } + + cursor := 0 + for i := 0; i < len(bs); i++ { + cursor += copy(result[cursor:], bs[i]) + } + + return result[:allLen], true +} diff --git a/utils/pool.go b/utils/pool.go index 5705d32..f26ca55 100644 --- a/utils/pool.go +++ b/utils/pool.go @@ -67,17 +67,17 @@ func PutPacket(bs []byte) { c := cap(bs) if c < MaxBufLen { if c >= StandardBytesLength { - standardBytesPool.Put(bs[:c]) + standardBytesPool.Put(bs[:StandardBytesLength]) } return } - standardPacketPool.Put(bs[:c]) + standardPacketPool.Put(bs[:MaxBufLen]) } // 从Pool中获取一个 StandardBytesLength 长度的 []byte func GetMTU() []byte { - return standardBytesPool.Get().([]byte)[:StandardBytesLength] + return standardBytesPool.Get().([]byte) } // 从pool中获取 []byte, 根据给出长度不同,来源于的Pool会不同. @@ -98,8 +98,8 @@ func PutBytes(bs []byte) { return } else if c >= StandardBytesLength && c < MaxBufLen { - standardBytesPool.Put(bs[:c]) + standardBytesPool.Put(bs[:StandardBytesLength]) } else if c >= MaxBufLen { - standardPacketPool.Put(bs[:c]) + standardPacketPool.Put(bs[:MaxBufLen]) } } diff --git a/utils/utils.go b/utils/utils.go deleted file mode 100644 index 1591b22..0000000 --- a/utils/utils.go +++ /dev/null @@ -1,9 +0,0 @@ -// Package utils provides utils that needed by all sub-packages in verysimle -package utils - -//具体实现见 readv_*.go; 用 GetReadVReader() 函数来获取本平台的对应实现。 -type MultiReader interface { - Init([][]byte) - Read(fd uintptr) int32 - Clear() -} diff --git a/ws/client.go b/ws/client.go index 70b5105..a6026cf 100644 --- a/ws/client.go +++ b/ws/client.go @@ -11,6 +11,7 @@ import ( "github.com/gobwas/ws" "github.com/gobwas/ws/wsutil" + "github.com/hahahrfool/v2ray_simple/netLayer" "github.com/hahahrfool/v2ray_simple/utils" ) @@ -44,10 +45,7 @@ func (c *Client) Handshake(underlay net.Conn) (net.Conn, error) { // 但是仔细思考,发现tls握手是在websocket的外部发生的,而我们传输的是数据的内层tls握手,那么就和Dialer没关系了,dialer只是负责读最初的握手部分; // 所以我们就算要配置buffer尺寸,也不是在这里配置,而是要配置 theConn.w 的buffer - //const bufsize = 1024 * 10 d := ws.Dialer{ - //ReadBufferSize: bufsize, - //WriteBufferSize: bufsize, NetDial: func(ctx context.Context, net, addr string) (net.Conn, error) { return underlay, nil }, @@ -64,11 +62,12 @@ func (c *Client) Handshake(underlay net.Conn) (net.Conn, error) { // 所以我们的用例中,br一定是nil theConn := &Conn{ - Conn: underlay, - state: ws.StateClientSide, + Conn: underlay, + state: ws.StateClientSide, + underlayIsBasic: netLayer.IsBasicConn(underlay), //w: wsutil.NewWriter(underlay, ws.StateClientSide, ws.OpBinary), } - //theConn.w.DisableFlush() //发现使用ws分片功能的话会出问题,所以就先关了. 搞清楚分片的问题再说。 + //theConn.w.DisableFlush() //使用ws分片功能会降低性能 // 根据 gobwas/ws的代码,在服务器没有返回任何数据时,br为nil if br == nil { @@ -153,11 +152,12 @@ func (edc *EarlyDataConn) Write(p []byte) (int, error) { utils.PutBuf(outBuf) theConn := &Conn{ - Conn: edc.Conn, - state: ws.StateClientSide, + Conn: edc.Conn, + state: ws.StateClientSide, + underlayIsBasic: netLayer.IsBasicConn(edc.Conn), } - //实测总是 br==nil,就算发送了earlydata也是如此 + //实测总是 br==nil,就算发送了earlydata也是如此;不过理论上有可能粘包,只要远程目标服务器的响应够快 if br == nil { //log.Println(" br == nil") diff --git a/ws/conn.go b/ws/conn.go index 82c9943..72fac93 100644 --- a/ws/conn.go +++ b/ws/conn.go @@ -9,6 +9,7 @@ import ( "github.com/hahahrfool/v2ray_simple/utils" ) +// 实现 net.Conn 以及 utils.MultiWriter // 因为 gobwas/ws 不包装conn,在写入和读取二进制时需要使用 较为底层的函数才行,并未被提供标准的Read和Write // 因此我们包装一下,统一使用Read和Write函数 来读写 二进制数据。因为我们这里是代理, // 所以我们默认 抛弃 websocket的 数据帧 长度。 @@ -24,6 +25,8 @@ type Conn struct { remainLenForLastFrame int64 serverEndGotEarlyData []byte + + underlayIsBasic bool } //Read websocket binary frames @@ -114,6 +117,60 @@ func (c *Conn) Read(p []byte) (int, error) { return n, nil } +//实现 utils.MultiWriter +// 主要是针对一串数据的情况,如果底层连接可以用writev, 此时我们不要每一小段都包包头 然后写N次, +// 而是只在最前面包数据头,然后即可用writev 一次发送出去 +// 比如从 socks5 读数据,写入 tcp +ws + vless 协议, 就是这种情况 +// 若底层是tls,那我们也合并再发出,这样能少些很多头部,也能减少Write次数 +func (c *Conn) WriteBuffers(buffers [][]byte) (int64, error) { + + nb := net.Buffers(buffers) + if c.underlayIsBasic { + allLen := utils.BuffersLen(buffers) + + if c.state == ws.StateClientSide { + + //如果是客户端,需要将全部数据进行掩码处理,超烦人的! + //我们直接将所有数据合并到一起, 然后自行写入 frame, 而不是使用 wsutil的函数,能省内存拷贝开销 + + bigbs, dup := utils.MergeBuffers(buffers) + frame := ws.NewFrame(ws.OpBinary, true, bigbs) + frame = ws.MaskFrameInPlace(frame) + e := ws.WriteFrame(c.Conn, frame) + if dup { + utils.PutPacket(bigbs) + } + + if e != nil { + return 0, e + } + return int64(allLen), nil + } else { + wsH := ws.Header{ + Fin: true, + OpCode: ws.OpBinary, + Length: int64(allLen), + } + + e := ws.WriteHeader(c.Conn, wsH) + if e != nil { + return 0, e + } + return nb.WriteTo(c.Conn) + } + + } else { + + bigbs, dup := utils.MergeBuffers(buffers) + n, e := c.Write(bigbs) + if dup { + utils.PutPacket(bigbs) + + } + return int64(n), e + } +} + //Write websocket binary frames func (c *Conn) Write(p []byte) (n int, e error) { //log.Println("ws Write called", len(p)) @@ -122,7 +179,7 @@ func (c *Conn) Write(p []byte) (n int, e error) { // 不分片的效率更高,因为无需缓存,zero copy if c.state == ws.StateClientSide { - e = wsutil.WriteClientBinary(c.Conn, p) + e = wsutil.WriteClientBinary(c.Conn, p) //实际我查看它的代码,发现Client端 最终调用到的 writeFrame 函数 还是多了一次拷贝; 它是为了防止篡改客户数据;但是我们代理的话不会使用数据,只是转发而已 } else { e = wsutil.WriteServerBinary(c.Conn, p) } @@ -159,8 +216,6 @@ func (c *Conn) Write(p []byte) (n int, e error) { // 在调用了 DisableFlush 方法后,还是必须要调用 Flush, 否则还是什么也不写入 e = c.w.Flush() - - //似乎Flush之后还要Reset?不知道是不是没Reset 导致了 分片时 读取出问题的情况 } //log.Println("ws Write finish", n, e) return diff --git a/ws/server.go b/ws/server.go index f59ebe4..de38d98 100644 --- a/ws/server.go +++ b/ws/server.go @@ -6,6 +6,7 @@ import ( "github.com/gobwas/ws" "github.com/gobwas/ws/wsutil" + "github.com/hahahrfool/v2ray_simple/netLayer" "github.com/hahahrfool/v2ray_simple/utils" ) @@ -105,8 +106,9 @@ func (s *Server) Handshake(underlay net.Conn) (net.Conn, error) { //log.Println("thePotentialEarlyData", len(thePotentialEarlyData)) theConn := &Conn{ - Conn: underlay, - state: ws.StateServerSide, + Conn: underlay, + underlayIsBasic: netLayer.IsBasicConn(underlay), + state: ws.StateServerSide, //w: wsutil.NewWriter(underlay, ws.StateServerSide, ws.OpBinary), r: wsutil.NewServerSideReader(underlay), }