Files
v2ray_simple/main.go
2022-03-29 21:58:23 +08:00

1109 lines
32 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"
"syscall"
"github.com/hahahrfool/v2ray_simple/grpc"
"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.ClientFromURL("direct://")
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
)
func init() {
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 main() {
printVersion()
flag.Parse()
cmdLL := utils.LogLevel
cmdUseReadv := netLayer.UseReadv
loadConfig()
if confMode < 0 {
log.Fatal("no config exist")
}
//有点尴尬, 读取配置文件必须要用命令行参数,而配置文件里的部分配置又会覆盖部分命令行参数
if cmdLL != utils.DefaultLL && utils.LogLevel != cmdLL {
//配置文件配置了日志等级, 但是因为 命令行给出的值优先, 所以要设回
utils.LogLevel = cmdLL
}
if cmdUseReadv != netLayer.UseReadv {
//配置文件配置了readv, 但是因为 命令行给出的值优先, 所以要设回
netLayer.UseReadv = cmdUseReadv
}
fmt.Println("Log Level:", utils.LogLevel)
fmt.Println("UseReadv:", netLayer.UseReadv)
runPreCommands()
var err error
var defaultInServer proxy.Server
//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)
}
}
var defaultOutClient proxy.Client
// 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]
}
// 后台运行主代码而main函数只监听中断信号
// TODO: 未来main函数可以推出 交互模式,等未来推出动态增删用户、查询流量等功能时就有用;
// 或可用于交互生成自己想要的配置
if confMode == simpleMode {
listenSer(defaultInServer, defaultOutClient)
} else {
for _, inServer := range allServers {
listenSer(inServer, defaultOutClient)
}
}
{
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, os.Kill, syscall.SIGTERM)
<-osSignals
}
}
//非阻塞
func listenSer(inServer proxy.Server, defaultOutClientForThis proxy.Client) {
//quic
if inServer.IsHandleInitialLayers() {
newConnChan, baseConn := inServer.StarthandleInitialLayers()
if newConnChan == nil || baseConn == nil {
if utils.CanLogErr() {
log.Println("StarthandleInitialLayers can't extablish baseConn")
}
return
}
for {
newConn, ok := <-newConnChan
if !ok {
if utils.CanLogErr() {
log.Println("read from SuperProxy not ok")
}
baseConn.Close()
return
}
iics := incomingInserverConnState{
wrappedConn: newConn,
baseLocalConn: baseConn,
inServer: inServer,
}
go handshakeInserver_and_passToOutClient(iics)
}
}
handleFunc := func(conn net.Conn) {
handleNewIncomeConnection(inServer, defaultOutClientForThis, conn)
}
network := inServer.Network()
err := netLayer.ListenAndAccept(network, inServer.AddrStr(), handleFunc)
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)
}
}
}
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
theFallbackFirstBuffer *bytes.Buffer
isTlsLazyServerEnd bool
shouldCloseBaseConnAfterCopyComplete bool
routedToDirect bool
}
// handleNewIncomeConnection 会处理 网络层至高级层的数据,
// 然后将代理层的处理发往 handshakeInserver_and_passToOutClient 函数。
func handleNewIncomeConnection(inServer proxy.Server, defaultClientForThis proxy.Client, thisLocalConnectionInstance net.Conn) {
iics := incomingInserverConnState{
baseLocalConn: thisLocalConnectionInstance,
inServer: inServer,
defaultClient: defaultClientForThis,
}
iics.isTlsLazyServerEnd = tls_lazy_encrypt && canLazyEncryptServer(inServer)
wrappedConn := thisLocalConnectionInstance
if utils.CanLogInfo() {
str := wrappedConn.RemoteAddr().String()
log.Println("New Accepted Conn from", str, ", being handled by "+proxy.GetVSI_url(inServer))
iics.cachedRemoteAddr = str
}
//此时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 utils.CanLogErr() {
log.Println("failed in inServer tls handshake ", inServer.AddrStr(), 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 {
case "grpc":
//grpc不太一样, 它是多路复用的
// 每一条建立好的 grpc 可以随时产生对新目标的请求,
// 我们直接循环监听然后分别用 新goroutine发向 handshakeInserver_and_passToOutClient
if utils.CanLogDebug() {
log.Println("start upgrade grpc")
}
grpcs := inServer.GetGRPC_Server() //这个grpc server是在配置阶段初始化好的.
grpcs.StartHandle(wrappedConn)
//start之后客户端就会利用同一条tcp链接 来发送多个 请求,自此就不能直接用原来的链接了;
// 新的子请求被 grpc包 抽象成了 抽象的 conn
//遇到chan被关闭的情况后就会自动关闭底层连接并退出整个函数。
for {
newGConn, ok := <-grpcs.NewConnChan
if !ok {
if utils.CanLogWarn() {
log.Println("grpc getNewSubConn not ok")
}
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 utils.CanLogErr() {
log.Println("ws: got not http request ", inServer.AddrStr())
}
} else {
if utils.CanLogErr() {
log.Println("ws: handshake read error ", 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 utils.CanLogDebug() {
log.Println("ws path not match", rp.Method, rp.Path, "should be:", wss.Thepath)
}
iics.shouldFallback = true
goto startPass
}
//此时path和method都已经匹配了, 如果还不能通过那就说明后面的header等数据不满足ws的upgrade请求格式, 肯定是非法数据了,也不用再回落
wsConn, err := wss.Handshake(rp.WholeRequestBuf, wrappedConn)
if err != nil {
if utils.CanLogErr() {
log.Println("failed in inServer websocket handshake ", inServer.AddrStr(), 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.ReadWriter
var targetAddr *netLayer.Addr
var err error
if iics.shouldFallback {
goto checkFallback
}
wlc, targetAddr, err = inServer.Handshake(wrappedConn)
if err == nil {
//log.Println("inServer handshake passed")
//无错误时直接跳过回落, 直接执行下一个步骤
goto afterLocalServerHandshake
}
////////////////////////////// 回落阶段 /////////////////////////////////////
//下面代码查看是否支持fallback; wlc先设为nil, 当所有fallback都不满足时,可以判断nil然后关闭连接
wlc = nil
if utils.CanLogWarn() {
log.Println("failed in inServer proxy handshake from", inServer.AddrStr(), 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字节的。
log.Fatal("No FirstBuffer")
}
}
checkFallback:
//先检查 mainFallback如果mainFallback中各项都不满足 或者根本没有 mainFallback 再检查 defaultFallback
if mainFallback != nil {
if utils.CanLogDebug() {
log.Println("checkFallback")
}
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则也是正常的, 不设置肯定就是空值
// TODO: 配置中加一个 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 utils.CanLogDebug() {
log.Println("checkFallback ,matched fallback:", fbAddr)
}
if fbAddr != nil {
targetAddr = fbAddr
wlc = wrappedConn
goto afterLocalServerHandshake
}
}
//默认回落, 每个listen配置 都可 有一个自己独享的默认回落
if defaultFallbackAddr := inServer.GetFallback(); defaultFallbackAddr != nil {
targetAddr = defaultFallbackAddr
wlc = wrappedConn
}
afterLocalServerHandshake:
if wlc == nil {
//无wlc证明 inServer 握手失败,且 没有任何回落可用, 直接return
if utils.CanLogDebug() {
log.Println("invalid request and no matched fallback, hung up.")
}
wrappedConn.Close()
return
}
////////////////////////////// 分流阶段 /////////////////////////////////////
var client proxy.Client = iics.defaultClient
//尝试分流, 获取到真正要发向 的 outClient
if routePolicy != nil && !inServer.CantRoute() {
desc := &netLayer.TargetDescription{
Addr: targetAddr,
Tag: inServer.GetTag(),
}
if utils.CanLogDebug() {
log.Println("try routing", desc)
}
outtag := routePolicy.GetOutTag(desc)
if outtag == "direct" {
client = directClient
iics.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))
}
}
}
}
////////////////////////////// 特殊处理阶段 /////////////////////////////////////
// 下面几段用于处理 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.Println("loading TLS SniffConn", 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.shouldCloseBaseConnAfterCopyComplete = true
}
case "socks5":
// UDP Associate
// 因为socks5的 UDP Associate 办法是较为特殊的不使用现有tcp而是新建立udp所以此时该tcp连接已经没用了
// 另外,此时 targetAddr.IsUDP 只是用于告知此链接是udp Associate并不包含实际地址信息
//但是根据socks5标准这个tcp链接同样是 keep alive的否则客户端就会认为服务端挂掉了.
default:
iics.shouldCloseBaseConnAfterCopyComplete = true
}
} else {
//lazy_encrypt情况比较特殊基础连接何时被关闭会在tlslazy相关代码中处理。
// 如果不是lazy的情况的话转发结束后要自动关闭
if !iics.isTlsLazyServerEnd {
//实测 grpc.Conn 被调用了Close 也不会实际关闭连接而是会卡住阻塞直到底层tcp连接被关闭后才会返回
// 但是我们还是 直接避免这种情况
if !inServer.IsMux() {
iics.shouldCloseBaseConnAfterCopyComplete = true
}
}
}
// 下面一段代码 单独处理 udp承载数据的特殊转发。
//
// 这里只处理 vless v1 的CRUMFURS 转发到direct的情况 以及 socks5 的udp associate 转发到vless 的情况;
// 如果条件不符合则会跳过而进入下一阶段
if targetAddr.IsUDP() {
switch inServer.Name() {
case "vless":
if client.Name() == "direct" {
uc := wlc.(*vless.UserConn)
if uc.GetProtocolVersion() < 1 {
break
}
// 根据 vless_v1的讨论vless_v1 的udp转发的 通信方式 也是与tcp连接类似的分离信道方式
// 上面已经把 CRUMFURS 的情况过滤掉了所以现在这里就是普通的udp请求
//
// 因为direct使用 proxy.RelayUDP_to_Direct 函数 直接实现了fullcone
// 那么我们只需要传入一个 UDP_Extractor 即可
//unknownRemoteAddrMsgWriter 在 vless v1中的实现就是 theCRUMFURS vless v0就是mux
id := uc.GetIdentityStr()
vlessServer := inServer.(*vless.Server)
theCRUMFURS := vlessServer.Get_CRUMFURS(id)
var unknownRemoteAddrMsgWriter netLayer.UDPResponseWriter
unknownRemoteAddrMsgWriter = theCRUMFURS
uniExtractor := netLayer.NewUniUDP_Extractor(targetAddr.ToUDPAddr(), wlc, unknownRemoteAddrMsgWriter)
netLayer.RelayUDP_to_Direct(uniExtractor) //阻塞
return
}
case "socks5":
// 此时socks5包已经帮我们dial好了一个udp连接即wlc但是还未读取到客户端想要访问的东西
udpConn := wlc.(*socks5.UDPConn)
dialFunc := func(targetAddr *netLayer.Addr) (io.ReadWriter, error) {
return dialClient(incomingInserverConnState{}, targetAddr, client, false, nil, true)
}
// 将 outClient 视为 UDP_Putter 就可以转发udp信息了
//direct 和 vless 的Client 都实现了 UDP_Putter.
// direct 通过 UDP_Pipe和 RelayUDP_to_Direct函数 实现了 UDP_Putter
// vless 的client 实现了 UDP_Putter, 新连接的Handshake过程会在 dialFunc 被调用 时发生
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, dialFunc)
} else {
if utils.CanLogErr() {
log.Println("socks5 server -> client for udp, but client didn't implement netLayer.UDP_Putter", client.Name())
}
}
return
}
}
////////////////////////////// 拨号阶段 /////////////////////////////////////
//log.Println("will dial", iics.shouldCloseBaseConnAfterCopyComplete)
dialClient(iics, targetAddr, client, isTlsLazy_clientEnd, 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.ReadWriter, noCopy bool) (io.ReadWriter, error) {
if iics.shouldCloseBaseConnAfterCopyComplete && !noCopy {
/*
log.Println("iics.wrappedConn will close after", reflect.TypeOf(iics.wrappedConn))
defer func() {
log.Println("iics.wrappedConn called")
iics.wrappedConn.Close()
log.Println("iics.wrappedConn closed")
}()
*/
defer iics.wrappedConn.Close()
}
var err error
//先确认拨号地址
//direct的话自己是没有目的地址的直接使用 请求的地址
// 而其它代理的话, realTargetAddr会被设成实际配置的代理的地址
var realTargetAddr *netLayer.Addr = targetAddr
if uniqueTestDomain != "" && uniqueTestDomain != targetAddr.Name {
if utils.CanLogDebug() {
log.Println("request isn't the appointed domain", targetAddr, uniqueTestDomain)
}
return nil, utils.NumErr{N: 1, Prefix: "dialClient err, "}
}
if utils.CanLogInfo() {
log.Println(iics.cachedRemoteAddr, " request ", targetAddr.UrlString(), "through", proxy.GetVSI_url(client))
}
if client.AddrStr() != "" {
//log.Println("will dial", client.AddrStr())
realTargetAddr, err = netLayer.NewAddr(client.AddrStr())
if err != nil {
log.Fatal("convert addr err:", err)
}
realTargetAddr.Network = client.Network()
}
var clientEndRemoteClientTlsRawReadRecorder *tlsLayer.Recorder
var clientConn net.Conn
var grpcClientConn grpc.ClientConn
//如果是单路的, 则我们在此dial, 如果是多路复用, 则不行, 因为要复用同一个连接
// Instead, 我们要试图从grpc中取出已经拨号好了的 grpc链接
if client.IsMux() {
if client.AdvancedLayer() == "grpc" {
grpcClientConn = grpc.GetEstablishedConnFor(realTargetAddr)
if grpcClientConn != nil {
//如果有已经建立好的连接,则跳过拨号阶段
goto advLayerStep
}
}
}
clientConn, err = realTargetAddr.Dial()
if err != nil {
if utils.CanLogErr() {
log.Println("failed in dial", realTargetAddr.String(), ", Reason: ", err)
}
return nil, utils.NumErr{N: 2, Prefix: "dialClient err, "}
}
//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 nil, utils.NumErr{N: 3, Prefix: "dialClient err, "}
} 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 nil, utils.NumErr{N: 4, Prefix: "dialClient err, "}
}
clientConn = tlsConn
}
////////////////////////////// 高级层握手阶段 /////////////////////////////////////
advLayerStep:
if adv := client.AdvancedLayer(); adv != "" {
switch adv {
case "grpc":
if grpcClientConn == nil {
grpcClientConn, err = grpc.ClientHandshake(clientConn, realTargetAddr)
if err != nil {
if utils.CanLogErr() {
log.Println("grpc.ClientHandshake failed,", err)
}
if iics.baseLocalConn != nil {
iics.baseLocalConn.Close()
}
return nil, utils.NumErr{N: 5, Prefix: "dialClient err, "}
}
}
clientConn, err = grpc.DialNewSubConn(client.Path(), grpcClientConn, realTargetAddr)
if err != nil {
if utils.CanLogErr() {
log.Println("grpc.DialNewSubConn failed,", 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 nil, utils.NumErr{N: 6, Prefix: "dialClient err, "}
}
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 utils.CanLogErr() {
log.Println("err when reading ws early data", e)
}
return nil, utils.NumErr{N: 7, Prefix: "dialClient err, "}
}
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)
}
//wc, err := wsClient.Handshake(clientConn, ed)
if err != nil {
if utils.CanLogErr() {
log.Println("failed in handshake ws to", targetAddr.String(), ", Reason: ", err)
}
return nil, utils.NumErr{N: 8, Prefix: "dialClient err, "}
}
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 nil, utils.NumErr{N: 9, Prefix: "dialClient err, "}
}
//log.Println("all handshake finished")
////////////////////////////// 实际转发阶段 /////////////////////////////////////
if noCopy {
return wrc, nil
}
if !iics.routedToDirect && tls_lazy_encrypt {
// 我们加了回落之后,就无法确定 “未使用tls的outClient 一定是在服务端” 了
if isTlsLazy_clientEnd {
if client.IsUseTLS() {
//必须是 UserClient
if userClient := client.(proxy.UserClient); userClient != nil {
tryTlsLazyRawCopy(false, userClient, nil, nil, wrc, wlc, iics.baseLocalConn, true, clientEndRemoteClientTlsRawReadRecorder)
return nil, utils.NumErr{N: 11, Prefix: "dialClient err, "}
}
}
} else if iics.isTlsLazyServerEnd {
// 最新代码已经确认使用uuid 作为 “特殊指令”所以要求Server必须是一个 proxy.UserServer
// 否则将无法开启splice功能。这是为了防止0-rtt 探测;
if userServer, ok := iics.inServer.(proxy.UserServer); ok {
tryTlsLazyRawCopy(false, nil, userServer, nil, wrc, wlc, iics.baseLocalConn, false, iics.inServerTlsRawReadRecorder)
return nil, utils.NumErr{N: 12, Prefix: "dialClient err, "}
}
}
}
if iics.theFallbackFirstBuffer != nil {
//这里注意,因为是把 tls解密了之后的数据发送到目标地址所以这种方式只支持转发到本机纯http服务器
wrc.Write(iics.theFallbackFirstBuffer.Bytes())
utils.PutBytes(iics.theFallbackFirstBuffer.Bytes()) //这个Buf不是从utils.GetBuf创建的而是从一个 GetBytes的[]byte 包装 的所以我们要PutBytes而不是PutBuf
}
if utils.CanLogDebug() {
if netLayer.UseReadv {
go func() {
n, e := netLayer.TryCopy(wrc, wlc)
log.Println("本地->远程 转发结束", realTargetAddr.String(), n, e)
}()
n, e := netLayer.TryCopy(wlc, wrc)
log.Println("远程->本地 转发结束", realTargetAddr.String(), n, e)
} else {
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 {
netLayer.Relay(wlc, wrc)
}
return wrc, nil
}