package v2ray_simple import ( "bytes" "crypto/tls" "errors" "io" "net" "net/url" "os" "sync" "sync/atomic" "time" "go.uber.org/zap" "golang.org/x/net/http2" "github.com/e1732a364fed/v2ray_simple/advLayer" "github.com/e1732a364fed/v2ray_simple/httpLayer" "github.com/e1732a364fed/v2ray_simple/netLayer" "github.com/e1732a364fed/v2ray_simple/proxy" "github.com/e1732a364fed/v2ray_simple/tlsLayer" "github.com/e1732a364fed/v2ray_simple/utils" ) // statistics type GlobalInfo struct { ActiveConnectionCount int32 AllDownloadBytesSinceStart uint64 AllUploadBytesSinceStart uint64 } var ( //一个默认的 非 fullcone 的 direct Client DirectClient, _ = proxy.ClientFromURL(proxy.DirectURL) ) // 用于回落到h2c var ( fallback_h2c_transport = &http2.Transport{ DialTLS: func(n, a string, cfg *tls.Config) (net.Conn, error) { // if ce := utils.CanLogDebug("fallback_h2c_transport, got dial"); ce != nil { // ce.Write(zap.String("a", n)) // } return net.Dial(n, a) }, AllowHTTP: true, } fb_h2c_PROXYprotocolAddrMap = make(map[string]*http2.Transport) fb_h2c_PROXYprotocolAddrMap_mutex sync.RWMutex ) /* ListenSer 函数 是本包 最重要的函数。可以 直接使用 本函数 来手动开启新的 自定义的 转发流程。 监听 inServer, 然后试图转发到一个 proxy.Client。如果env没给出,则会转发到 defaultOutClient。 若 env 不为 nil, 则会 进行分流或回落。具有env的情况下,可能会转发到 非 defaultOutClient 的其他 proxy.Client. inServer and defaultOutClient must not be nil. Use cases: refer to tcp_test.go, udp_test.go or cmd/verysimple. non-blocking. closer used to stop listening. It means listening failed if closer == nil, */ func ListenSer(inServer proxy.Server, defaultOutClient proxy.Client, env *proxy.RoutingEnv, gi *GlobalInfo) (closer io.Closer) { var extraCloser io.Closer var is bool var tcp, udp int //tproxy,tun 和 shadowsocks(udp) 都用到了 SelfListen if is, tcp, udp = inServer.SelfListen(); is { var tcpFunc func(netLayer.TCPRequestInfo) var udpFunc func(netLayer.UDPRequestInfo) if ce := utils.CanLogInfo("Listening"); ce != nil { ce.Write( zap.String("tag", inServer.GetTag()), zap.String("protocol", proxy.GetFullName(inServer)), zap.String("listen_addr", inServer.AddrStr()), zap.String("defaultClient", proxy.GetFullName(defaultOutClient)), zap.String("dial_addr", defaultOutClient.AddrStr()), ) } if tcp == 1 { tcpFunc = func(tcpInfo netLayer.TCPRequestInfo) { passToOutClient(incomingInserverConnState{ inTag: inServer.GetTag(), useSniffing: inServer.Sniffing(), wrappedConn: tcpInfo.Conn, defaultClient: defaultOutClient, routingEnv: env, GlobalInfo: gi, }, false, tcpInfo.Conn, nil, tcpInfo.Target) } } if udp == 1 { udpFunc = func(udpInfo netLayer.UDPRequestInfo) { passToOutClient(incomingInserverConnState{ inTag: inServer.GetTag(), useSniffing: inServer.Sniffing(), defaultClient: defaultOutClient, routingEnv: env, GlobalInfo: gi, }, false, nil, udpInfo.MsgConn, udpInfo.Target) } } closer = inServer.(proxy.ListenerServer).StartListen(tcpFunc, udpFunc) //可以直接return的值: 1,1 1,-1, -1,1 ; //还需继续监听的值: 1,0 0,1 if tcp+udp != 1 { return } else { extraCloser = closer } } var handleHere bool advs := inServer.GetAdvServer() if advs != nil { handleHere = advs.IsSuper() && advs.IsMux() } if handleHere { //如果像quic一样自行处理传输层至tls层之间的部分,则我们跳过 handleNewIncomeConnection 函数 // 拿到连接后直接调用 handshakeInserver_and_passToOutClient superSer := advs.(advLayer.SuperMuxServer) closer = superSer.StartListen(func(newConn net.Conn) { iics := incomingInserverConnState{ wrappedConn: newConn, inServer: inServer, defaultClient: defaultOutClient, routingEnv: env, GlobalInfo: gi, } iics.genID() handshakeInserver_and_passToOutClient(iics) }) if closer == nil { utils.Error("Failed in SuperMuxServer StartListen ") return } if ce := utils.CanLogInfo("Listening Super AdvLayer"); ce != nil { ce.Write( zap.String("protocol", proxy.GetFullName(inServer)), zap.String("addr", inServer.AddrStr()), ) } return } var err error network := inServer.Network() if network == netLayer.DualNetworkName && is { //设置这里实际监听的传输层协议 if is != (tcp == 1 && udp == 1) { if tcp == 1 { network = "udp" } else { network = "tcp" } } } closer, err = netLayer.ListenAndAccept( network, inServer.AddrStr(), inServer.GetSockopt(), inServer.GetXver(), func(conn net.Conn) { handleNewIncomeConnection(inServer, defaultOutClient, conn, env, gi) }, ) if err == nil { if ce := utils.CanLogInfo("Listening"); ce != nil { ce.Write( zap.String("tag", inServer.GetTag()), zap.String("protocol", proxy.GetFullName(inServer)), zap.String("listen_addr", inServer.AddrStr()), zap.String("defaultClient", proxy.GetFullName(defaultOutClient)), zap.String("dial_addr", defaultOutClient.AddrStr()), ) } if extraCloser != nil { closer = &utils.MultiCloser{Closers: []io.Closer{closer, extraCloser}} } } else { if err != nil { if ce := utils.CanLogErr("ListenSer failed"); ce != nil { ce.Write( zap.String("addr", inServer.AddrStr()), zap.Error(err), ) } } if extraCloser != nil { closer = extraCloser } } return } // handleNewIncomeConnection 会处理 网络层至高级层的数据, // 然后将代理层的处理发往 handshakeInserver_and_passToOutClient 函数。 // // 在 ListenSer 中被调用。 func handleNewIncomeConnection(inServer proxy.Server, defaultClientForThis proxy.Client, thisLocalConnectionInstance net.Conn, env *proxy.RoutingEnv, gi *GlobalInfo) { iics := incomingInserverConnState{ baseLocalConn: thisLocalConnectionInstance, inServer: inServer, defaultClient: defaultClientForThis, routingEnv: env, isTlsLazyServerEnd: inServer.IsLazyTls() && CanLazyEncrypt(inServer), GlobalInfo: gi, } iics.genID() wrappedConn := thisLocalConnectionInstance if ce := iics.CanLogInfo("New Accepted Conn"); ce != nil { var addrstr string if inServer.Network() == "unix" { addrstr = inServer.AddrStr() } else { addrstr = wrappedConn.RemoteAddr().String() } ce.Write( zap.String("from", addrstr), zap.String("handler", proxy.GetVSI_url(inServer, "")), ) iics.cachedRemoteAddr = addrstr } ////////////////////////////// tls层 ///////////////////////////////////// //此时,baseLocalConn里面 正常情况下, 服务端看到的是 客户端的golang的tls 拨号发出的 tls数据 // 客户端看到的是 socks5的数据, 我们首先就是要看看socks5里的数据是不是tls,而socks5自然 IsUseTLS 是false // 如果是服务端的话,那就是 inServer.IsUseTLS == true, 此时,我们正常握手,然后我们需要判断的是它承载的数据 // 每次tls试图从 原始连接 读取内容时,都会附带把原始数据写入到 这个 Recorder中 if inServer.IsUseTLS() { if iics.isTlsLazyServerEnd { tlsRecorder := tlsLayer.NewRecorder() iics.inServerTlsRawReadRecorder = tlsRecorder tlsRecorder.StopRecord() //先不记录,因为一开始是我们自己的tls握手包,没有意义 teeConn := tlsLayer.NewTeeConn(wrappedConn, tlsRecorder) wrappedConn = teeConn } tConf := inServer.GetBase().TlsConf if tConf.RejectUnknownSni { bs := utils.GetPacket() n, e := wrappedConn.Read(bs) if e != nil { if ce := iics.CanLogWarn("tls preread failed"); ce != nil { ce.Write(zap.Error(e)) } utils.PutPacket(bs) wrappedConn.Close() return } firstpayload := bs[:n] tlsSniff := new(tlsLayer.ComSniff) tlsSniff.CommonDetect(firstpayload, true, true) if tlsSniff.SniffedServerName == "" { if ce := iics.CanLogWarn("tls rejectUnknownSni, client didn't provide sni"); ce != nil { ce.Write() } utils.PutPacket(bs) wrappedConn.Close() return } //shadowTls自己没有防御功能,完全靠我们包外过滤 if tConf.IsShadowTls() { if tlsSniff.SniffedServerName != tConf.Host { if ce := iics.CanLogWarn("tls rejectUnknownSni, client sni not match"); ce != nil { ce.Write(zap.String("sni", tlsSniff.SniffedServerName), zap.String("shouldBe", tConf.Host)) } utils.PutPacket(bs) wrappedConn.Close() return } } wrappedConn = &netLayer.ReadWrapper{ Conn: wrappedConn, OptionalReader: io.MultiReader(bytes.NewBuffer(firstpayload), wrappedConn), RemainFirstBufLen: n, } } tlsConn, err := inServer.GetTLS_Server().Handshake(wrappedConn) if err != nil { if ce := iics.CanLogErr("Failed in TLS handshake"); ce != nil { ce.Write( zap.String("inServer", inServer.AddrStr()), zap.Error(err), ) } //see #241 if !errors.Is(err, netLayer.ErrDoNotClose) { wrappedConn.Close() } return } if iics.isTlsLazyServerEnd { //此时已经握手完毕,可以记录了 iics.inServerTlsRawReadRecorder.StartRecord() } realtlsConn, ok := tlsConn.(tlsLayer.Conn) if ok { iics.inServerTlsConn = realtlsConn } wrappedConn = tlsConn } adv := inServer.AdvancedLayer() advSer := inServer.GetAdvServer() ////////////////////////////// header 层 ///////////////////////////////////// if header := inServer.HasHeader(); header != nil { //高级层 均可以自行处理header, 不需要额外http包装 if !(advSer != nil && advSer.CanHandleHeaders()) { var ho *heapObj = iics.heapObj if iics.heapObj == nil { ho = &heapObj{} iics.heapObj = ho } wrappedConn = &httpLayer.HeaderConn{ Conn: wrappedConn, H: header, IsServerEnd: true, ReadOkCallback: func(b *bytes.Buffer) { ho.headerPass = true ho.wholeBuffer = b }, } } } ////////////////////////////// 高级层 ///////////////////////////////////// if adv != "" { //这里分 super, mux 和 default 三种情况,实际上对应了 quic,grpc 和ws switch { case advSer.IsSuper(): //Super 根本没调用 handleNewIncomeConnection (本函数),所以不在此处理 // 详见 ListenSer case advSer.IsMux(): //grpc muxSer := advSer.(advLayer.MuxServer) muxSer.StartHandle(wrappedConn, func(newGConn net.Conn) { iics.wrappedConn = newGConn handshakeInserver_and_passToOutClient(iics) }, func(fallbackMeta httpLayer.FallbackMeta) { newiics := iics newiics.fallbackRequestPath = fallbackMeta.Path newiics.fallbackFirstBuffer = fallbackMeta.H1RequestBuf newiics.wrappedConn = fallbackMeta.Conn newiics.isFallbackH2 = fallbackMeta.IsH2 newiics.fallbackH2Request = fallbackMeta.H2Request newiics.fallbackRW = fallbackMeta.H2RW passToOutClient(newiics, true, nil, nil, netLayer.Addr{}) }) return default: //ws singleSer := advSer.(advLayer.SingleServer) wsConn, err := singleSer.Handshake(wrappedConn) if err != nil { if errors.Is(err, httpLayer.ErrShouldFallback) { meta := wsConn.(httpLayer.FallbackMeta) iics.fallbackRequestPath = meta.Path iics.fallbackFirstBuffer = meta.H1RequestBuf iics.wrappedConn = meta.Conn if meta.XFF != nil { iics.cachedRemoteAddr = meta.XFF.String() } if ce := iics.CanLogDebug("Single AdvLayer Check failed, will fallback."); ce != nil { ce.Write( zap.String("handler", inServer.AddrStr()), zap.String("advLayer", adv), zap.String("Reason", meta.Reason), zap.String("validPath", advSer.GetPath()), zap.String("gotMethod", meta.Method), zap.String("gotPath", meta.Path), zap.String("from", iics.cachedRemoteAddr), ) } passToOutClient(iics, true, nil, nil, netLayer.Addr{}) return } else { if ce := iics.CanLogErr("InServer Single AdvLayer handshake failed"); ce != nil { ce.Write( zap.String("handler", inServer.AddrStr()), zap.String("advLayer", adv), zap.Error(err), ) } wrappedConn.Close() return } } else { wrappedConn = wsConn } } // switch adv } //if adv !="" iics.wrappedConn = wrappedConn handshakeInserver_and_passToOutClient(iics) } // 被 handshakeInserver_and_passToOutClient 调用 func handshakeInserver(iics *incomingInserverConnState) (wlc net.Conn, udp_wlc netLayer.MsgConn, targetAddr netLayer.Addr, err error) { inServer := iics.inServer if inServer == nil { err = utils.ErrInErr{ErrDesc: "Failed handshakeInserver, nil inServer"} return } wlc, udp_wlc, targetAddr, err = inServer.Handshake(iics.wrappedConn) if err != nil { if ce := iics.CanLogWarn("Failed handshakeInserver"); ce != nil { ce.Write( zap.String("handler", iics.inServer.AddrStr()), zap.String("client RemoteAddr", iics.getRealRAddr()), zap.Error(err), ) } return } if udp_wlc != nil && inServer.Name() == "socks5" { // socks5的 udp associate返回的是 clientFutureAddr, 而不是实际客户的第一个请求. //所以我们要读一次才能进行下一步。 firstSocks5RequestData, firstSocks5RequestAddr, err2 := udp_wlc.ReadMsg() if err2 != nil { if ce := iics.CanLogWarn("Failed in socks5 read"); ce != nil { ce.Write( zap.String("handler", inServer.AddrStr()), zap.Error(err2), ) } err = err2 return } iics.fallbackFirstBuffer = bytes.NewBuffer(firstSocks5RequestData) targetAddr = firstSocks5RequestAddr } ////////////////////////////// 内层mux阶段 ///////////////////////////////////// if muxInt, innerProxyName := inServer.HasInnerMux(); muxInt > 0 { mm, ok := wlc.(proxy.MuxMarker) if !ok { return } if !mm.IsMux() { return } innerSerConf := proxy.ListenConf{ CommonConf: proxy.CommonConf{ Protocol: innerProxyName, }, } innerSer, err2 := proxy.NewServer(&innerSerConf) if err2 != nil { if ce := iics.CanLogDebug("Failed mux inner proxy server creation"); ce != nil { ce.Write(zap.Error(err)) } err = err2 return } session := inServer.GetServerInnerMuxSession(mm) if session == nil { err = utils.ErrFailed return } //内层mux要对每一个子连接单独进行 子代理协议握手 以及 outClient的拨号。 go func() { for { if ce := iics.CanLogDebug("Try inServer accept smux stream "); ce != nil { ce.Write() } stream, err := session.AcceptStream() if err != nil { if ce := iics.CanLogWarn("Failed try mux inServer accept stream "); ce != nil { ce.Write(zap.Error(err)) } session.Close() return } if ce := iics.CanLogDebug("inServer got inner mux stream"); ce != nil { ce.Write(zap.String("innerProxyName", innerProxyName)) } go func() { wlc1, udp_wlc1, targetAddr1, err1 := innerSer.Handshake(stream) if err1 != nil { if ce := iics.CanLogDebug("Failed inServer mux inner proxy handshake"); ce != nil { ce.Write(zap.Error(err1)) } newiics := *iics if !newiics.extractFirstBufFromErr(err1) { return } passToOutClient(newiics, true, wlc1, udp_wlc1, targetAddr1) } else { if ce := iics.CanLogDebug("OK in inServer mux stream handshake "); ce != nil { ce.Write(zap.String("targetAddr", targetAddr1.String())) } newiics := *iics newiics.isInner = true //试图将user赋值给simplesocks, 使其在内部的对user的分流依旧可用 if wlc != nil { if u, ok := wlc.(utils.User); ok { if wlc1 != nil { if us, ok := wlc1.(utils.UserAssigner); ok { us.SetUser(u) } } else if udp_wlc1 != nil { if us, ok := udp_wlc1.(utils.UserAssigner); ok { us.SetUser(u) } } } } else if udp_wlc != nil { if u, ok := udp_wlc.(utils.User); ok { if udp_wlc1 != nil { if us, ok := udp_wlc1.(utils.UserAssigner); ok { us.SetUser(u) } } } } passToOutClient(newiics, false, wlc1, udp_wlc1, targetAddr1) } }() } }() err = utils.ErrHandled return } return } // 本函数 处理inServer的代理层数据,并在试图处理 分流和回落后,调用 passToOutClient 将流量导向目标。 // // iics 不使用指针, 因为iics不能公用,因为 在多路复用时 iics.wrappedConn 是会变化的。 // // 被 handleNewIncomeConnection 和 ListenSer 调用。 func handshakeInserver_and_passToOutClient(iics incomingInserverConnState) { wlc, udp_wlc, targetAddr, err := handshakeInserver(&iics) switch err { case nil: passToOutClient(iics, false, wlc, udp_wlc, targetAddr) case utils.ErrHandled: return default: //回落 if !iics.extractFirstBufFromErr(err) { return } if iics.isTlsLazyServerEnd { iics.isTlsLazyServerEnd = false /* 我们这里取巧,认为只要进行了回落,那么客户端就一定不是vs客户端,进而不再应用lazy 关联 issue #158 开启了lazy后,如果进行回落了,那么是要先从iics.firstbuffer 来读的,但是目前lazy函数根本不接收 iics. 而回落一般用于正常客户端的浏览网页,一般的客户端就是浏览器,是不用lazy的,所以可以如此取巧 */ } passToOutClient(iics, true, nil, nil, netLayer.Addr{}) } } // 被 handshakeInserver_and_passToOutClient 和 handshakeInserver 的innerMux部分 以及 tproxy 调用。 iics.inServer可能为nil。 // 本函数 可能是 本文件中 最长的 函数。分别处理 回落,firstpayload,sniff,dns解析,分流,以及lazy,最终转发到 某个 outClient。 // // 最终会调用 dialClient_andRelay. 若isfallback为true,传入的 wlc 和 udp_wlc 必须为nil,targetAddr必须为空值。 func passToOutClient(iics incomingInserverConnState, isfallback bool, wlc net.Conn, udp_wlc netLayer.MsgConn, targetAddr netLayer.Addr) { //这里的 iics.inServer 是可能为nil的,所以一定要判断一下,否则会空指针闪退 ////////////////////////////// 回落阶段 ///////////////////////////////////// if isfallback { fallbackTargetAddr, fbResult := iics.checkfallback() if fbResult >= 0 { targetAddr = fallbackTargetAddr wlc = iics.wrappedConn if iics.isFallbackH2 { //h2 的fallback 非常特殊,要单独处理. 下面进行h2c拨号并向真实h2c服务器发起请求。 rq := iics.fallbackH2Request if targetAddr.Network != "unix" { rq.Host = targetAddr.Name u, _ := url.Parse("https://" + targetAddr.String() + iics.fallbackRequestPath) rq.URL = u } else { //rq.Host = "" //如果host设成了 unix的地址,依然无法被nginx正常响应 rq.URL, _ = url.Parse("http://127.0.0.1" + iics.fallbackRequestPath) } var transport *http2.Transport newUdsTransport := func(doAfterDial func(conn net.Conn)) *http2.Transport { return &http2.Transport{ DialTLS: func(n, a string, cfg *tls.Config) (net.Conn, error) { //实测如果是uds,这里的a会为 :443, 丢失了uds监听文件信息 c, e := net.Dial("unix", targetAddr.String()) if doAfterDial != nil { doAfterDial(c) } return c, e }, AllowHTTP: true, } } if fbResult == 0 { transport = fallback_h2c_transport if targetAddr.Network == "unix" { transport = newUdsTransport(nil) } } else if fbResult > 0 { var wlcRaddrStr string if wlcraddr := wlc.RemoteAddr(); wlcraddr != nil { wlcRaddrStr = wlcraddr.String() } fb_h2c_PROXYprotocolAddrMap_mutex.RLock() transport = fb_h2c_PROXYprotocolAddrMap[wlcRaddrStr] fb_h2c_PROXYprotocolAddrMap_mutex.RUnlock() //因为一个客户端可能向我们服务器发起多个h2子连接,共用同一个wlc,我们如果能 // 共用 这种情况的transport,就可以节约到达实际服务器的tcp链接数量,而且 // 无缝粘贴了两个h2连接. // 因为 PROXYprotocol 头部对于每个wlc都是不同的, 所以才用到多个transport和map这种办法。 if transport == nil { if targetAddr.Network == "unix" { transport = newUdsTransport(func(conn net.Conn) { netLayer.WritePROXYprotocol(fbResult, wlc, conn) }) } else { transport = &http2.Transport{ DialTLS: func(n, a string, cfg *tls.Config) (net.Conn, error) { conn, e := net.Dial(n, a) netLayer.WritePROXYprotocol(fbResult, wlc, conn) return conn, e }, AllowHTTP: true, } } if wlcRaddrStr != "" { fb_h2c_PROXYprotocolAddrMap_mutex.Lock() fb_h2c_PROXYprotocolAddrMap[wlcRaddrStr] = transport fb_h2c_PROXYprotocolAddrMap_mutex.Unlock() } } } rsp, err := transport.RoundTrip(rq) defer wlc.Close() if err != nil { if ce := iics.CanLogErr("Failed in fallback h2 RoundTrip"); ce != nil { ce.Write( zap.Error(err), zap.String("real addr", targetAddr.UrlString()), ) } return } for k, v := range rsp.Header { iics.fallbackRW.Header().Set(k, v[0]) } iics.fallbackRW.WriteHeader(rsp.StatusCode) netLayer.TryCopy(wlc, rsp.Body, iics.id) return } } iics.fallbackXver = fbResult } else { iics.fallbackXver = -1 } // inServer 握手失败,且 没有任何回落可用时的情况, 在这里我们直接退出。 if wlc == nil && udp_wlc == nil { if ce := iics.CanLogWarn("Invalid request and no matched fallback, hung up"); ce != nil { ce.Write(zap.String("client RemoteAddr", iics.cachedRemoteAddr)) } if wc := iics.wrappedConn; wc != nil { //应该返回一个http400错误,这样更逼真一些 // 不过有一些高级层不属于 http1.1,如grpc和 quic,此时通过 如下方式处理 //本默认响应并不智能,如果你要智能、真实的响应,还是要自行配置好 回落。 if rejectConn, ok := wc.(netLayer.RejectConn); ok { if rejectConn.RejectBehaviorDefined() { rejectConn.Reject() } } else { wc.SetWriteDeadline(time.Now().Add(time.Second)) wc.Write([]byte(httpLayer.GetNginx400Response())) } wc.Close() } return } ////////////////////////////// 读取 First Payload 阶段 ///////////////////////////////////// //因为无论是 sniffing,还是后面 proxy的握手,抑或是 ws的 earlydata,都要预先获得用户数据,所以要提前读一下 //serverEnd 的 lazy 比较特殊,要自己读 if !iics.isTlsLazyServerEnd { isudp := targetAddr.IsUDP() if iics.fallbackFirstBuffer != nil { iics.firstPayload = iics.fallbackFirstBuffer.Bytes() iics.fallbackFirstBuffer = nil if isudp { iics.udpFirstTarget = targetAddr } } else { if !isudp && wlc != nil { bs := utils.GetMTU() wlc.SetReadDeadline(time.Now().Add(proxy.FirstPayloadTimeout)) n, err := wlc.Read(bs) wlc.SetReadDeadline(time.Time{}) if err != nil { notTimeout := true if errors.Is(err, os.ErrDeadlineExceeded) { notTimeout = false } else if toe, ok := err.(*net.OpError); ok { //在tun中使用gvisor时会遇到这种情况 if toe.Timeout() { notTimeout = false } } if notTimeout { if n <= 0 { if ce := iics.CanLogWarn("Failed in reading first payload, not because of timeout, will hung up"); ce != nil { ce.Write( zap.String("target", targetAddr.String()), zap.Error(err), ) } wlc.Close() return } } else { if ce := iics.CanLogInfo("Read first payload but timeout, will relay without first payload."); ce != nil { ce.Write( zap.String("target", targetAddr.String()), zap.Error(err), ) } } } if n > 0 { iics.firstPayload = bs[:n] } } else if isudp && udp_wlc != nil { udp_wlc.SetReadDeadline(time.Now().Add(proxy.FirstPayloadTimeout)) bs, targetAd, err := udp_wlc.ReadMsg() udp_wlc.SetReadDeadline(time.Time{}) if err != nil { if !errors.Is(err, os.ErrDeadlineExceeded) { if ce := iics.CanLogErr("Failed in reading first udp payload, not because of timeout, will hung up"); ce != nil { ce.Write( zap.String("target", targetAddr.String()), zap.Error(err), ) } udp_wlc.Close() return } else { if ce := iics.CanLogWarn("Read first udp payload but timeout, will relay without first payload."); ce != nil { ce.Write( zap.String("target", targetAddr.String()), zap.Error(err), ) } } } if len(bs) > 0 { iics.firstPayload = bs iics.udpFirstTarget = targetAd } } //if !isudp && wlc != nil { } } //if !iics.isTlsLazyServerEnd { var tlsSniff *tlsLayer.ComSniff inServer := iics.inServer ////////////////////////////// Sniff阶段 ///////////////////////////////////// //tls请求和纯http请求是可以嗅探 host的,嗅探可以帮助我们使用 geosite 精准分流,所以是很有用的 if len(iics.firstPayload) > 0 { inserverMarkedSniffing := false if inServer == nil { inserverMarkedSniffing = iics.useSniffing } else { inserverMarkedSniffing = inServer.Sniffing() } dialIslazy := iics.defaultClient.IsLazyTls() shouldSniff := inserverMarkedSniffing || dialIslazy if shouldSniff { tlsSniff = new(tlsLayer.ComSniff) if !iics.isTlsLazyServerEnd { tlsSniff.Isclient = true } tlsSniff.CommonDetect(iics.firstPayload, true, inserverMarkedSniffing && !(iics.isTlsLazyServerEnd || dialIslazy)) if sni := tlsSniff.SniffedServerName; sni != "" { if ce := iics.CanLogDebug("Sniffed Sni"); ce != nil { ce.Write(zap.String("sni", sni)) } targetAddr.Name = sni } } } ////////////////////////////// DNS解析阶段 ///////////////////////////////////// //dns解析会试图解析域名并将ip放入 targetAddr中 // 因为在direct时,netLayer.Addr 拨号时,会优先选用ip拨号,而且我们下面的分流阶段 如果使用ip的话, // 可以利用geoip文件, 可以做到国别分流. if iics.routingEnv != nil && iics.routingEnv.DnsMachine != nil && (targetAddr.Name != "" && len(targetAddr.IP) == 0) && targetAddr.Network != "unix" { if ce := iics.CanLogDebug("Dns querying"); ce != nil { ce.Write(zap.String("domain", targetAddr.Name)) } ip := iics.routingEnv.DnsMachine.Query(targetAddr.Name) if ip != nil { targetAddr.IP = ip if ce2 := iics.CanLogDebug("Dns result"); ce2 != nil { ce2.Write(zap.String("domain", targetAddr.Name), zap.String("ip", ip.String())) } } } //此时 targetAddr已经完全确定 ////////////////////////////// 分流阶段 ///////////////////////////////////// var client proxy.Client = iics.defaultClient routed := false //尝试分流, 获取到真正要发向 的 outClient if re := iics.routingEnv; re != nil && re.RoutePolicy != nil && !(inServer != nil && inServer.CantRoute()) { desc := &netLayer.TargetDescription{ Addr: targetAddr, } if inServer != nil { desc.InTag = inServer.GetTag() } else { desc.InTag = iics.inTag } if uc, ok := wlc.(utils.User); ok { desc.UserIdentityStr = uc.IdentityStr() } else if uc, ok := udp_wlc.(utils.User); ok { desc.UserIdentityStr = uc.IdentityStr() } if ce := iics.CanLogDebug("Try routing"); ce != nil { ce.Write(zap.Any("source", desc)) } outtag := re.RoutePolicy.CalcuOutTag(desc) if len(re.ClientsTagMap) > 0 { if tagC := re.GetClient(outtag); tagC != nil { client = tagC routed = true if ce := iics.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 && outtag == proxy.DirectName { client = DirectClient iics.routedToDirect = true routed = true if ce := iics.CanLogInfo("Route to direct"); ce != nil { ce.Write( zap.String("target", targetAddr.UrlString()), ) } } } if !routed { if ce := iics.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 = client.IsLazyTls() && CanLazyEncrypt(client) //比如dial是 tls+vless 这种 } // 我们在客户端 lazy_encrypt 探测时,读取socks5 传来的信息,因为这个 就是要发送到 outClient 的信息,所以就不需要等包上vless、tls后再判断了, 直接解包 socks5 对 tls 进行判断 // // 而在服务端探测时,因为 客户端传来的连接 包了 tls,所以要在tls解包后, vless 解包后,再进行判断; // 所以总之都是要在 inServer 判断 wlc; 总之,含义就是,去检索“用户承载数据”的来源 if isTlsLazy_clientEnd || iics.isTlsLazyServerEnd { wlc = tlsLayer.NewSniffConn(iics.baseLocalConn, wlc, isTlsLazy_clientEnd, tls_lazy_secure, tlsSniff) } //这一段代码是去判断是否要在转发结束后自动关闭连接, 主要是socks5 和lazy 的特殊情况 if targetAddr.IsUDP() { if inServer != nil { switch inServer.Name() { case "socks5", "socks5http": // 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 != nil && !(inServer.GetAdvServer() != nil && inServer.GetAdvServer().IsMux()) { iics.shouldCloseInSerBaseConnWhenFinish = true } } } ////////////////////////////// 拨号阶段 ///////////////////////////////////// dialClient_andRelay(iics, targetAddr, client, isTlsLazy_clientEnd, wlc, udp_wlc) } // dialClient 对实际client进行拨号,处理传输层, tls层, 高级层等所有层级后,进行代理层握手。 // result = 0 表示拨号成功, result = -1 表示 拨号失败, result = 1 表示 拨号成功 并 已经自行处理了转发阶段(用于lazy和 innerMux ); -10 标识 因为 client为reject 而关闭了连接。 // 在 dialClient_andRelay 中被调用。在udp为multi channel时也有用到. func dialClient(iics incomingInserverConnState, targetAddr netLayer.Addr, client proxy.Client, wlc net.Conn, udp_wlc netLayer.MsgConn, isTlsLazy_clientEnd bool) ( //return values: wrc io.ReadWriteCloser, udp_wrc netLayer.MsgConn, realTargetAddr netLayer.Addr, clientEndRemoteClientTlsRawReadRecorder *tlsLayer.Recorder, result int) { if client.Name() == proxy.RejectName { if wlc != nil { client.Handshake(wlc, nil, netLayer.Addr{}) } else if udp_wlc != nil { client.Handshake(netLayer.MsgConnNetAdapter{MsgConn: udp_wlc}, nil, netLayer.Addr{}) } result = -10 return } isudp := targetAddr.IsUDP() hasInnerMux := false var innerProxyName string { var muxInt int if muxInt, innerProxyName = client.HasInnerMux(); muxInt == 2 { hasInnerMux = true //先过滤掉 innermux 通道已经建立的情况, 此时我们不必再次外部拨号,而是直接进行内层拨号. client.Lock() if client.InnerMuxEstablished() { client.Unlock() if ce := iics.CanLogInfo("Mux Request"); ce != nil { ce.Write( zap.String("From", iics.cachedRemoteAddr), zap.String("Target", targetAddr.UrlString()), zap.String("through", proxy.GetVSI_url(client, targetAddr.Network)), ) } wrc1, realudp_wrc, result1 := dialInnerProxy(iics, client, wlc, nil, innerProxyName, targetAddr, isudp) if result1 == 0 { if wrc1 != nil { wrc = wrc1 } if realudp_wrc != nil { udp_wrc = realudp_wrc } result = result1 return } else { if ce := iics.CanLogDebug("Failed in client inner mux dialing innerProxy , will redial"); ce != nil { ce.Write() } } } else { //在实测时 发现,可能出现并发问题,比如在加载图多的网页时,很容易碰到 //此时如果是两个连接同时 发出,而且 尚未 建立 innerMux, // 则如果不加锁的话 ,两个连接 会同时 获取到 client.InnerMuxEstablished() 为 false // 这会导致同时试图拨号 innerMux,而这是错误的 //我们只允许有一个 innerMux 连接存在,如果有多个的话,那么最新拨号的innerMux 会覆盖以前的拨号, // 导致 以前的 innerMux 成为了 悬垂连接,而且会导致 相关联的请求卡住。 // 所以使用defer, 来确保同一时间只有一个smux的拨号 defer client.Unlock() } } } var err error //先确认拨号地址 //direct的话自己是没有目的地址的,直接使用 请求的地址 // 而其它代理的话, realTargetAddr会被设成实际配置的代理的地址 realTargetAddr = targetAddr if ce := iics.CanLogInfo("Request"); ce != nil { if iics.fallbackXver >= 0 { if iics.fallbackXver > 0 { ce.Write( zap.String("Fallback from", iics.cachedRemoteAddr), zap.String("Target", targetAddr.UrlString()), zap.String("through", proxy.GetVSI_url(client, targetAddr.Network)), zap.Int("with xver", iics.fallbackXver), ) } else { ce.Write( zap.String("Fallback from", iics.cachedRemoteAddr), zap.String("Target", targetAddr.UrlString()), zap.String("through", proxy.GetVSI_url(client, targetAddr.Network)), ) } } else { ce.Write( zap.String("From", iics.cachedRemoteAddr), zap.String("Target", targetAddr.UrlString()), zap.String("through", proxy.GetVSI_url(client, targetAddr.Network)), ) } } if client.AddrStr() != "" { realTargetAddr, err = netLayer.NewAddr(client.AddrStr()) if err != nil { if ce := iics.CanLogErr("Err at dial client convert addr "); ce != nil { ce.Write(zap.Error(err)) } result = -1 return } realTargetAddr.Network = client.Network() } var clientConn net.Conn //拨号所得到的net.Conn, 在下面代码中 会一层层进行包装 var dialedCommonConn any switch realTargetAddr.Network { case netLayer.DualNetworkName: realTargetAddr.Network = targetAddr.Network } dialhere := !(client.Name() == proxy.DirectName) /* direct的udp是自己拨号的,因为它用到了udp的fullcone 不是的话,也要分情况: 如果是单路的, 则我们在此dial, 如果是多路复用, 则不行, 因为要复用同一个连接 Instead, 我们要试图 取出已经拨号好了的 连接 */ adv := client.AdvancedLayer() advClient := client.GetAdvClient() var muxC advLayer.MuxClient if dialhere { if adv != "" && advClient.IsMux() { muxC = advClient.(advLayer.MuxClient) if advClient.IsSuper() { dialedCommonConn, err = muxC.GetCommonConn(nil) if dialedCommonConn != nil && err == nil { goto advLayerHandshakeStep } else { if ce := iics.CanLogErr("Failed in Super AdvLayer GetCommonConn"); ce != nil { ce.Write( zap.Error(err), ) } result = -1 return } } else { dialedCommonConn, err = muxC.GetCommonConn(nil) if dialedCommonConn != nil && err == nil { goto advLayerHandshakeStep } } } var na net.Addr switch realTargetAddr.Network { case "tcp": na = client.LocalTCPAddr() case "udp": na = client.LocalUDPAddr() } clientConn, err = realTargetAddr.Dial(client.GetSockopt(), na) if err != nil { if err == netLayer.ErrMachineCantConnectToIpv6 { //如果一开始就知道机器没有ipv6地址,那么该错误就不是error等级,而是warning等级 if ce := iics.CanLogWarn("Machine HasNo ipv6 but got ipv6 request"); ce != nil { ce.Write( zap.String("target", realTargetAddr.String()), ) } } else { //虽然拨号失败,但是不能认为我们一定有错误, 因为很可能申请的ip本身就是不可达的, 所以不是error等级而是warn等级 if ce := iics.CanLogWarn("Failed dialing"); ce != nil { ce.Write( zap.String("target", realTargetAddr.String()), zap.Error(err), ) } } result = -1 return } } else { goto shakeStep } if xver := iics.fallbackXver; xver > 0 && xver < 3 { if clientConn == nil { clientConn, err = realTargetAddr.Dial(nil, nil) if err != nil { result = -1 return } } _, err = netLayer.WritePROXYprotocol(xver, wlc, clientConn) if err != nil { if ce := iics.CanLogErr("Failed in WritePROXYprotocol"); ce != nil { ce.Write(zap.String("target", targetAddr.String()), zap.Error(err)) } result = -1 return } } ////////////////////////////// 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 { tryTlsLazyRawRelay(iics.id, true, uc, nil, targetAddr, clientConn, wlc, nil, true, nil) } result = 1 return } else { clientEndRemoteClientTlsRawReadRecorder = tlsLayer.NewRecorder() teeConn := tlsLayer.NewTeeConn(clientConn, clientEndRemoteClientTlsRawReadRecorder) clientConn = teeConn } } tlsConn, err2 := client.GetTLS_Client().Handshake(clientConn) if err2 != nil { if ce := iics.CanLogErr("Failed in handshake outClient tls"); ce != nil { ce.Write(zap.String("target", targetAddr.String()), zap.Error(err2)) } result = -1 return } clientConn = tlsConn } ////////////////////////////// header 层 ///////////////////////////////////// if header := client.HasHeader(); header != nil && !(advClient != nil && advClient.CanHandleHeaders()) { clientConn = &httpLayer.HeaderConn{ Conn: clientConn, H: header, } } ////////////////////////////// 高级层握手阶段 ///////////////////////////////////// advLayerHandshakeStep: if adv != "" { switch { //quic case advClient.IsSuper(): clientConn, err = muxC.DialSubConn(dialedCommonConn) if err != nil { if ce := iics.CanLogErr("Failed in DialSubConn"); ce != nil { ce.Write( zap.Error(err), ) } result = -1 return } //grpc case advClient.IsMux(): if dialedCommonConn == nil { dialedCommonConn, err = muxC.GetCommonConn(clientConn) if dialedCommonConn == nil { if ce := iics.CanLogErr("Failed in GetCommonConn"); ce != nil { ce.Write( zap.Error(err), ) } result = -1 return } } clientConn, err = muxC.DialSubConn(dialedCommonConn) if err != nil { if ce := iics.CanLogErr("Failed in grpc.DialNewSubConn"); 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() } result = -1 return } //ws default: var edlen int if !hasInnerMux && advClient.IsEarly() && wlc != nil { edlen = len(iics.firstPayload) } var wc net.Conn wcs := advClient.(advLayer.SingleClient) wc, err = wcs.Handshake(clientConn, edlen) if err != nil { if ce := iics.CanLogErr("Failed in handshake Single AdvLayer"); ce != nil { ce.Write( zap.String("advLayer", adv), zap.String("target", targetAddr.String()), zap.Error(err), ) } result = -1 return } clientConn = wc } } shakeStep: ////////////////////////////// 代理层 握手阶段 ///////////////////////////////////// if !isudp || hasInnerMux { //udp但是有innermux时 依然用handshake, 而不是 EstablishUDPChannel var ed []byte if !hasInnerMux { //如果有内层mux,则 firstPayload 不在本 dialClient 函数写入, 要在 dialInnerProxy 里 写入, 因为还有 innerMux层 和 inner Proxy 层 尚未拨号 if l := len(iics.firstPayload); l > 0 { ed = iics.firstPayload if ce := iics.CanLogDebug("handshake client with first payload"); ce != nil { ce.Write( zap.Int("len", l), ) } } else { iics.firstPayload = nil } } wrc, err = client.Handshake(clientConn, ed, targetAddr) if err != nil { if ce := iics.CanLogErr("Failed in Handshake client"); ce != nil { ce.Write( zap.String("target", targetAddr.String()), zap.Error(err), ) } result = -1 return } } else { theAddr := targetAddr if len(iics.firstPayload) > 0 { theAddr = iics.udpFirstTarget } udp_wrc, err = client.EstablishUDPChannel(clientConn, iics.firstPayload, theAddr) if err != nil { if ce := iics.CanLogErr("Failed in EstablishUDPChannel"); ce != nil { ce.Write( zap.String("target", targetAddr.String()), zap.Error(err), ) } result = -1 return } } ////////////////////////////// 建立内层 mux 阶段 ///////////////////////////////////// if hasInnerMux { //我们目前的实现中,mux统一使用smux v1, 即 smux.DefaultConfig返回的值。这可以兼容trojan-go的实现。 wrc, udp_wrc, result = dialInnerProxy(iics, client, wlc, wrc, innerProxyName, targetAddr, isudp) } return } //dialClient // 在 dialClient 中调用。 如果调用不成功,则result < 0. 若成功, 则 result == 0. func dialInnerProxy(iics incomingInserverConnState, client proxy.Client, wlc net.Conn, wrc io.ReadWriteCloser, innerProxyName string, targetAddr netLayer.Addr, isudp bool) (realwrc io.ReadWriteCloser, realudp_wrc netLayer.MsgConn, result int) { smuxSession := client.GetClientInnerMuxSession(wrc) if smuxSession == nil { result = -1 if ce := iics.CanLogErr("Failed dialInnerProxy, smuxSession == nil"); ce != nil { ce.Write() } return } stream, err := smuxSession.OpenStream() if err != nil { client.CloseInnerMuxSession() //发现就算 OpenStream 失败, session也不会自动被关闭, 需要我们手动关一下。 if ce := iics.CanLogWarn("Failed dialInnerProxy"); ce != nil { ce.Write(zap.Error(err)) } result = -1 return } muxDialConf := proxy.DialConf{ CommonConf: proxy.CommonConf{ Protocol: innerProxyName, }, } muxClient, err := proxy.NewClient(&muxDialConf) if err != nil { if ce := iics.CanLogDebug("mux inner proxy client creation failed"); ce != nil { ce.Write(zap.Error(err)) } result = -1 return } if isudp { theAddr := targetAddr if len(iics.firstPayload) > 0 { theAddr = iics.udpFirstTarget } realudp_wrc, err = muxClient.EstablishUDPChannel(stream, iics.firstPayload, theAddr) if err != nil { if ce := iics.CanLogDebug("mux inner proxy client handshake failed"); ce != nil { ce.Write(zap.Error(err)) } result = -1 return } } else { realwrc, err = muxClient.Handshake(stream, iics.firstPayload, targetAddr) if err != nil { if ce := iics.CanLogDebug("mux inner proxy client handshake failed"); ce != nil { ce.Write(zap.Error(err)) } result = -1 return } } return } //dialInnerProxy // dialClient_andRelay 进行实际转发(Copy)。被 passToOutClient 调用. // targetAddr为用户所请求的地址。 // client为真实要拨号的client,可能会与iics里的defaultClient不同。以client为准。 // wlc为调用者所提供的 此请求的 来源 链接 func dialClient_andRelay(iics incomingInserverConnState, targetAddr netLayer.Addr, client proxy.Client, isTlsLazy_clientEnd bool, wlc net.Conn, udp_wlc netLayer.MsgConn) { //在内层mux时, 不能因为单个传输完毕就关闭整个连接 if innerMuxResult, _ := client.HasInnerMux(); innerMuxResult == 0 { if iics.shouldCloseInSerBaseConnWhenFinish && !iics.isInner { if iics.baseLocalConn != nil { defer iics.baseLocalConn.Close() } } if wlc != nil { defer wlc.Close() } } wrc, udp_wrc, realTargetAddr, clientEndRemoteClientTlsRawReadRecorder, result := dialClient(iics, targetAddr, client, wlc, udp_wlc, isTlsLazy_clientEnd) if result != 0 { return } ////////////////////////////// 实际转发阶段 ///////////////////////////////////// if !targetAddr.IsUDP() { if !iics.routedToDirect { // 我们加了回落之后,就无法确定 “未使用tls的outClient 一定是在服务端” 了 if isTlsLazy_clientEnd { if client.IsUseTLS() { //必须是 UserClient if userClient := client.(proxy.UserClient); userClient != nil { tryTlsLazyRawRelay(iics.id, 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 { tryTlsLazyRawRelay(iics.id, false, nil, userServer, netLayer.Addr{}, wrc, wlc, iics.baseLocalConn, false, iics.inServerTlsRawReadRecorder) return } } } if gi := iics.GlobalInfo; gi != nil { atomic.AddInt32(&gi.ActiveConnectionCount, 1) netLayer.Relay(&realTargetAddr, wrc, wlc, iics.id, &gi.AllDownloadBytesSinceStart, &gi.AllUploadBytesSinceStart) atomic.AddInt32(&gi.ActiveConnectionCount, -1) } else { netLayer.Relay(&realTargetAddr, wrc, wlc, iics.id, nil, nil) } return } else { if ffb := iics.fallbackFirstBuffer; ffb != nil { udp_wrc.WriteMsg(ffb.Bytes(), targetAddr) } var ac *int32 var adc *uint64 var auc *uint64 if iics.GlobalInfo != nil { ac = &iics.GlobalInfo.ActiveConnectionCount adc = &iics.GlobalInfo.AllDownloadBytesSinceStart auc = &iics.GlobalInfo.AllUploadBytesSinceStart atomic.AddInt32(ac, 1) } if client.IsUDP_MultiChannel() { if ce := iics.CanLogDebug("Relaying UDP with MultiChannel"); ce != nil { ce.Write() } netLayer.RelayUDP_separate(udp_wrc, udp_wlc, &targetAddr, adc, auc, func(raddr netLayer.Addr) netLayer.MsgConn { if ce := iics.CanLogDebug("Relaying UDP with MultiChannel,dialfunc called"); ce != nil { ce.Write() } _, udp_wrc, _, _, result := dialClient(iics, raddr, client, nil, udp_wlc, false) if ce := iics.CanLogDebug("Relaying UDP with MultiChannel, dialfunc call returned"); ce != nil { ce.Write(zap.Int("result", result)) } if result == 0 { return udp_wrc } return nil }) } else { netLayer.RelayUDP(udp_wrc, udp_wlc, adc, auc) } if ac != nil { atomic.AddInt32(ac, -1) } return } }