mirror of
https://github.com/e1732a364fed/v2ray_simple.git
synced 2025-10-07 09:41:07 +08:00
1109 lines
32 KiB
Go
1109 lines
32 KiB
Go
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
|
||
}
|