完善对writev的支持;为vless和ws实现MultiWriter;修订代码

This commit is contained in:
hahahrfool
2022-03-22 22:39:26 +08:00
parent 536a84def6
commit 4d2bbaeceb
15 changed files with 267 additions and 42 deletions

14
main.go
View File

@@ -977,7 +977,12 @@ func tryRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, proxy_serve
if tlsLayer.PDD { if tlsLayer.PDD {
log.Println("SpliceRead R方向 退化……", wlcdc.R.GetFailReason()) log.Println("SpliceRead R方向 退化……", wlcdc.R.GetFailReason())
} }
io.Copy(wrc, wlc) if netLayer.UseReadv {
netLayer.TryCopy(wrc, wlc)
} else {
io.Copy(wrc, wlc)
}
return return
} }
@@ -1084,7 +1089,12 @@ func tryRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, proxy_serve
if tlsLayer.PDD { if tlsLayer.PDD {
log.Println("SpliceRead W方向 退化……", wlcdc.W.GetFailReason()) 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 return
} }

View File

@@ -7,3 +7,18 @@ Package netLayer contains definitions in network layer AND transport layer.
*/ */
package netLayer 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
}

View File

@@ -40,6 +40,13 @@ func TryCopy(writeConn io.Writer, readConn io.Reader) (allnum int64, err error)
var mr utils.MultiReader var mr utils.MultiReader
var buffers net.Buffers var buffers net.Buffers
var rawConn syscall.RawConn 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() { if utils.CanLogDebug() {
log.Println("TryCopy", reflect.TypeOf(readConn), "->", reflect.TypeOf(writeConn)) 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() { if utils.CanLogDebug() {
log.Println("copying with readv") log.Println("copying with readv")
} }
for { for {
buffers, err = ReadFromMultiReader(rawConn, mr) buffers, err = ReadFromMultiReader(rawConn, mr)
if err != nil { if err != nil {
return 0, err 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 allnum += num
if err2 != nil { if err2 != nil {
err = err2 err = err2
return return
} }
ReleaseNetBuffers(buffers) ReleaseNetBuffers(buffers)
} }
classic: classic:

View File

@@ -113,7 +113,7 @@ type ProxyCommon interface {
//use dc.Host, dc.Insecure, dc.Utls //use dc.Host, dc.Insecure, dc.Utls
func prepareTLS_forClient(com ProxyCommon, dc *DialConf) error { 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 return nil
} }
@@ -141,7 +141,7 @@ func prepareTLS_forProxyCommon_withURL(u *url.URL, isclient bool, com ProxyCommo
if isclient { if isclient {
utlsStr := u.Query().Get("utls") utlsStr := u.Query().Get("utls")
useUtls := utlsStr != "" && utlsStr != "false" && utlsStr != "0" 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 { } else {
certFile := u.Query().Get("cert") certFile := u.Query().Get("cert")

View File

@@ -172,10 +172,11 @@ func (c *Client) Handshake(underlay net.Conn, target *netLayer.Addr) (io.ReadWri
utils.PutBuf(buf) utils.PutBuf(buf)
return &UserConn{ return &UserConn{
Conn: underlay, Conn: underlay,
uuid: *c.user, uuid: *c.user,
version: c.version, version: c.version,
isUDP: target.Network == "udp", isUDP: target.Network == "udp",
underlayIsBasic: netLayer.IsBasicConn(underlay),
}, err }, err
} }

View File

@@ -390,6 +390,7 @@ realPart:
uuid: thisUUIDBytes, uuid: thisUUIDBytes,
version: int(version), version: int(version),
isUDP: addr.Network == "udp", isUDP: addr.Network == "udp",
underlayIsBasic: netLayer.IsBasicConn(underlay),
isServerEnd: true, isServerEnd: true,
}, addr, nil }, addr, nil

View File

@@ -30,12 +30,15 @@ const (
) )
//实现 net.Conn 以及 utils.MultiWriter
type UserConn struct { type UserConn struct {
net.Conn net.Conn
optionalReader io.Reader //在使用了缓存读取握手包头后就产生了buffer中有剩余数据的可能性此时就要使用MultiReader optionalReader io.Reader //在使用了缓存读取握手包头后就产生了buffer中有剩余数据的可能性此时就要使用MultiReader
remainFirstBufLen int //记录读取握手包头时读到的buf的长度. 如果我们读超过了这个部分的话,实际上我们就可以不再使用 optionalReader 读取, 而是直接从Conn读取 remainFirstBufLen int //记录读取握手包头时读到的buf的长度. 如果我们读超过了这个部分的话,实际上我们就可以不再使用 optionalReader 读取, 而是直接从Conn读取
underlayIsBasic bool
uuid [16]byte uuid [16]byte
convertedUUIDStr string convertedUUIDStr string
version int version int
@@ -62,6 +65,36 @@ func (uc *UserConn) GetIdentityStr() string {
return uc.convertedUUIDStr 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则是多线程不安全的如果是tcp则安不安全看底层的链接。
// 这里规定如果是UDP则 每Write一遍都要Write一个 完整的UDP 数据包 // 这里规定如果是UDP则 每Write一遍都要Write一个 完整的UDP 数据包
func (uc *UserConn) Write(p []byte) (int, error) { func (uc *UserConn) Write(p []byte) (int, error) {

View File

@@ -10,22 +10,25 @@ import (
utls "github.com/refraction-networking/utls" utls "github.com/refraction-networking/utls"
) )
// 关于utls的简单分析可参考
//https://github.com/hahahrfool/v2ray_simple/discussions/7
type Client struct { type Client struct {
tlsConfig *tls.Config 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{ c := &Client{
tlsConfig: &tls.Config{ tlsConfig: &tls.Config{
InsecureSkipVerify: insecure, InsecureSkipVerify: insecure,
ServerName: host, 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) 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) { func (c *Client) Handshake(underlay net.Conn) (tlsConn *Conn, err error) {
if c.useTls { if c.use_uTls {
utlsConn := utls.UClient(underlay, &utls.Config{ utlsConn := utls.UClient(underlay, &utls.Config{
InsecureSkipVerify: c.tlsConfig.InsecureSkipVerify, InsecureSkipVerify: c.tlsConfig.InsecureSkipVerify,
ServerName: c.tlsConfig.ServerName, ServerName: c.tlsConfig.ServerName,

View File

@@ -1,3 +1,4 @@
// Package utils provides utils that needed by all sub-packages in verysimle
package utils package utils
import "flag" import "flag"

94
utils/multi.go Normal file
View File

@@ -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
}

View File

@@ -67,17 +67,17 @@ func PutPacket(bs []byte) {
c := cap(bs) c := cap(bs)
if c < MaxBufLen { if c < MaxBufLen {
if c >= StandardBytesLength { if c >= StandardBytesLength {
standardBytesPool.Put(bs[:c]) standardBytesPool.Put(bs[:StandardBytesLength])
} }
return return
} }
standardPacketPool.Put(bs[:c]) standardPacketPool.Put(bs[:MaxBufLen])
} }
// 从Pool中获取一个 StandardBytesLength 长度的 []byte // 从Pool中获取一个 StandardBytesLength 长度的 []byte
func GetMTU() []byte { func GetMTU() []byte {
return standardBytesPool.Get().([]byte)[:StandardBytesLength] return standardBytesPool.Get().([]byte)
} }
// 从pool中获取 []byte, 根据给出长度不同来源于的Pool会不同. // 从pool中获取 []byte, 根据给出长度不同来源于的Pool会不同.
@@ -98,8 +98,8 @@ func PutBytes(bs []byte) {
return return
} else if c >= StandardBytesLength && c < MaxBufLen { } else if c >= StandardBytesLength && c < MaxBufLen {
standardBytesPool.Put(bs[:c]) standardBytesPool.Put(bs[:StandardBytesLength])
} else if c >= MaxBufLen { } else if c >= MaxBufLen {
standardPacketPool.Put(bs[:c]) standardPacketPool.Put(bs[:MaxBufLen])
} }
} }

View File

@@ -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()
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/gobwas/ws" "github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil" "github.com/gobwas/ws/wsutil"
"github.com/hahahrfool/v2ray_simple/netLayer"
"github.com/hahahrfool/v2ray_simple/utils" "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只是负责读最初的握手部分 // 但是仔细思考发现tls握手是在websocket的外部发生的而我们传输的是数据的内层tls握手那么就和Dialer没关系了dialer只是负责读最初的握手部分
// 所以我们就算要配置buffer尺寸也不是在这里配置而是要配置 theConn.w 的buffer // 所以我们就算要配置buffer尺寸也不是在这里配置而是要配置 theConn.w 的buffer
//const bufsize = 1024 * 10
d := ws.Dialer{ d := ws.Dialer{
//ReadBufferSize: bufsize,
//WriteBufferSize: bufsize,
NetDial: func(ctx context.Context, net, addr string) (net.Conn, error) { NetDial: func(ctx context.Context, net, addr string) (net.Conn, error) {
return underlay, nil return underlay, nil
}, },
@@ -64,11 +62,12 @@ func (c *Client) Handshake(underlay net.Conn) (net.Conn, error) {
// 所以我们的用例中br一定是nil // 所以我们的用例中br一定是nil
theConn := &Conn{ theConn := &Conn{
Conn: underlay, Conn: underlay,
state: ws.StateClientSide, state: ws.StateClientSide,
underlayIsBasic: netLayer.IsBasicConn(underlay),
//w: wsutil.NewWriter(underlay, ws.StateClientSide, ws.OpBinary), //w: wsutil.NewWriter(underlay, ws.StateClientSide, ws.OpBinary),
} }
//theConn.w.DisableFlush() //发现使用ws分片功能的话会出问题,所以就先关了. 搞清楚分片的问题再说。 //theConn.w.DisableFlush() //使用ws分片功能会降低性能
// 根据 gobwas/ws的代码在服务器没有返回任何数据时br为nil // 根据 gobwas/ws的代码在服务器没有返回任何数据时br为nil
if br == nil { if br == nil {
@@ -153,11 +152,12 @@ func (edc *EarlyDataConn) Write(p []byte) (int, error) {
utils.PutBuf(outBuf) utils.PutBuf(outBuf)
theConn := &Conn{ theConn := &Conn{
Conn: edc.Conn, Conn: edc.Conn,
state: ws.StateClientSide, state: ws.StateClientSide,
underlayIsBasic: netLayer.IsBasicConn(edc.Conn),
} }
//实测总是 br==nil就算发送了earlydata也是如此 //实测总是 br==nil就算发送了earlydata也是如此;不过理论上有可能粘包,只要远程目标服务器的响应够快
if br == nil { if br == nil {
//log.Println(" br == nil") //log.Println(" br == nil")

View File

@@ -9,6 +9,7 @@ import (
"github.com/hahahrfool/v2ray_simple/utils" "github.com/hahahrfool/v2ray_simple/utils"
) )
// 实现 net.Conn 以及 utils.MultiWriter
// 因为 gobwas/ws 不包装conn在写入和读取二进制时需要使用 较为底层的函数才行并未被提供标准的Read和Write // 因为 gobwas/ws 不包装conn在写入和读取二进制时需要使用 较为底层的函数才行并未被提供标准的Read和Write
// 因此我们包装一下统一使用Read和Write函数 来读写 二进制数据。因为我们这里是代理, // 因此我们包装一下统一使用Read和Write函数 来读写 二进制数据。因为我们这里是代理,
// 所以我们默认 抛弃 websocket的 数据帧 长度。 // 所以我们默认 抛弃 websocket的 数据帧 长度。
@@ -24,6 +25,8 @@ type Conn struct {
remainLenForLastFrame int64 remainLenForLastFrame int64
serverEndGotEarlyData []byte serverEndGotEarlyData []byte
underlayIsBasic bool
} }
//Read websocket binary frames //Read websocket binary frames
@@ -114,6 +117,60 @@ func (c *Conn) Read(p []byte) (int, error) {
return n, nil 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 //Write websocket binary frames
func (c *Conn) Write(p []byte) (n int, e error) { func (c *Conn) Write(p []byte) (n int, e error) {
//log.Println("ws Write called", len(p)) //log.Println("ws Write called", len(p))
@@ -122,7 +179,7 @@ func (c *Conn) Write(p []byte) (n int, e error) {
// 不分片的效率更高,因为无需缓存,zero copy // 不分片的效率更高,因为无需缓存,zero copy
if c.state == ws.StateClientSide { if c.state == ws.StateClientSide {
e = wsutil.WriteClientBinary(c.Conn, p) e = wsutil.WriteClientBinary(c.Conn, p) //实际我查看它的代码发现Client端 最终调用到的 writeFrame 函数 还是多了一次拷贝; 它是为了防止篡改客户数据;但是我们代理的话不会使用数据,只是转发而已
} else { } else {
e = wsutil.WriteServerBinary(c.Conn, p) e = wsutil.WriteServerBinary(c.Conn, p)
} }
@@ -159,8 +216,6 @@ func (c *Conn) Write(p []byte) (n int, e error) {
// 在调用了 DisableFlush 方法后,还是必须要调用 Flush, 否则还是什么也不写入 // 在调用了 DisableFlush 方法后,还是必须要调用 Flush, 否则还是什么也不写入
e = c.w.Flush() e = c.w.Flush()
//似乎Flush之后还要Reset不知道是不是没Reset 导致了 分片时 读取出问题的情况
} }
//log.Println("ws Write finish", n, e) //log.Println("ws Write finish", n, e)
return return

View File

@@ -6,6 +6,7 @@ import (
"github.com/gobwas/ws" "github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil" "github.com/gobwas/ws/wsutil"
"github.com/hahahrfool/v2ray_simple/netLayer"
"github.com/hahahrfool/v2ray_simple/utils" "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)) //log.Println("thePotentialEarlyData", len(thePotentialEarlyData))
theConn := &Conn{ theConn := &Conn{
Conn: underlay, Conn: underlay,
state: ws.StateServerSide, underlayIsBasic: netLayer.IsBasicConn(underlay),
state: ws.StateServerSide,
//w: wsutil.NewWriter(underlay, ws.StateServerSide, ws.OpBinary), //w: wsutil.NewWriter(underlay, ws.StateServerSide, ws.OpBinary),
r: wsutil.NewServerSideReader(underlay), r: wsutil.NewServerSideReader(underlay),
} }