Files
v2ray_simple/netLayer/splice.go
2022-04-05 14:42:02 +08:00

185 lines
5.3 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 netLayer
import (
"errors"
"io"
"net"
"runtime"
"github.com/hahahrfool/v2ray_simple/utils"
)
var errInvalidWrite = errors.New("vless readfrom, invalid write result")
const SystemCanSplice = runtime.GOARCH != "wasm" && runtime.GOOS != "windows"
type Splicer interface {
EverPossibleToSplice() bool //是否有机会splice, 如果这个返回false则永远无法splice; 主要审视自己能否向裸连接写入数据; 读不用splicer担心。
CanSplice() (bool, net.Conn) //当前状态是否可以splice
}
//这里认为能 splice 或 sendfile的 都算具体可参考go标准代码的实现, 总之就是tcp和 unix domain socket 可以.
// 若不是基本Conn则会试图转换为Splicer并获取底层Conn
func CanSpliceDirectly(r any) bool {
if _, ok := r.(*net.TCPConn); ok {
return true
} else if _, ok := r.(*net.UnixConn); ok {
return true
}
return false
}
func CanSpliceEventually(r any) bool {
if s, ok := r.(Splicer); ok {
return s.EverPossibleToSplice()
}
return false
}
//从r读取数据写入 maySpliceConn 或者 classicWriter, 在条件合适时会使用splice进行加速。
// 本函数主要应用于裸奔时一端是socks5/直连,另一端是vless/vless+ws的情况, 因为vless等协议就算裸奔也是要处理一下数据头等情况的, 所以需要进行处理才可裸奔.
//
// 注意splice只有在 maySpliceConn【本身是】或者【变成】 basicConn 且 r 也是 basicConn时才会发生。
// 如果r本身就不是 basicConn则调用本函数没有意义, 因为既然拿不到basicConn那就不是裸奔也就不可能splice。
//
func TryReadFrom_withSplice(classicWriter io.Writer, maySpliceConn net.Conn, r io.Reader, canDirectFunc func() bool) (written int64, err error) {
//log.Println("TryReadFrom_withSplice called")
underlay_canSpliceDirectly := CanSpliceDirectly(maySpliceConn)
var underlay_canSpliceEventually bool
if !underlay_canSpliceDirectly {
underlay_canSpliceEventually = CanSpliceEventually(maySpliceConn)
}
var splicer Splicer
if underlay_canSpliceEventually {
splicer = maySpliceConn.(Splicer)
}
/*
分多钟情况,
1. underlay直接是基础连接underlay_canSpliceDirectly且现在直接 canDirectFunc 就是true, 此时直接 splice
2. underlay直接是基础连接underlay_canSpliceDirectly但现在的连接阶段还不能直接直连此时要读写一次然后判断一次直到 canDirectFunc 变成 true
3. underlay 不是基础连接,但是 是 Splicerunderlay_canSpliceEventually且此时我们先等待 underlay已经处于 可直连状态, 即 splicer.CanSplice()变成 true然后再确保 canDirectFunc 返回true
4. underlay啥也不是直接经典拷贝。
*/
if underlay_canSpliceDirectly || underlay_canSpliceEventually {
if underlay_canSpliceDirectly && canDirectFunc() {
if rt, ok := maySpliceConn.(io.ReaderFrom); ok {
return rt.ReadFrom(r)
} else {
panic("uc.underlayIsBasic, but can't cast to ReadFrom")
}
} else {
//循环读写,直到 canDirectFunc 和 splicer.CanSplice() 都为true
buf := utils.GetPacket()
defer utils.PutPacket(buf)
for {
nr, er := r.Read(buf)
if nr > 0 {
nw, ew := classicWriter.Write(buf[0:nr])
if nw < 0 || nr < nw {
nw = 0
if ew == nil {
ew = errInvalidWrite
}
}
written += int64(nw)
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
}
if er != nil {
err = er
break
}
if underlay_canSpliceDirectly {
if rt, ok := maySpliceConn.(io.ReaderFrom); ok {
var readfromN int64
readfromN, err = rt.ReadFrom(r)
written += readfromN
return
} else {
panic("uc.underlayIsBasic, but can't cast to ReadFrom")
}
} else {
if canStartSplice, rawConn := splicer.CanSplice(); canStartSplice {
if rt, ok := rawConn.(io.ReaderFrom); ok {
var readfromN int64
readfromN, err = rt.ReadFrom(r)
written += readfromN
return
} else {
panic("uc.underlayIsBasic, but can't cast to ReadFrom")
}
}
}
} //for read
return
} //cant direct write
} else { //splice not possible, 仅仅循环读写即可
// 我们的vless的ReadFrom方法使用到了本TryReadFrom_withSplice函数, 所以本函数的内部不可再使用ReadFrom作为回落选项
// ReadFrom会调用 io.CopyBuffer 而 io.CopyBuffer 是又会ReadFrom的所以说我们调用 ReadFrom 那就会造成无限递归然后栈溢出闪退。
//所以我们只能单独把纯粹的经典拷贝代码拿出来使用
return ClassicCopy(classicWriter, r)
}
}
//拷贝自 io.CopyBuffer。 因为原始的 CopyBuffer会又调用ReadFrom, 而我们这里过滤掉了ReadFrom, 希望直接进行经典拷贝
func ClassicCopy(w io.Writer, r io.Reader) (written int64, err error) {
buf := utils.GetPacket()
defer utils.PutPacket(buf)
for {
nr, er := r.Read(buf)
if nr > 0 {
nw, ew := w.Write(buf[0:nr])
if nw < 0 || nr < nw {
nw = 0
if ew == nil {
ew = errInvalidWrite
}
}
written += int64(nw)
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
}
if er != nil {
err = er
break
}
}
return
}