Files
v2ray_simple/main.go
hahahrfool 548675e002 添加自定义网络层功能;添加udp网络层支持
配置中,listen和dial中,可添加 network = "udp" 字段,不添加则默认tcp
2022-03-21 21:19:31 +08:00

1066 lines
31 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 main
import (
"bytes"
"flag"
"io"
"log"
"net"
"os"
"os/signal"
"runtime"
"syscall"
"github.com/hahahrfool/v2ray_simple/httpLayer"
"github.com/hahahrfool/v2ray_simple/netLayer"
"github.com/hahahrfool/v2ray_simple/tlsLayer"
"github.com/hahahrfool/v2ray_simple/utils"
"github.com/hahahrfool/v2ray_simple/ws"
"github.com/hahahrfool/v2ray_simple/proxy"
"github.com/hahahrfool/v2ray_simple/proxy/socks5"
"github.com/hahahrfool/v2ray_simple/proxy/vless"
_ "github.com/hahahrfool/v2ray_simple/proxy/direct"
_ "github.com/hahahrfool/v2ray_simple/proxy/dokodemo"
_ "github.com/hahahrfool/v2ray_simple/proxy/http"
)
const (
simpleMode = iota
standardMode
v2rayCompatibleMode
)
var (
configFileName string
uniqueTestDomain string //有时需要测试到单一网站的流量,此时为了避免其它干扰,需要在这里声明 一下 该域名,然后程序里会进行过滤
confMode int = -1 //0: simple json, 1: standard toml, 2: v2ray compatible json
simpleConf *proxy.Simple
standardConf *proxy.Standard
directClient proxy.Client
defaultOutClient proxy.Client
defaultInServer proxy.Server
default_uuid string
allServers = make([]proxy.Server, 0, 8)
allClients = make([]proxy.Client, 0, 8)
serversTagMap = make(map[string]proxy.Server)
clientsTagMap = make(map[string]proxy.Client)
listenURL string
dialURL string
tls_lazy_encrypt bool
tls_lazy_secure bool
routePolicy *netLayer.RoutePolicy
mainFallback *httpLayer.ClassicFallback
isServerEnd bool //这个是代码里推断的不一定准确不过目前仅被用于tls lazy encrypt所以不是很重要
)
func init() {
directClient, _ = proxy.ClientFromURL("direct://")
flag.BoolVar(&tls_lazy_encrypt, "lazy", false, "tls lazy encrypt (splice)")
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.")
flag.StringVar(&configFileName, "c", "client.toml", "config file name")
flag.StringVar(&listenURL, "L", "", "listen URL (i.e. the local part in config file), only enbled when config file is not provided.")
flag.StringVar(&dialURL, "D", "", "dial URL (i.e. the remote part in config file), only enbled when config file is not provided.")
flag.StringVar(&uniqueTestDomain, "td", "", "test a single domain, like www.domain.com")
}
func printDesc() {
printVersion()
proxy.PrintAllServerNames()
proxy.PrintAllClientNames()
}
func main() {
printDesc()
flag.Parse()
loadConfig()
if confMode < 0 {
log.Fatal("no config exist")
}
var err error
//load server and routePolicy
switch confMode {
case simpleMode:
defaultInServer, err = proxy.ServerFromURL(simpleConf.Server_ThatListenPort_Url)
if err != nil {
log.Fatalln("can not create local server: ", err)
}
if !defaultInServer.CantRoute() && simpleConf.Route != nil {
netLayer.LoadMaxmindGeoipFile("")
//极简模式只支持通过 mycountry进行 geoip分流 这一种情况
routePolicy = netLayer.NewRoutePolicy()
if simpleConf.MyCountryISO_3166 != "" {
routePolicy.AddRouteSet(netLayer.NewRouteSetForMyCountry(simpleConf.MyCountryISO_3166))
}
}
case standardMode:
//虽然标准模式支持多个Server目前先只考虑一个
//多个Server存在的话则必须要用 tag指定路由; 然后我们需在预先阶段就判断好tag指定的路由
if len(standardConf.Listen) < 1 {
log.Fatal("no Listen in config settings!")
}
for _, serverConf := range standardConf.Listen {
thisConf := serverConf
if thisConf.Uuid == "" && default_uuid != "" {
thisConf.Uuid = default_uuid
}
thisServer, err := proxy.NewServer(thisConf)
if err != nil {
log.Fatalln("can not create local server: ", err)
}
allServers = append(allServers, thisServer)
if tag := thisServer.GetTag(); tag != "" {
serversTagMap[tag] = thisServer
}
}
hasMyCountry := (standardConf.App != nil && standardConf.App.MyCountryISO_3166 != "")
if standardConf.Route != nil || hasMyCountry {
netLayer.LoadMaxmindGeoipFile("")
routePolicy = netLayer.NewRoutePolicy()
if hasMyCountry {
routePolicy.AddRouteSet(netLayer.NewRouteSetForMyCountry(standardConf.App.MyCountryISO_3166))
}
proxy.LoadRulesForRoutePolicy(standardConf.Route, routePolicy)
}
}
// load client
switch confMode {
case simpleMode:
defaultOutClient, err = proxy.ClientFromURL(simpleConf.Client_ThatDialRemote_Url)
if err != nil {
log.Fatalln("can not create remote client: ", err)
}
case standardMode:
if len(standardConf.Dial) < 1 {
log.Fatal("no dial in config settings!")
}
for _, thisConf := range standardConf.Dial {
if thisConf.Uuid == "" && default_uuid != "" {
thisConf.Uuid = default_uuid
}
thisClient, err := proxy.NewClient(thisConf)
if err != nil {
log.Fatalln("can not create remote client: ", err)
}
allClients = append(allClients, thisClient)
if tag := thisClient.GetTag(); tag != "" {
clientsTagMap[tag] = thisClient
}
}
defaultOutClient = allClients[0]
}
isServerEnd = defaultOutClient.Name() == "direct"
// 后台运行主代码而main函数只监听中断信号
// TODO: 未来main函数可以推出 交互模式,等未来推出动态增删用户、查询流量等功能时就有用;
// 或可用于交互生成自己想要的配置
if confMode == simpleMode {
go listenSer(nil, defaultInServer)
} else {
for _, inServer := range allServers {
go listenSer(nil, inServer)
}
}
{
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, os.Kill, syscall.SIGTERM)
<-osSignals
}
}
func listenSer(listener net.Listener, inServer proxy.Server) {
theFunc := func(conn net.Conn) {
handleNewIncomeConnection(inServer, conn)
}
network := inServer.Network()
err := netLayer.ListenAndAccept(network, inServer.AddrStr(), theFunc)
if err == nil {
if utils.CanLogInfo() {
log.Println(proxy.GetFullName(inServer), "is listening ", network, "on", inServer.AddrStr())
}
} else {
if err != nil {
log.Fatalln("can not listen inServer on", inServer.AddrStr(), err)
}
}
}
func handleNewIncomeConnection(inServer proxy.Server, thisLocalConnectionInstance net.Conn) {
baseLocalConn := thisLocalConnectionInstance
var cachedRemoteAddr string
if utils.CanLogInfo() {
cachedRemoteAddr = thisLocalConnectionInstance.RemoteAddr().String()
log.Println("New req from", cachedRemoteAddr)
}
var err error
//此时baseLocalConn里面 正常情况下, 服务端看到的是 客户端的golang的tls 拨号发出的 tls数据
// 客户端看到的是 socks5的数据 我们首先就是要看看socks5里的数据是不是tls而socks5自然 IsUseTLS 是false
// 如果是服务端的话,那就是 inServer.IsUseTLS == true, 此时,我们正常握手,然后我们需要判断的是它承载的数据
// 每次tls试图从 原始连接 读取内容时,都会附带把原始数据写入到 这个 Recorder中
var inServerTlsRawReadRecorder *tlsLayer.Recorder
if inServer.IsUseTLS() {
if tls_lazy_encrypt {
inServerTlsRawReadRecorder = tlsLayer.NewRecorder()
inServerTlsRawReadRecorder.StopRecord() //先不记录因为一开始是我们自己的tls握手包没有意义
teeConn := tlsLayer.NewTeeConn(baseLocalConn, inServerTlsRawReadRecorder)
thisLocalConnectionInstance = teeConn
}
tlsConn, err := inServer.GetTLS_Server().Handshake(thisLocalConnectionInstance)
if err != nil {
if utils.CanLogErr() {
log.Println("failed in inServer tls handshake ", inServer.AddrStr(), err)
}
thisLocalConnectionInstance.Close()
return
}
if tls_lazy_encrypt {
//此时已经握手完毕,可以记录了
inServerTlsRawReadRecorder.StartRecord()
}
thisLocalConnectionInstance = tlsConn
}
//log.Println("handshake passed tls")
if adv := inServer.AdvancedLayer(); adv != "" {
if adv == "ws" {
wsConn, err := ws.Handshake(inServer.GetListenConf().Path, thisLocalConnectionInstance)
if err != nil {
if utils.CanLogErr() {
log.Println("failed in inServer websocket handshake ", inServer.AddrStr(), err)
}
return
}
thisLocalConnectionInstance = wsConn
}
}
var theFallbackFirstBuffer *bytes.Buffer
wlc, targetAddr, err := inServer.Handshake(thisLocalConnectionInstance)
if err != nil {
if utils.CanLogWarn() {
log.Println("failed in inServer proxy handshake from", inServer.AddrStr(), err)
}
if !inServer.CanFallback() {
thisLocalConnectionInstance.Close()
return
}
fe, ok := err.(httpLayer.FallbackErr)
if !ok {
// 能fallback 但是返回的 err却不是fallback err证明遇到了更大问题可能是底层read问题所以也不用继续fallback了
thisLocalConnectionInstance.Close()
return
}
//fe提供的fallback是该服务器默认的fallback我们先试图使用mainFallback都不满足条件的话再回落到默认fallback
f := fe.Fallback()
buf := f.FirstBuffer()
if buf == nil {
//不应该至少能读到1字节的。
log.Fatal("No FirstBuffer")
}
var fbAddr *netLayer.Addr
goto checkFallback
fallbackok:
theFallbackFirstBuffer = buf
targetAddr = fbAddr
wlc = thisLocalConnectionInstance
goto afterLocalServerHandshake
checkFallback:
if mainFallback != nil {
if utils.CanLogDebug() {
log.Println("checkFallback")
}
_, path, failreason := httpLayer.GetRequestMethod_and_PATH_from_Bytes(buf.Bytes(), false)
if failreason != 0 {
fbAddr = mainFallback.GetFallback(httpLayer.Fallback_path, path)
if utils.CanLogDebug() {
log.Println("checkFallback ", path, "matched fallback:", fbAddr)
}
if fbAddr != nil {
goto fallbackok
}
}
}
if httpLayer.HasFallbackType(f.SupportType(), httpLayer.FallBack_default) {
fbAddr = f.GetFallback(httpLayer.FallBack_default, "")
if fbAddr != nil {
goto fallbackok
}
}
}
afterLocalServerHandshake:
var client proxy.Client = defaultOutClient
var routedToDirect bool
//尝试分流
if !inServer.CantRoute() && routePolicy != nil {
if utils.CanLogDebug() {
log.Println("try routing")
}
outtag := routePolicy.GetOutTag(&netLayer.TargetDescription{
Addr: targetAddr,
Tag: inServer.GetTag(),
})
if outtag == "direct" {
client = directClient
routedToDirect = true
if utils.CanLogInfo() {
log.Println("routed to direct", targetAddr.UrlString())
}
} else {
//log.Println("outtag", outtag, clientsTagMap)
if tagC, ok := clientsTagMap[outtag]; ok {
client = tagC
if utils.CanLogInfo() {
log.Println("routed to", outtag, proxy.GetFullName(client))
}
}
}
}
// 我们在客户端 lazy_encrypt 探测时读取socks5 传来的信息,因为这个 就是要发送到 outClient 的信息所以就不需要等包上vless、tls后再判断了, 直接解包 socks5 对 tls 进行判断
//
// 而在服务端探测时,因为 客户端传来的连接 包了 tls所以要在tls解包后, vless 解包后,再进行判断;
// 所以总之都是要在 inServer 判断 wlc; 总之,含义就是,去检索“用户承载数据”的来源
if tls_lazy_encrypt && !(!isServerEnd && routedToDirect) {
if tlsLayer.PDD {
log.Println("loading TLS SniffConn", !isServerEnd)
}
wlc = tlsLayer.NewSniffConn(baseLocalConn, wlc, !isServerEnd, tls_lazy_secure)
}
//如果目标是udp则要分情况讨论
if targetAddr.Network == "udp" {
switch inServer.Name() {
case "vlesss":
fallthrough
case "vless":
if targetAddr.Name == vless.CRUMFURS_Established_Str {
// 预留了 CRUMFURS 信道的话,就不要关闭 thisLocalConnectionInstance
// 而且也不在这里处理监听事件client自己会在额外的 goroutine里处理
// server也一样会在特定的场合给 CRUMFURS 传值这个机制是与main函数无关的
// 而且 thisLocalConnectionInstance 会被 inServer 保存起来,用于后面的 unknownRemoteAddrMsgWriter
return
} else {
//如果不是CRUMFURS命令那就是普通的针对某udp地址的连接见下文 uniExtractor 的使用
defer thisLocalConnectionInstance.Close()
}
case "socks5":
// UDP Associate
//
// 因为socks5的 UDP Associate 办法是较为特殊的不使用现有tcp而是新建立udp所以要单独拿出来处理。
// 此时 targetAddr.IsUDP 只是用于告知此链接是udp Associate并不包含实际地址信息
defer thisLocalConnectionInstance.Close()
// 此时socks5包已经帮我们dial好了一个udp连接即wlc但是还未读取到客户端想要访问的东西
udpConn := wlc.(*socks5.UDPConn)
// 将 outClient 视为 UDP_Putter 就可以转发udp信息了
// direct 也实现了 UDP_Putter (通过 UDP_Pipe和 RelayUDP_to_Direct函数), 所以目前 socks5直接转发udp到direct 的功能 已经实现。
// 我在 vless v1 的client 的 UserConn 中实现了 UDP_Putter, vless 的 client的 新连接的Handshake过程会在 UDP_Putter.WriteUDPRequest 被调用 时发生
if putter := client.(netLayer.UDP_Putter); putter != nil {
//UDP_Putter 不使用传统的Handshake过程因为Handshake是用于第一次数据然后后面接着的双向传输都不再需要额外信息而 UDP_Putter 每一次数据传输都是需要传输 目标地址的,所以每一次都需要一些额外数据,这就是我们 UDP_Putter 接口去解决的事情。
//因为UDP Associate后就会保证以后的向 wlc 的 所有请求数据都是udp请求所以可以在这里直接循环转发了。
go udpConn.StartPushResponse(putter)
udpConn.StartReadRequest(putter)
}
return
default:
defer thisLocalConnectionInstance.Close()
}
} else {
if !tls_lazy_encrypt { //lazy_encrypt情况比较特殊
defer thisLocalConnectionInstance.Close()
}
// 这里 因为 vless v1 的 CRUMFURS 信道 会对 thisLocalConnectionInstance 出现keep alive
// 而且具体的传递信息的部分并不是在main函数中处理而是自己的go routine所以不能直接关闭 thisLocalConnectionInstance
// 所以要分情况进行 defer thisLocalConnectionInstance.Close()。
}
// 如果目标是udp 则我们单独处理。这里为了简便,不考虑多级串联的关系,直接只考虑 直接转发到direct
// 根据 vless_v1的讨论vless_v1 的udp转发的 通信方式 也是与tcp连接类似的分离信道方式
// 上面已经把 CRUMFURS 的情况过滤掉了所以现在这里就是普通的udp请求
//
// 因为direct使用 proxy.RelayUDP_to_Direct 函数 直接实现了fullcone
// 那么我们只需要传入一个 UDP_Extractor 即可
if targetAddr.Network == "udp" {
var unknownRemoteAddrMsgWriter netLayer.UDPResponseWriter
switch inServer.Name() {
case "vlesss":
fallthrough
case "vless":
uc := wlc.(*vless.UserConn)
if uc.GetProtocolVersion() < 1 {
break
}
//unknownRemoteAddrMsgWriter 在 vless v1中的实现就是 theCRUMFURS vless v0就是mux
id := uc.GetIdentityStr()
vlessServer := inServer.(*vless.Server)
theCRUMFURS := vlessServer.Get_CRUMFURS(id)
unknownRemoteAddrMsgWriter = theCRUMFURS
}
uniExtractor := netLayer.NewUniUDP_Extractor(targetAddr.ToUDPAddr(), wlc, unknownRemoteAddrMsgWriter)
netLayer.RelayUDP_to_Direct(uniExtractor) //阻塞
return
}
var realTargetAddr *netLayer.Addr = targetAddr //direct的话自己是没有目的地址的直接使用 请求的地址
if uniqueTestDomain != "" && uniqueTestDomain != targetAddr.Name {
if utils.CanLogDebug() {
log.Println("request isn't the appointed domain", targetAddr, uniqueTestDomain)
}
return
}
if utils.CanLogInfo() {
log.Println(client.Name(), cachedRemoteAddr, " want to dial ", proxy.GetFullName(client), targetAddr.UrlString())
}
if client.AddrStr() != "" {
//log.Println("will dial", client.AddrStr())
realTargetAddr, _ = netLayer.NewAddr(client.AddrStr())
realTargetAddr.Network = client.Network()
}
clientConn, err := realTargetAddr.Dial()
if err != nil {
if utils.CanLogErr() {
log.Println("failed in dial", realTargetAddr.String(), ", Reason: ", err)
}
return
}
//log.Println("dial real addr ok", realTargetAddr)
var clientEndRemoteClientTlsRawReadRecorder *tlsLayer.Recorder
if client.IsUseTLS() { //即客户端
if tls_lazy_encrypt {
if tls_lazy_secure {
// 如果使用secure办法则我们每次不能先拨号而是要detect用户的首包后再拨号
// 这种情况只需要客户端操作, 此时我们wrc直接传入原始的 刚拨号好的 tcp连接即 clientConn
// 而且为了避免黑客攻击或探测我们要使用uuid作为特殊指令此时需要 UserServer和 UserClient
if uc := client.(proxy.UserClient); uc != nil {
tryRawCopy(true, uc, nil, targetAddr, clientConn, wlc, nil, true, nil)
}
return
} else {
clientEndRemoteClientTlsRawReadRecorder = tlsLayer.NewRecorder()
teeConn := tlsLayer.NewTeeConn(clientConn, clientEndRemoteClientTlsRawReadRecorder)
clientConn = teeConn
}
}
tlsConn, err := client.GetTLS_Client().Handshake(clientConn)
if err != nil {
log.Println("failed in handshake outClient tls", targetAddr.String(), ", Reason: ", err)
return
}
clientConn = tlsConn
}
if adv := client.AdvancedLayer(); adv != "" {
if adv == "ws" {
wsClient := client.GetWS_Client()
wc, err := wsClient.Handshake(clientConn)
if err != nil {
if utils.CanLogErr() {
log.Println("failed in handshake ws to", targetAddr.String(), ", Reason: ", err)
}
return
}
clientConn = wc
}
}
wrc, err := client.Handshake(clientConn, targetAddr)
if err != nil {
if utils.CanLogErr() {
log.Println("failed in handshake to", targetAddr.String(), ", Reason: ", err)
}
return
}
//log.Println("all handshake finished")
if !routedToDirect && tls_lazy_encrypt {
// 这里的错误是,我们加了回落之后,就无法确定 “未使用tls的outClient 一定是在服务端” 了
if !isServerEnd {
if client.IsUseTLS() {
//必须是 UserClient
if userClient := client.(proxy.UserClient); userClient != nil {
tryRawCopy(false, userClient, nil, nil, wrc, wlc, baseLocalConn, true, clientEndRemoteClientTlsRawReadRecorder)
return
}
}
} else {
// 最新代码已经确认使用uuid 作为 “特殊指令”所以要求Server必须是一个 proxy.UserServer
// 否则将无法开启splice功能。这是为了防止0-rtt 探测;
if userServer, ok := inServer.(proxy.UserServer); ok {
tryRawCopy(false, nil, userServer, nil, wrc, wlc, baseLocalConn, false, inServerTlsRawReadRecorder)
return
}
}
}
if theFallbackFirstBuffer != nil {
//这里注意因为是吧tls解密了之后的数据发送到目标地址所以这种方式只支持转发到本机纯http服务器
wrc.Write(theFallbackFirstBuffer.Bytes())
utils.PutBytes(theFallbackFirstBuffer.Bytes()) //这个Buf不是从utils.GetBuf创建的而是从一个 GetBytes的[]byte 包装 的所以我们要PutBytes而不是PutBuf
}
if utils.CanLogDebug() {
go func() {
n, e := io.Copy(wrc, wlc)
log.Println("本地->远程 转发结束", realTargetAddr.String(), n, e)
}()
n, e := io.Copy(wlc, wrc)
log.Println("远程->本地 转发结束", realTargetAddr.String(), n, e)
} else {
//如果两个都是 *net.TCPConn或uds, 则Copy会自动进行splice/sendfile无需额外处理
go io.Copy(wrc, wlc)
io.Copy(wlc, wrc)
}
}
var tlslazy_willuseSystemCall = runtime.GOOS == "linux" || runtime.GOOS == "darwin"
// tryRawCopy 尝试能否直接对拷,对拷 直接使用 原始 TCPConn也就是裸奔转发
// 如果在linux上则和 xtls的splice 含义相同. 在其他系统时与xtls-direct含义相同。
// 我们内部先 使用 DetectConn进行过滤分析然后再判断进化为splice 或者退化为普通拷贝
// 第一个参数仅用于 tls_lazy_secure
func tryRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, proxy_server proxy.UserServer, clientAddr *netLayer.Addr, wrc, wlc io.ReadWriter, localConn net.Conn, isclient bool, theRecorder *tlsLayer.Recorder) {
//如果用了 lazy_encrypt 则不直接利用Copy因为有两个阶段判断阶段和直连阶段
// 在判断阶段,因为还没确定是否是 tls所以是要继续用tls加密的
// 而直连阶段,只要能让 Copy使用 net.TCPConn的 ReadFrom, 就不用管了, golang最终就会使用splice
// 之所以可以对拷直连,是因为无论是 socks5 还是vless只是在最开始的部分 加了目标头后面的所有tcp连接都是直接传输的数据就是说一开始握手什么的是不能直接对拷的等到后期就可以了
// 而且之所以能对拷,还有个原因就是,远程服务器 与 客户端 总是源源不断地 为 我们的 原始 TCP 连接 提供数据,我们只是一个中间商而已,左手倒右手
// 如果开启了 half lazy 开关,则会在 Write的那一端 加强过滤过滤一些alert(目前还没做),然后 只在Read端 进行splice
//
// 如果是客户端,则 从 wlc 读取,写入 wrc ,这种情况是 Write, 然后对于 DetectConn 来说是 Read即 从DetectConn读取然后 写入到远程连接
// 如果是服务端,则 从 wrc 读取,写入 wlc 这种情况是 Write
//
// 总之判断 Write 的对象,是考虑 客户端和服务端之间的数据传输,不考虑 远程真实服务器
wlcdc := wlc.(*tlsLayer.SniffConn)
wlccc_raw := wlcdc.RawConn
if isclient {
sc := []byte(proxy_client.GetUser().GetIdentityBytes())
wlcdc.R.SpecialCommandBytes = sc
wlcdc.W.SpecialCommandBytes = sc
} else {
wlcdc.R.UH = proxy_server
}
var rawWRC *net.TCPConn
if !useSecureMethod {
//wrc 有两种情况如果客户端那就是tls服务端那就是direct。我们不讨论服务端 处于中间层的情况
if isclient {
// 不过实际上客户端 wrc 是 vless的 UserConn 而UserConn的底层连接才是TLS
// 很明显目前我们只支持vless所以才可这么操作以后再说。
wrcVless := wrc.(*vless.UserConn)
tlsConn := wrcVless.Conn.(*tlsLayer.Conn)
rawWRC = tlsConn.GetRaw(tls_lazy_encrypt)
//不过仔细思考,我们根本不需要这么繁琐地获取啊?!因为我们的 原始连接我们本来就是有的!
//rawWRC = localConn.(*net.TCPConn) //然而我实测,竟然传输会得到错误的结果,怎么回事
} else {
rawWRC = wrc.(*net.TCPConn) //因为是direct
}
if rawWRC == nil {
if tlsLayer.PDD {
log.Println("splice fail reason 0 ")
}
if tls_lazy_encrypt {
theRecorder.StopRecord()
theRecorder.ReleaseBuffers()
}
//退化回原始状态
go io.Copy(wrc, wlc)
io.Copy(wlc, wrc)
return
}
} else {
rawWRC = wrc.(*net.TCPConn) //useSecureMethod的一定是客户端此时就是直接给出原始连接
}
waitWRC_CreateChan := make(chan int)
go func(wrcPtr *io.ReadWriter) {
//从 wlccc 读取,向 wrc 写入
// 此时如果ReadFrom那就是 wrc.ReadFrom(wlccc)
//wrc 要实现 ReaderFrom才行, 或者把最底层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.Println(" 才开始Dial 服务端")
}
theRecorder = tlsLayer.NewRecorder()
teeConn := tlsLayer.NewTeeConn(rawWRC, theRecorder)
tlsConn, err := proxy_client.GetTLS_Client().Handshake(teeConn)
if err != nil {
if utils.CanLogErr() {
log.Println("failed in handshake outClient tls", ", Reason: ", err)
}
return
}
wrc, err = proxy_client.Handshake(tlsConn, clientAddr)
if err != nil {
if utils.CanLogErr() {
log.Println("failed in handshake to", clientAddr.String(), ", Reason: ", err)
}
return
}
*wrcPtr = wrc
waitWRC_CreateChan <- 1
} else {
if tlsLayer.PDD {
log.Println(" 第", 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.Println("R 准备发送特殊命令, 以及保存的TLS内容", 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 {
nextFreeData := bs[nextI:]
if tlsLayer.PDD {
log.Println("R 从Recorder 提取 真实TLS内容", len(nextFreeData))
}
rawWRC.Write(nextFreeData)
}
theRecorder.StopRecord()
theRecorder.ReleaseBuffers()
}
} else {
if tlsLayer.PDD {
log.Println("pass write")
}
wrc.Write(p[:n])
if wlcdc.R.DefinitelyNotTLS {
isbad = true
}
}
}
utils.PutPacket(p)
if isbad {
//直接退化成普通Copy
if tlsLayer.PDD {
log.Println("SpliceRead R方向 退化……", wlcdc.R.GetFailReason())
}
io.Copy(wrc, wlc)
return
}
if isgood {
if tlslazy_willuseSystemCall {
runtime.Gosched() //详情请阅读我的 xray_splice- 文章,了解为什么这个调用是必要的
}
if tlsLayer.PDD {
log.Println("成功SpliceRead R方向")
num, e1 := rawWRC.ReadFrom(wlccc_raw)
log.Println("SpliceRead R方向 传完,", 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.Println("准备从wrc读")
}
n, err := wrc.Read(p)
if err != nil {
break
}
if tlsLayer.PDD {
log.Println("从wrc读到数据", n, "准备写入wlcdc")
}
wn, _ := wlcdc.Write(p[:n])
if tlsLayer.PDD {
log.Println("写入wlcdc完成", 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(-1) //这里就暂时不要退出程序了,毕竟理论上有可能由一些黑客来触发这里。
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())
}
io.Copy(wlc, wrc)
return
}
if isgood2 {
if tlsLayer.PDD {
log.Println("成功SpliceRead W方向,准备 直连对拷")
}
if tlslazy_willuseSystemCall {
runtime.Gosched() //详情请阅读我的 xray_splice- 文章了解为什么这个调用是必要的。不过不一定对因为我很笨只是看xray的代码有这一行
}
if tlsLayer.PDD {
num, e2 := wlccc_raw.ReadFrom(rawWRC) //看起来是ReadFrom实际上是向 wlccc_raw进行Write即箭头向左
log.Println("SpliceRead W方向 传完,", e2, ", 长度:", num)
} else {
wlccc_raw.ReadFrom(rawWRC)
}
}
}