diff --git a/examples/vlesss.client.toml b/examples/vlesss.client.toml index 594e295..59422ae 100644 --- a/examples/vlesss.client.toml +++ b/examples/vlesss.client.toml @@ -25,10 +25,10 @@ host = "127.0.0.1" # 必填, 可填ip或域名;如果 network是unix的话, port = 10800 # 必填 [[dial]] -tag = "my_vlesss1" # 可选, 但不可与其他tag重复 +tag = "my_vlesss1" # 同listen对应配置, 可选, 但不可与其他tag重复 protocol = "vlesss" # vless 的 尾缀s 表示 使用tls uuid = "a684455c-b14f-11ea-bf0d-42010aaa0003" # 这个只是一个示例uuid,请自己生成一个新的. -host = "127.0.0.1" +host = "127.0.0.1" # 同listen对应配置, 可填ip或域名;如果 network是unix的话,要填一个文件名(不需要已存在,可以是完整路径). #network = "udp" # network 目前支持 tcp,udp 和unix;如果不给出则默认为 tcp; dial和listen都可配置此项 @@ -47,7 +47,7 @@ host = "127.0.0.1" # 除了在 protocol 字段使用 s尾缀 之外,还可以明示使用tls. # 这两种方法不可重复使用.我们首选前者, 更简约, 当然如果你使用时,需要频繁开关tls,那么可以单独列出来 便于配置 -port = 4433 +port = 4433 # 必填 version = 0 # 协议版本, 可省略, 省略则默认为最老版本 insecure = true # 我们示例使用自签名证书,所以要开启 insecure. 实际场合请使用真证书并关闭 insecure utls = true #是否使用 utls 来应用 chrome指纹进行伪装 diff --git a/grpc/client.go b/grpc/client.go index be9ad1b..89ef7c8 100644 --- a/grpc/client.go +++ b/grpc/client.go @@ -3,7 +3,7 @@ package grpc import ( "context" "net" - sync "sync" + "sync" "time" "github.com/hahahrfool/v2ray_simple/netLayer" @@ -21,11 +21,11 @@ var ( type ClientConn *grpc.ClientConn /* -调用过程 +建立新客户端连接的调用过程: 先用 GetEstablishedConnFor 看看有没有已存在的 clientConn -没有已存在的话,自己先拨号tcp,然后拨号tls,然后把tls连接 传递给 ClientHandshake, 生成一个 clientConn +没有 已存在的 时,自己先拨号tcp,然后拨号tls,然后把tls连接 传递给 ClientHandshake, 生成一个 clientConn 然后把获取到的 clientConn传递给 DialNewSubConn, 获取可用的一条 grpc 连接 @@ -34,16 +34,18 @@ type ClientConn *grpc.ClientConn //获取与 某grpc服务器的 已存在的grpc连接 func GetEstablishedConnFor(addr *netLayer.Addr) ClientConn { clientconnMutex.RLock() - clieintconn := clientconnMap[addr.GetHashable()] + clientconn := clientconnMap[addr.GetHashable()] clientconnMutex.RUnlock() - if clieintconn == nil { + if clientconn == nil { return nil } - if (*grpc.ClientConn)(clieintconn).GetState() != connectivity.Shutdown { - return clieintconn + if (*grpc.ClientConn)(clientconn).GetState() != connectivity.Shutdown { + return clientconn } + //如果state是shutdown的话,我们也不用特地清除map,因为下一次申请就会覆盖map中的该项. + //如果底层tcp被关闭,state就会为 Shutdown return nil } @@ -51,10 +53,11 @@ func GetEstablishedConnFor(addr *netLayer.Addr) ClientConn { //该 underlay一般为 tls连接。 addr为实际的远程地址,我们不从 underlay里获取addr,避免转换. func ClientHandshake(underlay net.Conn, addr *netLayer.Addr) (ClientConn, error) { - //v2ray的实现中用到了一个 globalDialerMap[dest], 可以利用现有连接, + // 用到了一个 clientconnMap , 可以利用现有连接, // 只有map里没有与对应目标远程地址的连接的时候才会拨号; - // 这 应该就是一种mux的实现 - // 如果有之前播过的client的话,直接利用现有client进行 NewStream, 然后服务端的话, 实际上会获得到第二条连接; + // 这就是一种mux的实现 + // 如果有之前拨号过的client的话,直接利用现有client进行 NewStream, + // 然后服务端的话, 实际上会获得到同一条tcp链接上的第二条子连接; //也就是说,底层连接在客户端-服务端只用了一条,但是 服务端处理时却会抽象出 多条Stream连接进行处理 @@ -84,7 +87,8 @@ func ClientHandshake(underlay net.Conn, addr *netLayer.Addr) (ClientConn, error) //在一个已存在的grpc连接中 进行新的子连接申请 func DialNewSubConn(path string, clientconn ClientConn, addr *netLayer.Addr) (net.Conn, error) { - //不像服务端需要自己写一个实现StreamServer接口的结构, 我们Client端直接可以调用函数生成 StreamClient + // 帮助理解: + // 不像服务端需要自己写一个实现StreamServer接口的结构, 我们Client端直接可以调用函数生成 StreamClient // 这也是grpc的特点, 客户端只负责 “调用“ ”service“,而具体的service的实现 是在服务端. streamClient := NewStreamClient((*grpc.ClientConn)(clientconn)).(StreamClient_withName) diff --git a/grpc/server.go b/grpc/server.go index fa19fad..47739a8 100644 --- a/grpc/server.go +++ b/grpc/server.go @@ -9,8 +9,8 @@ import ( ) // google.golang.org/grpc.(*Server).handleRawConn -//go:linkname HandleRawConn google.golang.org/grpc.(*Server).handleRawConn -func HandleRawConn(c *grpc.Server, lisAddr string, rawConn net.Conn) +//go:linkname handle_grpcRawConn google.golang.org/grpc.(*Server).handleRawConn +func handle_grpcRawConn(c *grpc.Server, lisAddr string, rawConn net.Conn) //Server实现 grpc生成的 StreamServer 接口,用于不断处理一个客户端传来的新需求 type Server struct { @@ -25,15 +25,17 @@ type Server struct { } // StartHandle方法 被用于 手动给 grpc提供新连接. -// 在本作中 我们不使用 grpc的listen的方法。 -//非阻塞, +// 在本作中 我们不使用 grpc的listen的方法。这样更加灵活. +//非阻塞. func (s *Server) StartHandle(conn net.Conn) { //非阻塞,因为 grpc.(*Server).handleRawConn 是非阻塞的,里面用了新的goroutine - HandleRawConn(s.gs, "", conn) + handle_grpcRawConn(s.gs, "", conn) } // 该 Tun方法会被 grpc包调用, stream_TunServer就是获取到的新连接; +// 实际上就是在 handle_grpcRawConn 后, 每一条客户端发来的子连接 都会调用一次 s.Tun . +// // 我们把该 stream_TunServer 包装成 net.Conn 并传入 NewConnChan // 该方法是自动调用的, 我们不用管. func (s *Server) Tun(stream_TunServer Stream_TunServer) error { @@ -66,7 +68,7 @@ func NewServer(serviceName string) *Server { newConnChan := make(chan net.Conn, 10) s := &Server{ - NewConnChan: newConnChan, + NewConnChan: newConnChan, //该NewConnChan目前是永远不会被关闭的, 因为我们始终在监听新连接 gs: gs, serviceName: serviceName, } diff --git a/main.go b/main.go index 8174151..ba38d0b 100644 --- a/main.go +++ b/main.go @@ -399,9 +399,8 @@ func handleNewIncomeConnection(inServer proxy.Server, defaultClientForThis proxy for { newGConn, ok := <-grpcs.NewConnChan if !ok { - if utils.CanLogErr() { - log.Println("upgrade grpc not ok") - + if utils.CanLogWarn() { + log.Println("grpc getNewSubConn not ok") } iics.baseLocalConn.Close() @@ -706,35 +705,34 @@ afterLocalServerHandshake: // server也一样,会在特定的场合给 CRUMFURS 传值,这个机制是与main函数无关的 // 而且 wrappedConn 会被 inServer 保存起来,用于后面的 unknownRemoteAddrMsgWriter - return } else { //如果不是CRUMFURS命令,那就是普通的针对某udp地址的连接,见下文 uniExtractor 的使用 - //defer wrappedConn.Close() + iics.shouldCloseBaseConnAfterCopyComplete = true } case "socks5": - // UDP Associate: - // // 因为socks5的 UDP Associate 办法是较为特殊的,不使用现有tcp而是新建立udp,所以此时该tcp连接已经没用了 // 另外,此时 targetAddr.IsUDP 只是用于告知此链接是udp Associate,并不包含实际地址信息 - //但是根据socks5标准,这个tcp链接同样是 keep alive的,否则客户端就会认为服务端挂掉了. - default: - //defer wrappedConn.Close() iics.shouldCloseBaseConnAfterCopyComplete = true } } else { - if !(iics.isTlsLazyServerEnd) { - //lazy_encrypt情况比较特殊 - // 如果不是lazy的情况的话,转发结束后,要自动关闭 - //defer wrappedConn.Close() - iics.shouldCloseBaseConnAfterCopyComplete = true + //lazy_encrypt情况比较特殊,基础连接何时被关闭会在tlslazy相关代码中处理。 + // 如果不是lazy的情况的话,转发结束后,要自动关闭 + if !iics.isTlsLazyServerEnd { + + //实测 grpc.Conn 被调用了Close 也不会实际关闭连接,而是会卡住,阻塞,直到底层tcp连接被关闭后才会返回 + // 但是我们还是 直接避免这种情况 + if !inServer.IsMux() { + iics.shouldCloseBaseConnAfterCopyComplete = true + + } } @@ -742,7 +740,7 @@ afterLocalServerHandshake: // 下面一段代码 单独处理 udp承载数据的特殊转发。 // - // 这里只处理 vless v1 的CRUMFURS 转发到direct的情况 以及 socks5 的udp associate 转发到vless v1的情况; + // 这里只处理 vless v1 的CRUMFURS 转发到direct的情况 以及 socks5 的udp associate 转发到vless 的情况; // 如果条件不符合则会跳过而进入下一阶段 if targetAddr.IsUDP() { @@ -791,11 +789,11 @@ afterLocalServerHandshake: // 将 outClient 视为 UDP_Putter ,就可以转发udp信息了 - //direct 和 vless 都实现了 UDP_Putter. + //direct 和 vless 的Client 都实现了 UDP_Putter. - // direct 实现了 UDP_Putter (通过 UDP_Pipe和 RelayUDP_to_Direct函数), 所以目前 socks5直接转发udp到direct 的功能 已经实现。 + // direct 通过 UDP_Pipe和 RelayUDP_to_Direct函数 实现了 UDP_Putter - // 我在 vless 的client 的 UserConn 中实现了 UDP_Putter, 新连接的Handshake过程会在 dialFunc 被调用 时发生 + // vless 的client 实现了 UDP_Putter, 新连接的Handshake过程会在 dialFunc 被调用 时发生 if putter := client.(netLayer.UDP_Putter); putter != nil { @@ -818,6 +816,9 @@ afterLocalServerHandshake: } + ////////////////////////////// 拨号阶段 ///////////////////////////////////// + + //log.Println("will dial", iics.shouldCloseBaseConnAfterCopyComplete) dialClient(iics, targetAddr, client, isTlsLazy_clientEnd, wlc, false) } @@ -830,11 +831,19 @@ afterLocalServerHandshake: 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 - ////////////////////////////// 拨号阶段 ///////////////////////////////////// //先确认拨号地址 @@ -847,7 +856,7 @@ func dialClient(iics incomingInserverConnState, targetAddr *netLayer.Addr, clien log.Println("request isn't the appointed domain", targetAddr, uniqueTestDomain) } - return nil, &utils.NumErr{N: 1, Prefix: "dialClient err, "} + return nil, utils.NumErr{N: 1, Prefix: "dialClient err, "} } if utils.CanLogInfo() { @@ -890,7 +899,7 @@ func dialClient(iics incomingInserverConnState, targetAddr *netLayer.Addr, clien log.Println("failed in dial", realTargetAddr.String(), ", Reason: ", err) } - return nil, &utils.NumErr{N: 2, Prefix: "dialClient err, "} + return nil, utils.NumErr{N: 2, Prefix: "dialClient err, "} } //log.Println("dial real addr ok", realTargetAddr) @@ -912,7 +921,7 @@ func dialClient(iics incomingInserverConnState, targetAddr *netLayer.Addr, clien } - return nil, &utils.NumErr{N: 3, Prefix: "dialClient err, "} + return nil, utils.NumErr{N: 3, Prefix: "dialClient err, "} } else { clientEndRemoteClientTlsRawReadRecorder = tlsLayer.NewRecorder() @@ -925,7 +934,7 @@ func dialClient(iics incomingInserverConnState, targetAddr *netLayer.Addr, clien 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, "} + return nil, utils.NumErr{N: 4, Prefix: "dialClient err, "} } clientConn = tlsConn @@ -950,7 +959,7 @@ advLayerStep: iics.baseLocalConn.Close() } - return nil, &utils.NumErr{N: 5, Prefix: "dialClient err, "} + return nil, utils.NumErr{N: 5, Prefix: "dialClient err, "} } } @@ -960,11 +969,14 @@ advLayerStep: 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, "} + return nil, utils.NumErr{N: 6, Prefix: "dialClient err, "} } case "ws": @@ -981,7 +993,7 @@ advLayerStep: if utils.CanLogErr() { log.Println("err when reading ws early data", e) } - return nil, &utils.NumErr{N: 7, Prefix: "dialClient err, "} + return nil, utils.NumErr{N: 7, Prefix: "dialClient err, "} } ed = edBuf[:n] //log.Println("will send early data", n, ed) @@ -1007,7 +1019,7 @@ advLayerStep: log.Println("failed in handshake ws to", targetAddr.String(), ", Reason: ", err) } - return nil, &utils.NumErr{N: 8, Prefix: "dialClient err, "} + return nil, utils.NumErr{N: 8, Prefix: "dialClient err, "} } clientConn = wc @@ -1022,7 +1034,7 @@ advLayerStep: log.Println("failed in handshake to", targetAddr.String(), ", Reason: ", err) } - return nil, &utils.NumErr{N: 9, Prefix: "dialClient err, "} + return nil, utils.NumErr{N: 9, Prefix: "dialClient err, "} } //log.Println("all handshake finished") @@ -1041,7 +1053,7 @@ advLayerStep: //必须是 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, "} + return nil, utils.NumErr{N: 11, Prefix: "dialClient err, "} } } @@ -1052,7 +1064,7 @@ advLayerStep: 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, "} + return nil, utils.NumErr{N: 12, Prefix: "dialClient err, "} } } diff --git a/proxy/socks5/client.go b/proxy/socks5/client.go index 8ed40d5..d2c58f1 100644 --- a/proxy/socks5/client.go +++ b/proxy/socks5/client.go @@ -29,7 +29,7 @@ func Client_EstablishUDPAssociate(conn net.Conn) (port int, err error) { return } if n != 2 || ba[0] != Version5 || ba[1] != 0 { - return 0, &utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 1} + return 0, utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 1} } //请求udp associate 阶段 @@ -45,6 +45,8 @@ func Client_EstablishUDPAssociate(conn net.Conn) (port int, err error) { ba[8] = 0 //port ba[9] = 0 //port // 按理说要告诉服务端我们要用到的ip和端口,但是我们不知道,所以全填零 + // 在内网中的话,我们是可以知道的,但是因为内网很安全所以无所谓;在NAT中我们肯定是不知道的。 + // 如果是在纯外网中则是可以知道的,但是为啥非要socks5这么不安全的协议呢?所以还是不予考虑。 _, err = conn.Write(ba[:10]) if err != nil { @@ -56,7 +58,7 @@ func Client_EstablishUDPAssociate(conn net.Conn) (port int, err error) { return } if n != 10 || ba[0] != Version5 || ba[1] != 0 || ba[2] != 0 || ba[3] != 1 || ba[4] != 0 || ba[5] != 0 || ba[6] != 0 || ba[7] != 0 { - return 0, &utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 2} + return 0, utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 2} } port = int(ba[8])<<8 | int(ba[9]) @@ -126,7 +128,7 @@ func Client_ReadUDPResponse(udpConn *net.UDPConn, supposedServerAddr *net.UDPAdd return } if buf[0] != 0 || buf[1] != 0 || buf[2] != 0 { - e = &utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 1} + e = utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 1} return } atype := buf[3] @@ -149,7 +151,7 @@ func Client_ReadUDPResponse(udpConn *net.UDPConn, supposedServerAddr *net.UDPAdd target.Name = string(nameBuf) default: - e = &utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 2} + e = utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 2} return } diff --git a/proxy/socks5/server.go b/proxy/socks5/server.go index 988ebd4..622ec77 100644 --- a/proxy/socks5/server.go +++ b/proxy/socks5/server.go @@ -164,7 +164,7 @@ func (s *Server) Handshake(underlay net.Conn) (io.ReadWriter, *netLayer.Addr, er Network: "udp", } - //这里为了解析域名, 就是用了用 netLayer.Addr 作为中介的方式 + //这里为了解析域名, 就用了 netLayer.Addr 作为中介的方式 uc := &UDPConn{ clientSupposedAddr: clientFutureAddr.ToUDPAddr(), UDPConn: udpRC, @@ -243,6 +243,7 @@ func (u *UDPConn) StartPushResponse(udpPutter netLayer.UDP_Putter) { // 监听 与客户端的udp连接 (u.UDPConn);循环查看客户端发来的请求信息; // 然后将该请求 用 udpPutter.WriteUDPRequest 发送给 udpPutter // 至于fullcone与否它是不管的。 +// 如果客户端一开始没有指明自己连接本服务端的ip和端口, 则将第一个发来的正确的socks5请求视为该客户端,并记录。 func (u *UDPConn) StartReadRequest(udpPutter netLayer.UDP_Putter, dialFunc func(targetAddr *netLayer.Addr) (io.ReadWriter, error)) { var clientSupposedAddrIsNothing bool @@ -269,11 +270,14 @@ func (u *UDPConn) StartReadRequest(udpPutter netLayer.UDP_Putter, dialFunc func( continue } - if !clientSupposedAddrIsNothing && (!addr.IP.Equal(u.clientSupposedAddr.IP) || addr.Port != u.clientSupposedAddr.Port) { + if !clientSupposedAddrIsNothing { - //just random attack message. - continue + if !addr.IP.Equal(u.clientSupposedAddr.IP) || addr.Port != u.clientSupposedAddr.Port { + //just random attack message. + continue + + } } atyp := bs[3] @@ -322,7 +326,8 @@ func (u *UDPConn) StartReadRequest(udpPutter netLayer.UDP_Putter, dialFunc func( newStart := off + l - thisaddr := &netLayer.Addr{ + //为了解析域名, 我们用 netLayer.Addr 作为中介. + requestAddr := &netLayer.Addr{ IP: theIP, Name: theName, Port: thePort, @@ -336,7 +341,7 @@ func (u *UDPConn) StartReadRequest(udpPutter netLayer.UDP_Putter, dialFunc func( //log.Println("socks5 server,StartReadRequest, got msg", thisaddr, string(bs[newStart:n])) - udpPutter.WriteUDPRequest(thisaddr.ToUDPAddr(), bs[newStart:n], dialFunc) + udpPutter.WriteUDPRequest(requestAddr.ToUDPAddr(), bs[newStart:n], dialFunc) } } diff --git a/utils/error.go b/utils/error.go index be460c9..80fd17f 100644 --- a/utils/error.go +++ b/utils/error.go @@ -12,7 +12,7 @@ type NumErr struct { Prefix string } -func (ne *NumErr) Error() string { +func (ne NumErr) Error() string { return ne.Prefix + strconv.Itoa(ne.N) } @@ -23,57 +23,51 @@ type ErrFirstBuffer struct { First *bytes.Buffer } -func (ef *ErrFirstBuffer) Unwarp() error { +func (ef ErrFirstBuffer) Unwarp() error { return ef.Err } -func (ef *ErrFirstBuffer) Error() string { +func (ef ErrFirstBuffer) Error() string { return ef.Err.Error() } -func NewErr(desc string, e error) *ErrInErr { - return &ErrInErr{ +// 返回结构体,而不是指针, 这样可以避免内存逃逸到堆 +func NewErr(desc string, e error) ErrInErr { + return ErrInErr{ ErrDesc: desc, ErrDetail: e, } } -func NewDataErr(desc string, e error, data interface{}) *ErrInErr { - return &ErrInErr{ +// 返回结构体,而不是指针, 这样可以避免内存逃逸到堆 +func NewDataErr(desc string, e error, data interface{}) ErrInErr { + return ErrInErr{ ErrDesc: desc, ErrDetail: e, Data: data, } } -// ErrInErr 很适合一个err包含另一个err,并且提供附带数据的情况 +// ErrInErr 很适合一个err包含另一个err,并且提供附带数据的情况. type ErrInErr struct { ErrDesc string ErrDetail error Data any - - cachedStr string } -func (e *ErrInErr) Error() string { +func (e ErrInErr) Error() string { return e.String() } -func (e *ErrInErr) Unwarp() error { +func (e ErrInErr) Unwarp() error { return e.ErrDetail } -func (e *ErrInErr) String() string { - if e.cachedStr == "" { - e.cachedStr = e.string() - } - return e.cachedStr -} +func (e ErrInErr) String() string { -func (e *ErrInErr) string() string { if e.Data != nil { if e.ErrDetail != nil {