Files
v2ray_simple/main.go
hahahrfool ce735dbb99 修订udp代码; dial配置 添加 fullcone 选项;默认为非fullcone
现在整个程序均通过了go test, main 也可以正常运行了。

Relay_UDP 函数添加流量计数;

发现之前 Relay函数的流量计数 在main.go里参数传反了,导致实际上计数的是上传而不是下载,已修复

对fullcone的情况做了特别考量。MsgConn的 Close函数在fullcone时不能随便被调用。

因此我添加了一个 CloseConnWithRaddr(raddr Addr) error  方法,以及 Fullcone() bool     方法

在utils包的init部分使用 rand 随机种子
2022-04-08 20:31:59 +08:00

1295 lines
36 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"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
"runtime/pprof"
"strings"
"sync/atomic"
"syscall"
"github.com/hahahrfool/v2ray_simple/grpc"
"github.com/hahahrfool/v2ray_simple/httpLayer"
"github.com/hahahrfool/v2ray_simple/netLayer"
"github.com/hahahrfool/v2ray_simple/quic"
"github.com/hahahrfool/v2ray_simple/tlsLayer"
"github.com/hahahrfool/v2ray_simple/utils"
"github.com/hahahrfool/v2ray_simple/ws"
"github.com/pkg/profile"
"go.uber.org/zap"
"github.com/hahahrfool/v2ray_simple/proxy"
"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 StandardConf
directClient, _, _ = proxy.ClientFromURL("direct://")
defaultOutClient proxy.Client
default_uuid string
allServers = make([]proxy.Server, 0, 8)
allClients = make([]proxy.Client, 0, 8)
listenerArray []net.Listener
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
dnsMachine *netLayer.DNSMachine
startPProf bool
startMProf bool
)
func init() {
flag.BoolVar(&startPProf, "pp", false, "pprof")
flag.BoolVar(&startMProf, "mp", false, "memory pprof")
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. Only valid when loglevel=0")
}
func main() {
flag.Parse()
if cmdPrintVer {
printVersion_simple()
//根据 cmdPrintVer 的定义, 我们直接退出
os.Exit(0)
} else {
printVersion()
}
utils.InitLog()
if startPProf {
f, _ := os.OpenFile("cpu.pprof", os.O_CREATE|os.O_RDWR, 0644)
defer f.Close()
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
if startMProf {
//若不使用 NoShutdownHook, 我们ctrl+c退出时不会产生 pprof文件
p := profile.Start(profile.MemProfile, profile.MemProfileRate(1), profile.NoShutdownHook)
defer p.Stop()
}
if err := loadConfig(); err != nil && !isFlexible() {
log.Printf("no config exist, and no api server or interactive cli enabled, exiting...")
os.Exit(-1)
}
netLayer.Prepare()
fmt.Printf("Log Level:%d\n", utils.LogLevel)
fmt.Printf("UseReadv:%t\n", netLayer.UseReadv)
runPreCommands()
var defaultInServer proxy.Server
//load server and routePolicy
switch confMode {
case simpleMode:
var hase bool
var eie utils.ErrInErr
defaultInServer, hase, eie = proxy.ServerFromURL(simpleConf.Server_ThatListenPort_Url)
if hase {
log.Fatalf("can not create local server: %s\n", eie)
}
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:
loadCommonComponentsFromStandardConf()
//虽然标准模式支持多个Server目前先只考虑一个
//多个Server存在的话则必须要用 tag指定路由; 然后我们需在预先阶段就判断好tag指定的路由
if len(standardConf.Listen) < 1 {
utils.Warn("no listen in config settings")
break
}
for _, serverConf := range standardConf.Listen {
thisConf := serverConf
if thisConf.Uuid == "" && default_uuid != "" {
thisConf.Uuid = default_uuid
}
thisServer, err := proxy.NewServer(thisConf)
if err != nil {
if ce := utils.CanLogErr("can not create local server:"); ce != nil {
ce.Write(zap.Error(err))
}
continue
}
allServers = append(allServers, thisServer)
if tag := thisServer.GetTag(); tag != "" {
serversTagMap[tag] = thisServer
}
}
}
// load client
switch confMode {
case simpleMode:
var hase bool
var eie utils.ErrInErr
defaultOutClient, hase, eie = proxy.ClientFromURL(simpleConf.Client_ThatDialRemote_Url)
if hase {
log.Fatalln("can not create remote client: ", eie)
}
case standardMode:
if len(standardConf.Dial) < 1 {
utils.Warn("no dial in config settings")
break
}
for _, thisConf := range standardConf.Dial {
if thisConf.Uuid == "" && default_uuid != "" {
thisConf.Uuid = default_uuid
}
thisClient, err := proxy.NewClient(thisConf)
if err != nil {
if ce := utils.CanLogErr("can not create remote client: "); ce != nil {
ce.Write(zap.Error(err))
}
continue
}
allClients = append(allClients, thisClient)
if tag := thisClient.GetTag(); tag != "" {
clientsTagMap[tag] = thisClient
}
}
if len(allClients) > 0 {
defaultOutClient = allClients[0]
} else {
defaultOutClient = directClient
}
}
configFileQualifiedToRun := false
if (defaultInServer != nil || len(allServers) > 0) && defaultOutClient != nil {
configFileQualifiedToRun = true
if confMode == simpleMode {
listenSer(defaultInServer, defaultOutClient, true)
} else {
for _, inServer := range allServers {
listenSer(inServer, defaultOutClient, true)
}
}
}
//没配置可用的listen或者dail而且还无法动态更改配置
if !configFileQualifiedToRun && !isFlexible() {
utils.Fatal("No valid proxy settings available, exit now.")
return
}
if enableApiServer {
go checkConfigAndTryRunApiServer()
}
if interactive_mode {
runCli()
}
{
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, os.Kill, syscall.SIGTERM)
<-osSignals
//在程序ctrl+C关闭时, 会主动Close所有的监听端口. 主要是被报告windows有时退出程序之后, 端口还是处于占用状态.
// 用下面代码以试图解决端口占用问题.
for _, listener := range listenerArray {
if listener != nil {
listener.Close()
}
}
}
}
//非阻塞.
// 若 not_temporary 为true, 则生成的listener将会被添加到 listenerArray 中
func listenSer(inServer proxy.Server, defaultOutClientForThis proxy.Client, not_temporary bool) (thisListener net.Listener) {
var err error
//quic
if inServer.IsHandleInitialLayers() {
//如果像quic一样自行处理传输层至tls层之间的部分则我们跳过 handleNewIncomeConnection 函数
// 拿到连接后直接调用 handshakeInserver_and_passToOutClient
handleFunc := inServer.HandleInitialLayersFunc()
if handleFunc == nil {
panic("inServer.IsHandleInitialLayers but inServer.HandleInitialLayersFunc() returns nil")
}
//baseConn可以为nilquic就是如此
newConnChan, baseConn := handleFunc()
if newConnChan == nil {
utils.Error("StarthandleInitialLayers can't extablish baseConn")
return
}
go func() {
for {
newConn, ok := <-newConnChan
if !ok {
utils.Error("read from SuperProxy not ok")
quic.CloseSession(baseConn)
return
}
iics := incomingInserverConnState{
wrappedConn: newConn,
//baseLocalConn: baseConn, //quic是没有baseLocalConn的因为基于udp
// 或者说虽然有baseConn但是并不与子连接一一对应.那个conn更类似于一个listener
inServer: inServer,
defaultClient: defaultOutClientForThis,
}
go handshakeInserver_and_passToOutClient(iics)
}
}()
if ce := utils.CanLogInfo("Listening"); ce != nil {
ce.Write(
zap.String("protocol", proxy.GetFullName(inServer)),
zap.String("addr", inServer.AddrStr()),
)
}
return
}
handleFunc := func(conn net.Conn) {
handleNewIncomeConnection(inServer, defaultOutClientForThis, conn, !not_temporary)
}
network := inServer.Network()
thisListener, err = netLayer.ListenAndAccept(network, inServer.AddrStr(), handleFunc)
if err == nil {
if ce := utils.CanLogInfo("Listening"); ce != nil {
ce.Write(
zap.String("protocol", proxy.GetFullName(inServer)),
zap.String("addr", inServer.AddrStr()),
)
}
if not_temporary {
listenerArray = append(listenerArray, thisListener)
}
} else {
if err != nil {
utils.ZapLogger.Error(
"can not listen inServer on %s %s\n", zap.String("addr", inServer.AddrStr()), zap.Error(err))
}
}
return
}
type incomingInserverConnState struct {
//baseLocalConn 是来自客户端的原始网络层链接
//wrappedConn是层层握手后包装的链接;
// 在多路复用的情况下, 可能产生多个 IncomingInserverConnState
// 共用一个 baseLocalConn, 但是 wrappedConn 各不相同。
//这里说的多路复用基本指的就是grpc/quic; 如果是 vless内嵌 mux.cool 的话不属于这种情况.
// 要区分 多路复用的包装 是在 vless等代理的握手验证 的外部 还是 内部
baseLocalConn, wrappedConn net.Conn
inServer proxy.Server
defaultClient proxy.Client
cachedRemoteAddr string
theRequestPath string
inServerTlsConn *tlsLayer.Conn
inServerTlsRawReadRecorder *tlsLayer.Recorder
shouldFallback bool
forbidDNS_orRoute bool
theFallbackFirstBuffer *bytes.Buffer
isTlsLazyServerEnd bool
shouldCloseInSerBaseConnWhenFinish bool
routedToDirect bool
}
// handleNewIncomeConnection 会处理 网络层至高级层的数据,
// 然后将代理层的处理发往 handshakeInserver_and_passToOutClient 函数。
func handleNewIncomeConnection(inServer proxy.Server, defaultClientForThis proxy.Client, thisLocalConnectionInstance net.Conn, forbidDNS_orRoute bool) {
//log.Println("handleNewIncomeConnection called", defaultClientForThis)
iics := incomingInserverConnState{
baseLocalConn: thisLocalConnectionInstance,
inServer: inServer,
defaultClient: defaultClientForThis,
forbidDNS_orRoute: forbidDNS_orRoute,
}
iics.isTlsLazyServerEnd = tls_lazy_encrypt && canLazyEncryptServer(inServer)
wrappedConn := thisLocalConnectionInstance
if ce := utils.CanLogInfo("New Accepted Conn"); ce != nil {
addrstr := wrappedConn.RemoteAddr().String()
ce.Write(
zap.String("from", addrstr),
zap.String("handler", proxy.GetVSI_url(inServer)),
)
iics.cachedRemoteAddr = addrstr
}
//此时baseLocalConn里面 正常情况下, 服务端看到的是 客户端的golang的tls 拨号发出的 tls数据
// 客户端看到的是 socks5的数据 我们首先就是要看看socks5里的数据是不是tls而socks5自然 IsUseTLS 是false
// 如果是服务端的话,那就是 inServer.IsUseTLS == true, 此时,我们正常握手,然后我们需要判断的是它承载的数据
// 每次tls试图从 原始连接 读取内容时,都会附带把原始数据写入到 这个 Recorder中
if inServer.IsUseTLS() {
if iics.isTlsLazyServerEnd {
iics.inServerTlsRawReadRecorder = tlsLayer.NewRecorder()
iics.inServerTlsRawReadRecorder.StopRecord() //先不记录因为一开始是我们自己的tls握手包没有意义
teeConn := tlsLayer.NewTeeConn(wrappedConn, iics.inServerTlsRawReadRecorder)
wrappedConn = teeConn
}
tlsConn, err := inServer.GetTLS_Server().Handshake(wrappedConn)
if err != nil {
if ce := utils.CanLogErr("tls handshake failed"); ce != nil {
ce.Write(
zap.String("inServer", inServer.AddrStr()),
zap.Error(err),
)
}
wrappedConn.Close()
return
}
if iics.isTlsLazyServerEnd {
//此时已经握手完毕,可以记录了
iics.inServerTlsRawReadRecorder.StartRecord()
}
iics.inServerTlsConn = tlsConn
wrappedConn = tlsConn
}
//log.Println("handshake passed tls")
if adv := inServer.AdvancedLayer(); adv != "" {
switch adv {
//quic虽然也是adv层但是因为根本没调用 handleNewIncomeConnection 函数,所以不在此处理
//
case "grpc":
//grpc不太一样, 它是多路复用的
// 每一条建立好的 grpc 可以随时产生对新目标的请求,
// 我们直接循环监听然后分别用 新goroutine发向 handshakeInserver_and_passToOutClient
if ce := utils.CanLogDebug("start upgrade grpc"); ce != nil {
ce.Write()
}
grpcs := inServer.GetGRPC_Server() //这个grpc server是在配置阶段初始化好的.
grpcs.StartHandle(wrappedConn)
//start之后客户端就会利用同一条tcp链接 来发送多个 请求,自此就不能直接用原来的链接了;
// 新的子请求被 grpc包 抽象成了 抽象的 conn
//遇到chan被关闭的情况后就会自动关闭底层连接并退出整个函数。
for {
newGConn, ok := <-grpcs.NewConnChan
if !ok {
if ce := utils.CanLogWarn("grpc getNewSubConn not ok"); ce != nil {
ce.Write()
}
iics.baseLocalConn.Close()
return
}
iics.wrappedConn = newGConn
go handshakeInserver_and_passToOutClient(iics)
}
case "ws":
//从ws开始就可以应用fallback了
// 但是不能先upgrade, 因为我们要用path分流, 所以我们要先预读;
// 否则的话正常的http流量频繁地被用不到的 ws 过滤器处理,会损失很多性能,而且gobwas没办法简洁地保留初始buffer.
var rp httpLayer.RequestParser
re := rp.ReadAndParse(wrappedConn)
if re != nil {
if re == httpLayer.ErrNotHTTP_Request {
if ce := utils.CanLogErr("WS check ErrNotHTTP_Request"); ce != nil {
ce.Write(
zap.String("handler", inServer.AddrStr()),
)
}
} else {
if ce := utils.CanLogErr("WS check handshake read failed"); ce != nil {
ce.Write(
zap.String("handler", inServer.AddrStr()),
)
}
}
wrappedConn.Close()
return
}
wss := inServer.GetWS_Server()
if rp.Method != "GET" || wss.Thepath != rp.Path {
iics.theRequestPath = rp.Path
iics.theFallbackFirstBuffer = rp.WholeRequestBuf
if ce := utils.CanLogDebug("WS check err"); ce != nil {
ce.Write(
zap.String("handler", inServer.AddrStr()),
zap.String("reason", "path/method not match"),
zap.String("validPath", wss.Thepath),
zap.String("gotMethod", rp.Method),
zap.String("gotPath", rp.Path),
)
}
iics.shouldFallback = true
goto startPass
}
//此时path和method都已经匹配了, 如果还不能通过那就说明后面的header等数据不满足ws的upgrade请求格式, 肯定是非法数据了,也不用再回落
wsConn, err := wss.Handshake(rp.WholeRequestBuf, wrappedConn)
if err != nil {
if ce := utils.CanLogErr("InServer ws handshake failed"); ce != nil {
ce.Write(
zap.String("handler", inServer.AddrStr()),
zap.Error(err),
)
}
wrappedConn.Close()
return
}
wrappedConn = wsConn
} // switch adv
} //if adv !=""
startPass:
iics.wrappedConn = wrappedConn
handshakeInserver_and_passToOutClient(iics)
}
// 本函数 处理inServer的代理层数据并在试图处理 分流和回落后将流量导向目标并开始Copy。
// iics 不使用指针, 因为iics不能公用因为 在多路复用时 iics.wrappedConn 是会变化的。
func handshakeInserver_and_passToOutClient(iics incomingInserverConnState) {
//log.Println("handshakeInserver_and_passToOutClient")
wrappedConn := iics.wrappedConn
inServer := iics.inServer
var wlc io.ReadWriteCloser
var udp_wlc netLayer.MsgConn
var targetAddr netLayer.Addr
var err error
if iics.shouldFallback {
goto checkFallback
}
wlc, udp_wlc, targetAddr, err = inServer.Handshake(wrappedConn)
if err == nil {
//log.Println("inServer handshake passed")
//无错误时直接跳过回落, 直接执行下一个步骤
goto afterLocalServerHandshake
}
////////////////////////////// 回落阶段 /////////////////////////////////////
//下面代码查看是否支持fallback; wlc先设为nil, 当所有fallback都不满足时,可以判断nil然后关闭连接
wlc = nil
udp_wlc = nil
if ce := utils.CanLogWarn("failed in inServer proxy handshake"); ce != nil {
ce.Write(
zap.String("handler", inServer.AddrStr()),
zap.Error(err),
)
}
if !inServer.CanFallback() {
wrappedConn.Close()
return
}
{ //为防止goto 跳过变量定义,使用单独代码块
fe, ok := err.(*utils.ErrFirstBuffer)
if !ok {
// 能fallback 但是返回的 err却不是fallback err证明遇到了更大问题可能是底层read问题所以也不用继续fallback了
wrappedConn.Close()
return
}
iics.theFallbackFirstBuffer = fe.First
if iics.theFallbackFirstBuffer == nil {
//不应该至少能读到1字节的。
panic("No FirstBuffer")
}
}
checkFallback:
//先检查 mainFallback如果mainFallback中各项都不满足 或者根本没有 mainFallback 再检查 defaultFallback
if mainFallback != nil {
utils.Debug("Fallback check")
var thisFallbackType byte
fallback_params := make([]string, 0, 4)
theRequestPath := iics.theRequestPath
if iics.theFallbackFirstBuffer != nil && theRequestPath == "" {
var failreason int
_, theRequestPath, failreason = httpLayer.GetRequestMethod_and_PATH_from_Bytes(iics.theFallbackFirstBuffer.Bytes(), false)
if failreason != 0 {
theRequestPath = ""
}
}
if theRequestPath != "" {
fallback_params = append(fallback_params, theRequestPath)
thisFallbackType |= httpLayer.Fallback_path
}
if inServerTlsConn := iics.inServerTlsConn; inServerTlsConn != nil {
//默认似乎默认tls不会给出alpn和sni项获得的是空值,也许是因为我用了自签名+insecure,所以导致server并不会设置连接好后所协商的ServerName
// 而alpn则也是正常的, 不设置肯定就是空值
alpn := inServerTlsConn.GetAlpn()
if alpn != "" {
fallback_params = append(fallback_params, alpn)
thisFallbackType |= httpLayer.Fallback_alpn
}
sni := inServerTlsConn.GetSni()
if sni != "" {
fallback_params = append(fallback_params, sni)
thisFallbackType |= httpLayer.Fallback_sni
}
}
fbAddr := mainFallback.GetFallback(thisFallbackType, fallback_params...)
if ce := utils.CanLogDebug("Fallback check"); ce != nil {
ce.Write(
zap.String("matched", fbAddr.String()),
)
}
if fbAddr != nil {
targetAddr = *fbAddr
wlc = wrappedConn
goto afterLocalServerHandshake
}
}
//默认回落, 每个listen配置 都可 有一个自己独享的默认回落
if defaultFallbackAddr := inServer.GetFallback(); defaultFallbackAddr != nil {
targetAddr = *defaultFallbackAddr
wlc = wrappedConn
}
afterLocalServerHandshake:
if wlc == nil && udp_wlc == nil {
//无wlc证明 inServer 握手失败,且 没有任何回落可用, 直接return
utils.Debug("invalid request and no matched fallback, hung up")
wrappedConn.Close()
return
}
//此时 targetAddr已经完全确定
////////////////////////////// DNS解析阶段 /////////////////////////////////////
//dns解析会试图解析域名并将ip放入 targetAddr中
// 因为在direct时netLayer.Addr 拨号时会优先选用ip拨号而且我们下面的分流阶段 如果使用ip的话
// 可以利用geoip文件, 可以做到国别分流.
if !iics.forbidDNS_orRoute && dnsMachine != nil && (targetAddr.Name != "" && len(targetAddr.IP) == 0) && targetAddr.Network != "unix" {
if ce := utils.CanLogDebug("Dns querying"); ce != nil {
ce.Write(zap.String("domain", targetAddr.Name))
}
ip := dnsMachine.Query(targetAddr.Name)
if ip != nil {
targetAddr.IP = ip
if ce2 := utils.CanLogDebug("Dns result"); ce2 != nil {
ce2.Write(zap.String("domain", targetAddr.Name), zap.String("ip", ip.String()))
}
}
}
////////////////////////////// 分流阶段 /////////////////////////////////////
var client proxy.Client = iics.defaultClient
routed := false
//尝试分流, 获取到真正要发向 的 outClient
if !iics.forbidDNS_orRoute && routePolicy != nil && !inServer.CantRoute() {
desc := &netLayer.TargetDescription{
Addr: targetAddr,
Tag: inServer.GetTag(),
}
if ce := utils.CanLogDebug("try routing"); ce != nil {
ce.Write(zap.Any("source", desc))
}
outtag := routePolicy.GetOutTag(desc)
if outtag == "direct" {
client = directClient
iics.routedToDirect = true
routed = true
if ce := utils.CanLogInfo("Route to direct"); ce != nil {
ce.Write(
zap.String("target", targetAddr.UrlString()),
)
}
} else {
if tagC, ok := clientsTagMap[outtag]; ok {
client = tagC
routed = true
if ce := utils.CanLogInfo("Route"); ce != nil {
ce.Write(
zap.String("to outtag", outtag),
zap.String("with addr", client.AddrStr()),
zap.String("and protocol", proxy.GetFullName(client)),
zap.Any("for source", desc),
)
}
}
}
}
if !routed {
if ce := utils.CanLogDebug("Default Route"); ce != nil {
ce.Write(
zap.Any("source", targetAddr.String()),
zap.String("client", proxy.GetFullName(client)),
zap.String("addr", client.AddrStr()),
)
}
}
////////////////////////////// 特殊处理阶段 /////////////////////////////////////
// 下面几段用于处理 tls lazy
var isTlsLazy_clientEnd bool
if targetAddr.IsUDP() {
//udp数据是无法splice的因为不是入口处是真udp就是出口处是真udp; 同样暂不考虑级连情况.
if iics.isTlsLazyServerEnd {
iics.isTlsLazyServerEnd = false
//此时 inServer的tls还被包了一个Recorder所以我们赶紧关闭记录省着产生额外开销
iics.inServerTlsRawReadRecorder.StopRecord()
}
} else {
isTlsLazy_clientEnd = tls_lazy_encrypt && canLazyEncryptClient(client)
}
// 我们在客户端 lazy_encrypt 探测时读取socks5 传来的信息,因为这个 就是要发送到 outClient 的信息所以就不需要等包上vless、tls后再判断了, 直接解包 socks5 对 tls 进行判断
//
// 而在服务端探测时,因为 客户端传来的连接 包了 tls所以要在tls解包后, vless 解包后,再进行判断;
// 所以总之都是要在 inServer 判断 wlc; 总之,含义就是,去检索“用户承载数据”的来源
if isTlsLazy_clientEnd || iics.isTlsLazyServerEnd {
if tlsLayer.PDD {
log.Printf("loading TLS SniffConn %t %t\n", isTlsLazy_clientEnd, iics.isTlsLazyServerEnd)
}
wlc = tlsLayer.NewSniffConn(iics.baseLocalConn, wlc, isTlsLazy_clientEnd, tls_lazy_secure)
}
//这一段代码是去判断是否要在转发结束后自动关闭连接
//如果目标是udp则要分情况讨论
//
// 这里 因为 vless v1 的 CRUMFURS 信道 会对 wrappedConn 进行 keep alive
// 而且具体的传递信息的部分并不是在main函数中处理而是自己的go routine所以不能直接关闭 wrappedConn
// 所以要分情况进行 defer wrappedConn.Close()。
// 然后这里是设置 iics.shouldCloseBaseConnWhenComplete, 然后在dialClient函数里再实际 defer
if targetAddr.IsUDP() {
switch inServer.Name() {
case "vless":
if targetAddr.Name == vless.CRUMFURS_Established_Str {
// 预留了 CRUMFURS 信道的话,就不要关闭 wrappedConn
// 而且也不在这里处理监听事件client自己会在额外的 goroutine里处理
// server也一样会在特定的场合给 CRUMFURS 传值这个机制是与main函数无关的
// 而且 wrappedConn 会被 inServer 保存起来,用于后面的 unknownRemoteAddrMsgWriter
return
} else {
//如果不是CRUMFURS命令那就是普通的针对某udp地址的连接见下文 uniExtractor 的使用
iics.shouldCloseInSerBaseConnWhenFinish = true
}
case "socks5":
// UDP Associate
// 因为socks5的 UDP Associate 办法是较为特殊的不使用现有tcp而是新建立udp所以此时该tcp连接已经没用了
// 但是根据socks5标准这个tcp链接同样是 keep alive的否则客户端就会认为服务端挂掉了.
// 另外,此时 targetAddr.IsUDP 只是用于告知此链接是udp Associate并不包含实际地址信息
default:
iics.shouldCloseInSerBaseConnWhenFinish = true
}
} else {
//lazy_encrypt情况比较特殊基础连接何时被关闭会在tlslazy相关代码中处理。
// 如果不是lazy的情况的话转发结束后要自动关闭
if !iics.isTlsLazyServerEnd {
//实测 grpc.Conn 被调用了Close 也不会实际关闭连接而是会卡住阻塞直到底层tcp连接被关闭后才会返回
// 但是我们还是 直接避免这种情况
if !inServer.IsMux() {
iics.shouldCloseInSerBaseConnWhenFinish = true
}
}
}
////////////////////////////// 拨号阶段 /////////////////////////////////////
//log.Println("will dial", client)
dialClient(iics, targetAddr, client, isTlsLazy_clientEnd, wlc, udp_wlc, false)
}
// dialClient 对实际client进行拨号处理传输层, tls层, 高级层等所有层级后,进行代理层握手,
// 然后 进行实际转发(Copy)。
// targetAddr为用户所请求的地址。
//client为真实要拨号的client可能会与iics里的defaultClient不同。以client为准。
// wlc为调用者所提供的 此请求的 来源 链接。wlc主要用于 Copy阶段.
// noCopy是为了让其它调用者自行处理 转发 时使用。
func dialClient(iics incomingInserverConnState, targetAddr netLayer.Addr, client proxy.Client, isTlsLazy_clientEnd bool, wlc io.ReadWriteCloser, udp_wlc netLayer.MsgConn, noCopy bool) {
isudp := targetAddr.IsUDP()
if iics.shouldCloseInSerBaseConnWhenFinish && !noCopy {
if iics.baseLocalConn != nil {
defer iics.baseLocalConn.Close()
}
}
if wlc != nil {
defer wlc.Close()
}
var err error
//先确认拨号地址
//direct的话自己是没有目的地址的直接使用 请求的地址
// 而其它代理的话, realTargetAddr会被设成实际配置的代理的地址
var realTargetAddr netLayer.Addr = targetAddr
if ce := utils.CanLogDebug("request isn't the appointed domain"); ce != nil {
if uniqueTestDomain != "" && uniqueTestDomain != targetAddr.Name {
ce.Write(
zap.String("request", targetAddr.String()),
zap.String("uniqueTestDomain", uniqueTestDomain),
)
return
}
}
if ce := utils.CanLogInfo("Request"); ce != nil {
ce.Write(
zap.String("from", iics.cachedRemoteAddr),
zap.String("target", targetAddr.UrlString()),
zap.String("through", proxy.GetVSI_url(client)),
)
}
if client.AddrStr() != "" {
realTargetAddr, err = netLayer.NewAddr(client.AddrStr())
if err != nil {
if ce := utils.CanLogErr("dial client convert addr err"); ce != nil {
ce.Write(zap.Error(err))
}
return
}
realTargetAddr.Network = client.Network()
}
var clientEndRemoteClientTlsRawReadRecorder *tlsLayer.Recorder
var clientConn net.Conn
var grpcClientConn grpc.ClientConn
var dailedCommonConn any
//如果是单路的, 则我们在此dial, 如果是多路复用, 则不行, 因为要复用同一个连接
// Instead, 我们要试图从grpc中取出已经拨号好了的 grpc链接
switch client.AdvancedLayer() {
case "grpc":
grpcClientConn = grpc.GetEstablishedConnFor(&realTargetAddr)
if grpcClientConn != nil {
//如果有已经建立好的连接,则跳过拨号阶段
goto advLayerStep
}
case "quic":
//quic这里并不是在tls层基础上进行dial而是直接从传输层dial 或者获取之前已经存在的session
dailedCommonConn = client.GetQuic_Client().DialCommonConn(false, nil)
if dailedCommonConn != nil {
//跳过tls阶段
goto advLayerStep
} else {
//dail失败, 直接return掉
return
}
}
clientConn, err = realTargetAddr.Dial()
if err != nil {
if err == netLayer.ErrMachineCantConnectToIpv6 {
//如果一开始就知道机器没有ipv6地址那么该错误就不是error等级而是warning等级
if ce := utils.CanLogWarn("Machine HasNo ipv6 but got ipv6 request"); ce != nil {
ce.Write(
zap.String("target", realTargetAddr.String()),
)
}
} else {
//虽然拨号失败,但是不能认为我们一定有错误, 因为很可能申请的ip本身就是不可达的, 所以不是error等级而是warn等级
if ce := utils.CanLogWarn("failed dialing"); ce != nil {
ce.Write(
zap.String("target", realTargetAddr.String()),
zap.Error(err),
)
}
}
return
}
defer clientConn.Close()
//log.Println("dial real addr ok", realTargetAddr)
////////////////////////////// tls握手阶段 /////////////////////////////////////
if client.IsUseTLS() { //即客户端
if isTlsLazy_clientEnd {
if tls_lazy_secure && wlc != nil {
// 如果使用secure办法则我们每次不能先拨号而是要detect用户的首包后再拨号
// 这种情况只需要客户端操作, 此时我们wrc直接传入原始的 刚拨号好的 tcp连接即 clientConn
// 而且为了避免黑客攻击或探测我们要使用uuid作为特殊指令此时需要 UserServer和 UserClient
if uc := client.(proxy.UserClient); uc != nil {
tryTlsLazyRawCopy(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 {
if ce := utils.CanLogErr("failed in handshake outClient tls"); ce != nil {
ce.Write(zap.String("target", targetAddr.String()), zap.Error(err))
}
return
}
clientConn = tlsConn
}
////////////////////////////// 高级层握手阶段 /////////////////////////////////////
advLayerStep:
if adv := client.AdvancedLayer(); adv != "" {
switch adv {
case "quic":
qclient := client.GetQuic_Client()
clientConn, err = qclient.DialSubConn(dailedCommonConn)
if err != nil {
eStr := err.Error()
if strings.Contains(eStr, "too many") {
if ce := utils.CanLogDebug("DialSubConnFunc got full session, open another one"); ce != nil {
ce.Write(
zap.String("full reason", eStr),
)
}
//第一条连接已满再开一条session
dailedCommonConn = qclient.DialCommonConn(true, dailedCommonConn)
if dailedCommonConn == nil {
//再dial还是nil也许是暂时性的网络错误, 先关闭
return
}
clientConn, err = qclient.DialSubConn(dailedCommonConn)
if err != nil {
if ce := utils.CanLogErr("DialSubConnFunc failed with redial new session"); ce != nil {
ce.Write(
zap.Error(err),
)
}
return
}
} else {
if ce := utils.CanLogErr("DialSubConnFunc failed"); ce != nil {
ce.Write(
zap.Error(err),
)
}
return
}
}
case "grpc":
if grpcClientConn == nil {
grpcClientConn, err = grpc.ClientHandshake(clientConn, &realTargetAddr)
if err != nil {
if ce := utils.CanLogErr("grpc.ClientHandshake failed"); ce != nil {
ce.Write(zap.Error(err))
}
if iics.baseLocalConn != nil {
iics.baseLocalConn.Close()
}
return
}
}
clientConn, err = grpc.DialNewSubConn(client.Path(), grpcClientConn, &realTargetAddr)
if err != nil {
if ce := utils.CanLogErr("grpc.DialNewSubConn failed"); ce != nil {
ce.Write(zap.Error(err))
//如果底层tcp连接被关闭了的话错误会是
// rpc error: code = Unavailable desc = connection error: desc = "transport: failed to write client preface: tls: use of closed connection"
}
if iics.baseLocalConn != nil {
iics.baseLocalConn.Close()
}
return
}
case "ws":
wsClient := client.GetWS_Client()
var ed []byte
if wsClient.UseEarlyData && wlc != nil {
//若配置了 MaxEarlyDataLen则我们先读一段;
edBuf := utils.GetPacket()
edBuf = edBuf[:ws.MaxEarlyDataLen]
n, e := wlc.Read(edBuf)
if e != nil {
if ce := utils.CanLogErr("failed to read ws early data"); ce != nil {
ce.Write(zap.Error(e))
}
return
}
ed = edBuf[:n]
//log.Println("will send early data", n, ed)
}
// 我们verysimple的架构是 ws握手之后再进行vless握手
// 但是如果要传输earlydata的话则必须要在握手阶段就 预知 vless 的所有数据才行
// 所以我们需要一种特殊方法
var wc net.Conn
if len(ed) > 0 {
wc, err = wsClient.HandshakeWithEarlyData(clientConn, ed)
} else {
wc, err = wsClient.Handshake(clientConn)
}
if err != nil {
if ce := utils.CanLogErr("failed in handshake ws"); ce != nil {
ce.Write(
zap.String("target", targetAddr.String()),
zap.Error(err),
)
}
return
}
clientConn = wc
}
}
////////////////////////////// 代理层 握手阶段 /////////////////////////////////////
if !isudp {
wrc, err := client.Handshake(clientConn, targetAddr)
if err != nil {
if ce := utils.CanLogErr("Handshake client failed"); ce != nil {
ce.Write(
zap.String("target", targetAddr.String()),
zap.Error(err),
)
}
return
}
//log.Println("all handshake finished")
////////////////////////////// 实际转发阶段 /////////////////////////////////////
if noCopy {
return
}
if tls_lazy_encrypt && !iics.routedToDirect {
// 我们加了回落之后,就无法确定 “未使用tls的outClient 一定是在服务端” 了
if isTlsLazy_clientEnd {
if client.IsUseTLS() {
//必须是 UserClient
if userClient := client.(proxy.UserClient); userClient != nil {
tryTlsLazyRawCopy(false, userClient, nil, netLayer.Addr{}, wrc, wlc, iics.baseLocalConn, true, clientEndRemoteClientTlsRawReadRecorder)
return
}
}
} else if iics.isTlsLazyServerEnd {
// 最新代码已经确认使用uuid 作为 “特殊指令”所以要求Server必须是一个 proxy.UserServer
// 否则将无法开启splice功能。这是为了防止0-rtt 探测;
if userServer, ok := iics.inServer.(proxy.UserServer); ok {
tryTlsLazyRawCopy(false, nil, userServer, netLayer.Addr{}, wrc, wlc, iics.baseLocalConn, false, iics.inServerTlsRawReadRecorder)
return
}
}
}
if iics.theFallbackFirstBuffer != nil {
//这里注意,因为是把 tls解密了之后的数据发送到目标地址所以这种方式只支持转发到本机纯http服务器
wrc.Write(iics.theFallbackFirstBuffer.Bytes())
utils.PutBytes(iics.theFallbackFirstBuffer.Bytes()) //这个Buf不是从utils.GetBuf创建的而是从一个 GetBytes的[]byte 包装 的所以我们要PutBytes而不是PutBuf
}
atomic.AddInt32(&activeConnectionCount, 1)
downloadBytes := netLayer.Relay(&realTargetAddr, wrc, wlc)
atomic.AddInt32(&activeConnectionCount, -1)
atomic.AddUint64(&allDownloadBytesSinceStart, uint64(downloadBytes))
return
} else {
udp_wrc, err := client.EstablishUDPChannel(clientConn, targetAddr)
if err != nil {
if ce := utils.CanLogErr("EstablishUDPChannel failed"); ce != nil {
ce.Write(
zap.String("target", targetAddr.String()),
zap.Error(err),
)
}
return
}
if noCopy {
return
}
if iics.theFallbackFirstBuffer != nil {
udp_wrc.WriteTo(iics.theFallbackFirstBuffer.Bytes(), targetAddr)
utils.PutBytes(iics.theFallbackFirstBuffer.Bytes())
}
atomic.AddInt32(&activeConnectionCount, 1)
downloadBytes := netLayer.RelayUDP(udp_wrc, udp_wlc)
atomic.AddInt32(&activeConnectionCount, -1)
atomic.AddUint64(&allDownloadBytesSinceStart, uint64(downloadBytes))
return
}
}