Files
v2ray_simple/tls_lazy.go
2022-12-26 18:33:16 +08:00

462 lines
14 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 v2ray_simple
import (
"flag"
"io"
"log"
"net"
"runtime"
"strconv"
"github.com/e1732a364fed/v2ray_simple/netLayer"
"github.com/e1732a364fed/v2ray_simple/proxy"
"github.com/e1732a364fed/v2ray_simple/tlsLayer"
"github.com/e1732a364fed/v2ray_simple/utils"
"go.uber.org/zap"
)
var (
tls_lazy_secure bool
)
const tlslazy_willuseSystemCall = runtime.GOOS == "linux" || runtime.GOOS == "darwin"
func init() {
flag.BoolVar(&tls_lazy_secure, "ls", false, "tls lazy secure, use special techs to ensure the tls lazy encrypt data can't be detected. Only valid at client end. (under developping.)")
}
// 有TLS, network为tcp或者unix, 无AdvLayer.
// grpc 这种多路复用的链接是绝对无法开启 lazy的, ws 理论上也只有服务端发向客户端的链接 内嵌tls时可以lazy但暂不考虑
func CanLazyEncrypt(x proxy.BaseInterface) bool {
return x.IsUseTLS() && CanNetwork_tlsLazy(x.Network()) && x.AdvancedLayer() == ""
}
func CanNetwork_tlsLazy(n string) bool {
switch n {
case "", "tcp", "tcp4", "tcp6", "unix":
return true
}
return false
}
// tryTlsLazyRawRelay 尝试能否直接对拷,对拷 直接使用 原始 TCPConn也就是裸奔转发.
//
// 如果在linux上则和 xtls的splice 含义相同. 在其他系统时与xtls-direct含义相同。
//
// 我们内部先 使用 SniffConn 进行过滤分析然后再判断进化为splice / 退化为普通拷贝.
//
// useSecureMethod仅用于 tls_lazy_secure
func tryTlsLazyRawRelay(connid uint32, useSecureMethod bool, proxy_client proxy.UserClient, proxy_server proxy.UserServer, targetAddr netLayer.Addr, wrc, wlc io.ReadWriteCloser, localConn net.Conn, isclient bool, theRecorder *tlsLayer.Recorder) {
if ce := utils.CanLogDebug("Try tls lazy"); ce != nil {
ce.Write(zap.Uint32("id", connid))
}
if wlc != nil {
defer wlc.Close()
}
if wrc != nil {
defer wrc.Close()
}
//如果用了 lazy_encrypt 则不直接利用Copy因为有两个阶段判断阶段和直连阶段
// 在判断阶段,因为还没确定是否是 tls所以是要继续用tls加密的
// 而直连阶段,只要能让 Copy使用 net.TCPConn的 ReadFrom, 就不用管了, golang最终就会使用splice
// 之所以可以对拷直连,是因为无论是 socks5 还是vless只是在最开始的部分 加了目标头后面的所有tcp连接都是直接传输的数据就是说一开始握手什么的是不能直接对拷的等到后期就可以了
// 而且之所以能对拷,还有个原因就是,远程服务器 与 客户端 总是源源不断地 为 我们的 原始 TCP 连接 提供数据,我们只是一个中间商而已,左手倒右手
// 如果是客户端,则 从 wlc 读取,写入 wrc ,这种情况是 Write, 然后对于 SniffConn 来说是 Read即 SniffConn 读取,然后 写入到远程连接
// 如果是服务端,则 从 wrc 读取,写入 wlc 这种情况是 Write
//
// 总之判断 Write 的对象,是考虑 客户端和服务端之间的数据传输,不考虑 远程真实服务器
wlcdc := wlc.(*tlsLayer.SniffConn)
wlccc_raw := wlcdc.RawConn
if isclient {
sc := proxy_client.GetUser().AuthBytes()
wlcdc.R.SpecialCommandBytes = sc
wlcdc.W.SpecialCommandBytes = sc
} else {
wlcdc.R.Auther = proxy_server
}
var rawWRC *net.TCPConn
if !useSecureMethod {
//wrc 有两种情况如果客户端那就是tls服务端那就是direct。我们不讨论服务端 处于中间层的情况
if isclient {
// vless v0 或者trojan时客户端 wrc 是 vless的 UserConn 而UserConn的底层连接才是TLS
// 而vless v1时在客户端是直接就是 *tlsLayer.Conn
//总之只能先用笨拙的穷举情况判断,以后再做优化
var isTlsDirectly bool
switch proxy_client.Name() {
case "socks5":
isTlsDirectly = true
case "vless_1":
isTlsDirectly = true
}
if isTlsDirectly {
tlsConn := wrc.(tlsLayer.Conn)
rawWRC = tlsConn.GetRaw(true)
} else {
wrcWrapper := wrc.(netLayer.ConnWrapper)
tlsConn := wrcWrapper.Upstream().(tlsLayer.Conn)
rawWRC = tlsConn.GetRaw(true)
}
} else {
rawWRC = wrc.(*net.TCPConn) //因为是direct
}
if rawWRC == nil { //一般情况下这段代码是不会被触发的.(因为如果类型错误一般上面代码会直接panic但是不排除符合接口但为nil的情况)
if tlsLayer.PDD {
log.Printf("splice fail reason 0\n")
}
if true {
theRecorder.StopRecord()
theRecorder.ReleaseBuffers()
}
//退化回原始状态
go netLayer.TryCopy(wrc, wlc, connid)
netLayer.TryCopy(wlc, wrc, connid)
return
}
} else {
rawWRC = wrc.(*net.TCPConn) //useSecureMethod的一定是客户端此时就是直接给出原始连接
}
waitWRC_CreateChan := make(chan int)
go func(wrcPtr *io.ReadWriteCloser) {
//从 wlccc 读取,向 wrc 写入
// 此时如果ReadFrom那就是 wrc.ReadFrom(wlccc)
//wrc 要实现 ReaderFrom才行, or把最底层TCPConn暴露然后 wlccc 也要把最底层 TCPConn暴露出来
// 这里就直接采取底层方式
p := utils.GetPacket()
isgood := false
isbad := false
checkCount := 0
for {
if isgood || isbad {
break
}
n, err := wlcdc.Read(p)
if err != nil {
break
}
checkCount++
if useSecureMethod && checkCount == 1 {
//此时还未dial需要进行dial; 仅限客户端
if tlsLayer.PDD {
log.Printf(" 才开始Dial 服务端\n")
}
theRecorder = tlsLayer.NewRecorder()
teeConn := tlsLayer.NewTeeConn(rawWRC, theRecorder)
tlsConn, err := proxy_client.GetTLS_Client().Handshake(teeConn)
if err != nil {
if ce := utils.CanLogErr("failed in handshake outClient tls"); ce != nil {
ce.Write(zap.Uint32("id", connid), zap.Error(err))
}
return
}
wrc, err = proxy_client.Handshake(tlsConn, p[:n], targetAddr)
if err != nil {
if ce := utils.CanLogErr("failed in handshake"); ce != nil {
ce.Write(zap.Uint32("id", connid), zap.String("target", targetAddr.String()), zap.Error(err))
}
return
}
*wrcPtr = wrc
waitWRC_CreateChan <- 1
continue
} else {
if tlsLayer.PDD {
log.Printf("从wlc读, 第 %s 次", strconv.Itoa(checkCount))
}
}
//wrc.Write(p[:n])
//在判断 “是TLS” 的瞬间,它会舍弃写入数据,而把写入的主动权交回我们,我们发送特殊命令后,通过直连写入数据
if wlcdc.R.IsTls && wlcdc.RawConn != nil {
isgood = true
if isclient {
// 若是client因为client是在Read时判断的 IsTLS所以特殊指令实际上是要在这里发送
if tlsLayer.PDD {
log.Printf("R 准备发送特殊命令, 以及保存的TLS内容,%d\n", len(p[:n]))
}
wrc.Write(wlcdc.R.SpecialCommandBytes)
//然后还要发送第一段FreeData
rawWRC.Write(p[:n])
} else {
//如果是 server, 则 此时 已经收到了解密后的 特殊指令
// 我们要从 theRecorder 中最后一个Buf里找 原始数据
//theRecorder.DigestAll()
//这个瞬间R是存放了 SpecialCommand了即uuid然而W还是没有的
// 所以我们要先给W的SpecialCommand 赋值
wlcdc.W.SpecialCommandBytes = wlcdc.R.SpecialCommandBytes
rawBuf := theRecorder.GetLast()
bs := rawBuf.Bytes()
/*
if tlsLayer.PDD {
_, record_count := tlsLayer.GetLastTlsRecordTailIndex(bs)
if record_count < 2 { // 应该是0-rtt的情况
log.Println("检测到0-rtt"")
}
log.Println("R Recorder 中记录了", record_count, "条记录")
}
*/
nextI := tlsLayer.GetTlsRecordNextIndex(bs)
//有可能只存有一个record然后 supposedLen非常长此时 nextI是大于整个bs长度的
//正常来说这是不应该发生的,但是实际测速时发生了!会导致服务端闪退,
// 就是说在客户端上传大流量时,可能导致服务端出问题
//
//仔细思考如果在客户端发送特殊指令的同时tls的Conn仍然在继续写入的话那么就有可能出现这种情况
// 也就是说是多线程问题但是还是不对如果tls正在写入那么我们则还没达到写特殊指令的代码
//只能说,写入的顺序完全是正确的,但是收到的数据似乎有错误发生
//
// 还是说特殊指令实际上被分成了两个record发送这么短的指令也能吗
//还有一种可能就是在写入“特殊指令”的瞬间需要发送一些alert然后在特殊指令的前后一起发送了
//仅在 ubuntu中测试发生过macos中 测试从未发生过
//总之,实际测试这个 nextI 似乎特别大然后bs也很大。bs大倒是正常因为是测速
//
// 一种情况是特殊指令粘在上一次tls包后面被一起发送。那么此时lastbuffer应该完全是新自由数据
// 上一次的tls包应该是最后一个握手包。但是问题是client必须要收到服务端的握手回应才能继续发包
// 所以还是不应该发生。
// 除非使用了某种方式在握手的同时也传递数据等等tls1.3的0-rtt就是如此啊
//
// 而且,上传正好属于握手的同时上传数据的情况。
// 而服务端是无法进行tls1.3的0-rtt的因为理论上 tls1.3的 0-rtt只能由客户端发起。
// 所以才会出现下载时毫无问题上传时出bug的情况
//
//如果是0-rtt我们的Recorder应该根本没有记录到我们的特殊指令包因为它是从第二个包开始记录的啊
// 所以我们从Recorder获取到的包是不含“特殊指令”包的所以获取到的整个数据全是我们想要的
if len(bs) < nextI {
// 应该是 tls1.3 0-rtt的情况
rawWRC.Write(bs)
} else if nextI < 0 {
if tlsLayer.PDD {
log.Printf("nextI 为 -1")
}
} else {
nextFreeData := bs[nextI:]
if tlsLayer.PDD {
log.Printf("R 从Recorder 提取 真实TLS内容, %d\n", len(nextFreeData))
}
rawWRC.Write(nextFreeData)
}
theRecorder.StopRecord()
theRecorder.ReleaseBuffers()
}
} else {
if tlsLayer.PDD {
log.Printf("pass write\n")
}
wrc.Write(p[:n])
if wlcdc.R.DefinitelyNotTLS {
isbad = true
}
}
} //for
utils.PutPacket(p)
if isbad {
//直接退化成普通Copy
if tlsLayer.PDD {
log.Printf("SpliceRead R方向 退化…… %d\n", wlcdc.R.GetFailReason())
}
netLayer.TryCopy(wrc, wlc, connid)
return
}
if isgood {
if tlslazy_willuseSystemCall {
runtime.Gosched() //详情请阅读我的 xray_splice- 文章,了解为什么这个调用是必要的
}
if tlsLayer.PDD {
log.Printf("成功SpliceRead R方向\n")
num, e1 := rawWRC.ReadFrom(wlccc_raw)
log.Printf("SpliceRead R方向 传完,%v , 长度: %d\n", e1, num)
} else {
rawWRC.ReadFrom(wlccc_raw)
}
}
}(&wrc)
isgood2 := false
isbad2 := false
p := utils.GetPacket()
count := 0
//从 wrc 读取,向 wlccc 写入
for {
if isgood2 || isbad2 {
break
}
count++
if useSecureMethod && count == 1 {
<-waitWRC_CreateChan
}
if tlsLayer.PDD {
log.Printf("准备从wrc读\n")
}
n, err := wrc.Read(p)
if err != nil {
break
}
if tlsLayer.PDD {
log.Printf("从wrc读到数据%d 准备写入wlcdc", n)
}
wn, _ := wlcdc.Write(p[:n])
if tlsLayer.PDD {
log.Printf("写入wlcdc完成 %d\n", wn)
}
if wlcdc.W.IsTls && wlcdc.RawConn != nil {
if isclient {
//读到了服务端 发来的 特殊指令
rawBuf := theRecorder.GetLast()
bs := rawBuf.Bytes()
nextI := tlsLayer.GetTlsRecordNextIndex(bs)
if nextI > len(bs) { //理论上有可能但是又不应该收到的buf不应该那么短应该至少包含一个有效的整个tls record因为此时理论上已经收到了服务端的 特殊指令,它是单独包在一个 tls record 里的
//不像上面类似的一段,这个例外从来没有被触发过,也就是说,下载方向是毫无问题的
//
//这是因为, 触发上一段类似代码的原因是tls 0-rtt而 tls 0-rtt 总是由 客户端发起的
log.Println("有问题, nextI > len(bs)", nextI, len(bs))
//这里不应 用 os.Exit 退出程序,理论上有可能由一些黑客来触发这里, 直接退出转发过程即可。
localConn.Close()
rawWRC.Close()
return
}
if nextI < len(bs) {
//有额外的包
nextFreeData := bs[nextI:]
wlccc_raw.Write(nextFreeData)
}
theRecorder.StopRecord()
theRecorder.ReleaseBuffers()
} else {
//此时已经写入了 特殊指令,需要再发送 freedata
wlccc_raw.Write(p[:n])
}
isgood2 = true
} else if wlcdc.W.DefinitelyNotTLS {
isbad2 = true
}
}
utils.PutPacket(p)
if isbad2 {
if tlsLayer.PDD {
log.Println("SpliceRead W方向 退化……", wlcdc.W.GetFailReason())
}
//就算不用splice, 一样可以用readv来在读那一端增强性能
netLayer.TryCopy(wlc, wrc, connid)
return
}
if isgood2 {
if tlsLayer.PDD {
log.Printf("成功SpliceRead W方向,准备 直连对拷\n")
}
if tlslazy_willuseSystemCall {
runtime.Gosched() //详情请阅读我的 xray_splice- 文章,了解为什么这个调用是必要的
}
if tlsLayer.PDD {
num, e2 := wlccc_raw.ReadFrom(rawWRC) //看起来是ReadFrom实际上是向 wlccc_raw进行Write即箭头向左
log.Printf("SpliceRead W方向 传完,%v , 长度: %d\n", e2, num)
} else {
wlccc_raw.ReadFrom(rawWRC)
}
}
}