Files
v2ray_simple/netLayer/relay.go
2023-04-10 07:09:30 +08:00

387 lines
8.7 KiB
Go
Raw Permalink 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 netLayer
import (
"io"
"net"
"reflect"
"syscall"
"github.com/e1732a364fed/v2ray_simple/utils"
"go.uber.org/zap"
)
func CanWEverSplice(writeConn io.Writer) (wCanSplice bool) {
wCanSpliceDirectly := CanWSplice(writeConn)
if wCanSpliceDirectly {
wCanSplice = true
} else {
if CanSpliceEventually(writeConn) {
wCanSplice = true
}
}
return
}
// TryCopy 尝试 循环 从 readConn 读取数据并写入 writeConn, 直到错误发生。
// 会接连尝试 splice、循环readv 以及 原始Copy方法。如果 UseReadv 的值为false则不会使用readv。
//
// identity只用于debug 日志输出.
func TryCopy(writeConn io.Writer, readConn io.Reader, id uint32) (allnum int64, err error) {
var multiWriter utils.MultiWriter
var rawReadConn syscall.RawConn
var isWriteConn_MultiWriter bool
var mr utils.MultiReader
var br utils.BuffersReader
var userlevel_readv bool
readvType := 0
if ce := utils.CanLogDebug("TryCopy"); ce != nil {
ce.Write(
zap.Uint32("id", id),
zap.String("from", reflect.TypeOf(readConn).String()),
zap.String("->", reflect.TypeOf(writeConn).String()),
)
}
if SystemCanSplice {
rCanSplice := CanRSplice(readConn)
wCanSplice := CanWEverSplice(writeConn)
if wCanSplice {
if rCanSplice {
if ce := utils.CanLogDebug("copying with splice"); ce != nil {
ce.Write(zap.Uint32("id", id))
}
goto copy
} else if sr, ok := (readConn).(SpliceReader); ok && sr != nil {
if sr.EverPossibleToSpliceRead() {
if ce := utils.CanLogDebug("copying with splice, waiting spliceReader"); ce != nil {
ce.Write(zap.Uint32("id", id))
}
for {
canRS, tcpConn, unixConn := sr.CanSpliceRead()
if canRS {
if tcpConn != nil {
readConn = tcpConn
} else if unixConn != nil {
readConn = unixConn
}
if ce := utils.CanLogDebug("copying with splice, spliceReader ok"); ce != nil {
ce.Write(zap.Uint32("id", id))
}
goto copy
}
//在没能得到用于spliceRead的conn的时候先普通拷贝
bs := utils.GetPacket()
n, err1 := readConn.Read(bs)
if err1 != nil {
err = err1
return
}
n2, err2 := writeConn.Write(bs[:n])
allnum += int64(n2)
if err2 != nil {
err = err2
return
}
}
}
}
}
}
// 不全 支持splice的话我们就考虑 read端 可 readv 的情况
// 连readv都不让 那就直接 经典拷贝
if !UseReadv {
goto classic
}
rawReadConn = GetRawConn(readConn)
if rawReadConn == nil {
var ok bool
mr, ok = readConn.(utils.MultiReader)
if ok {
readvType = mr.WillReadBuffersBenifit()
if readvType != 0 {
userlevel_readv = true
} else {
goto classic
}
} else {
goto classic
}
}
if !IsBasicConn(writeConn) {
multiWriter, isWriteConn_MultiWriter = writeConn.(utils.MultiWriter)
if userlevel_readv && !isWriteConn_MultiWriter {
goto classic
}
}
if ce := utils.CanLogDebug("copying with readv"); ce != nil {
if userlevel_readv {
ce.Write(zap.Uint32("id", id),
zap.String("with", "buffersReader"))
} else {
ce.Write(zap.Uint32("id", id))
}
}
{
var readv_mem *utils.ReadvMem
if !userlevel_readv {
readv_mem = utils.Get_readvMem()
defer utils.Put_readvMem(readv_mem)
} else {
//循环读写直到 CanMultiRead 返回 true
bs := utils.GetPacket()
for {
if mr.CanMultiRead() {
break
}
var n int
n, err = readConn.Read(bs)
if err != nil {
return
}
n, err = writeConn.Write(bs[:n])
allnum += int64(n)
if err != nil {
return
}
}
utils.PutPacket(bs)
if readvType == 2 {
userlevel_readv = false
rawReadConn = readConn.(utils.Readver).GetRawForReadv()
} else {
br = readConn.(utils.BuffersReader)
}
}
//这个for循环 只会通过 return 跳出, 不会落到外部
for {
var buffers net.Buffers
//自此 userlevel_readv 将被当作 是否使用 ReadBuffers 的标识
if userlevel_readv {
buffers, err = br.ReadBuffers()
} else {
buffers, err = utils.ReadvFrom(rawReadConn, readv_mem)
}
if err != nil {
return
}
var thisWriteNum int64
var writeErr error
// vless/trojan.UserTCPConn, ws.Conn 实现了 utils.MultiWriter
// vless/trojan的 UserTCPConn 会 间接调用 ws.Conn 的 WriteBuffers
if isWriteConn_MultiWriter {
thisWriteNum, writeErr = multiWriter.WriteBuffers(buffers)
} else {
// 这里不能直接使用 buffers.WriteTo, 因为它会修改buffer本身
// 而我们为了缓存,是不能允许篡改的
// 所以我们在确保 writeConn 不是 基本连接后, 要 自行write
//在basic时之所以可以 WriteTo是因为它并不会用循环读取方式, 而是用底层的writev
// 而writev时是不会篡改 buffers的
//然而经实测,writev会篡改我们的buffers会导致问题. 而且writev也毫无性能优势,
//所以这里统一使用我们自己的函数
thisWriteNum, writeErr = utils.BuffersWriteTo(buffers, writeConn)
}
allnum += thisWriteNum
if writeErr != nil {
err = writeErr
return
}
if userlevel_readv {
br.PutBuffers(buffers)
} else {
buffers = utils.RecoverBuffers(buffers, utils.Readv_buffer_allocLen, utils.ReadvSingleBufLen)
}
}
}
classic:
if ce := utils.CanLogDebug("copying with classic method"); ce != nil {
ce.Write(zap.Uint32("id", id))
}
copy:
//Copy内部实现 会调用 ReadFrom, 而ReadFrom 会自动进行splice,
// 若无splice实现则直接使用原始方法 “循环读取 并 写入”
// 我们的 vless/trojan 和 ws 的Conn均实现了ReadFrom方法可以最终splice
return io.Copy(writeConn, readConn)
}
// 类似TryCopy但是只会读写一次; 因为只读写一次所以没办法splice
func TryCopyOnce(writeConn io.Writer, readConn io.Reader) (allnum int64, err error) {
var buffers net.Buffers
var rawConn syscall.RawConn
var rm *utils.ReadvMem
if ce := utils.CanLogDebug("TryCopy"); ce != nil {
ce.Write(
zap.String("from", reflect.TypeOf(readConn).String()),
zap.String("->", reflect.TypeOf(writeConn).String()),
)
}
// 不全 支持splice的话我们就考虑 read端 可 readv 的情况
// 连readv都不让 那就直接 经典拷贝
if !UseReadv {
goto classic
}
rawConn = GetRawConn(readConn)
if rawConn == nil {
goto classic
}
if ce := utils.CanLogDebug("copying with readv"); ce != nil {
ce.Write()
}
rm = utils.Get_readvMem()
defer utils.Put_readvMem(rm)
buffers, err = utils.ReadvFrom(rawConn, rm)
if err != nil {
return 0, err
}
allnum, err = utils.BuffersWriteTo(buffers, writeConn) //buffers.WriteTo(writeConn)
return
classic:
if ce := utils.CanLogDebug("copying with classic method"); ce != nil {
ce.Write()
}
bs := utils.GetPacket()
n, e := readConn.Read(bs)
if e != nil {
utils.PutPacket(bs)
return 0, e
}
n, e = writeConn.Write(bs[:n])
utils.PutPacket(bs)
return int64(n), e
}
// 从 rc 读取 写入到 lc ,并同时从 lc 读取写入 rc.
// 阻塞. rc是指 remoteConn, lc 是指localConn; 一般lc由自己监听的Accept产生, rc 由自己拨号产生.
// UseReadv==true 时 内部使用 TryCopy 进行拷贝,
// 会自动优选 splicereadv不行则使用经典拷贝.
//
// 拷贝完成后会主动关闭双方连接.
// 返回从 rc读取到的总字节长度即下载流量. 如果 downloadByteCount, uploadByteCount 给出,
// 则会 分别原子更新 上传和下载的总字节数. identity 用于输出日志。
func Relay(realTargetAddr *Addr, rc, lc io.ReadWriteCloser, identity uint32, downloadByteCount, uploadByteCount *uint64) int64 {
if utils.LogLevel == utils.Log_debug {
rtaddrStr := realTargetAddr.String()
go func() {
n, e := TryCopy(rc, lc, identity)
utils.CanLogDebug("Relay End").Write(zap.Uint32("id", identity),
zap.String("direction", "L->R"),
zap.String("target", rtaddrStr),
zap.Int64("bytes", n),
zap.Error(e),
)
lc.Close()
rc.Close()
if uploadByteCount != nil {
utils.AtomicAddUint64(uploadByteCount, uint64(n))
}
}()
n, e := TryCopy(lc, rc, identity)
utils.CanLogDebug("Relay End").Write(zap.Uint32("id", identity),
zap.String("direction", "R->L"),
zap.String("target", rtaddrStr),
zap.Int64("bytes", n),
zap.Error(e),
)
lc.Close()
rc.Close()
if downloadByteCount != nil {
utils.AtomicAddUint64(downloadByteCount, uint64(n))
}
return n
} else {
go func() {
n, _ := TryCopy(rc, lc, identity)
lc.Close()
rc.Close()
if uploadByteCount != nil {
utils.AtomicAddUint64(uploadByteCount, uint64(n))
}
}()
n, _ := TryCopy(lc, rc, identity)
lc.Close()
rc.Close()
if downloadByteCount != nil {
utils.AtomicAddUint64(downloadByteCount, uint64(n))
}
return n
}
}