diff --git a/.gitignore b/.gitignore index 025e4b1..3e4c678 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ *.DS_Store v2ray_simple +v2ray_simple_linux* +v2ray_simple_win* client.json server.json diff --git a/README.md b/README.md index 4be6ae5..48d9453 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,13 @@ verysimple 继承 v2simple的一个优点,就是服务端的配置也可以用 此问题有待考证解决。也不知道是不是只有我自己有这个问题。。 +## 交叉编译 + +```sh +GOARCH=amd64 GOOS=linux go build -trimpath -ldflags "-s -w -buildid=" -o v2ray_simple_linux_amd64_v1.0.0 +GOARCH=arm64 GOOS=linux go build -trimpath -ldflags "-s -w -buildid=" -o v2ray_simple_linux_arm64_v1.0.0 +GOARCH=amd64 GOOS=windows go build -trimpath -ldflags "-s -w -buildid=" -o v2ray_simple_win10_v1.0.0.exe +``` ## 交流 diff --git a/common/pool.go b/common/pool.go index 153afe1..88ec1f8 100644 --- a/common/pool.go +++ b/common/pool.go @@ -6,9 +6,13 @@ import ( ) var ( - standardBytesPool sync.Pool //1500 + standardBytesPool sync.Pool //1500 + + // 实际上tcp默认是 16384, 16k,实际上范围是1k~128k之间,我们64k已经够了 + //而 udp则最大还不到 64k。(65535-20-8) standardPacketPool sync.Pool //64*1024 - customBytesPool sync.Pool // >1500 + + customBytesPool sync.Pool // >1500 bufPool sync.Pool ) diff --git a/main.go b/main.go index d7637e1..98de7be 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ import ( "github.com/hahahrfool/v2ray_simple/proxy/direct" "github.com/hahahrfool/v2ray_simple/proxy/socks5" "github.com/hahahrfool/v2ray_simple/proxy/vless" + "github.com/hahahrfool/v2ray_simple/tlsLayer" "github.com/hahahrfool/v2ray_simple/proxy" ) @@ -31,6 +32,9 @@ var ( conf *Config directClient proxy.Client + + tls_lazy_encryptPtr = flag.Bool("lazy", false, "tls lazy encrypt (splice)") + tls_lazy_encrypt bool ) func init() { @@ -74,6 +78,7 @@ func main() { printVersion() flag.Parse() + tls_lazy_encrypt = *tls_lazy_encryptPtr var err error @@ -135,10 +140,19 @@ func main() { func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Client, thisLocalConnectionInstance net.Conn) { - //log.Println("got new", thisLocalConnectionInstance.RemoteAddr().String()) + baseLocalConn := thisLocalConnectionInstance + + log.Println("got new", thisLocalConnectionInstance.RemoteAddr().String()) var err error + + //此时,baseLocalConn里面 正常情况下, 服务端看到的是 客户端的golang的tls 拨号发出的 tls数据 + // 客户端看到的是 socks5的数据, 我们首先就是要看看socks5里的数据是不是tls,而socks5自然 IsUseTLS 是false + + // 如果是服务端的话,那就是 localServer.IsUseTLS == true, 此时,我们正常握手,然后我们需要判断的是它承载的数据 + if localServer.IsUseTLS() { + tlsConn, err := localServer.GetTLS_Server().Handshake(thisLocalConnectionInstance) if err != nil { log.Println("failed in handshake localServer tls", localServer.AddrStr(), err) @@ -147,6 +161,7 @@ func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Clie } thisLocalConnectionInstance = tlsConn + } wlc, targetAddr, err := localServer.Handshake(thisLocalConnectionInstance) @@ -156,6 +171,21 @@ func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Clie return } + // 我们在客户端 lazy_encrypt 探测时,读取socks5 传来的信息,因为这个和要发送到tls的信息时一模一样的,所以就不需要等包上vless、tls后再判断了, 直接解包 socks5进行判断 + // + // 而在服务端探测时,因为包了 tls,所以要在tls解包后, vless 解包后,再进行判断; + // 所以总之都是要在 localServer判断 wlc,只不过理由不一样 + + var thecc *tlsLayer.CopyConn + + if tls_lazy_encrypt { + + thecc = tlsLayer.NewDetectConn(baseLocalConn, wlc) + + wlc = thecc + //clientConn = cc + } + if targetAddr.IsUDP { switch localServer.Name() { @@ -267,7 +297,7 @@ func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Clie var realTargetAddr *proxy.Addr = targetAddr //direct的话自己是没有目的地址的,直接使用 请求的地址 if client.AddrStr() != "" { - log.Println("will dial", client.AddrStr()) + //log.Println("will dial", client.AddrStr()) realTargetAddr, _ = proxy.NewAddr(client.AddrStr()) } @@ -279,6 +309,7 @@ func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Clie } if client.IsUseTLS() { + tlsConn, err := client.GetTLS_Client().Handshake(clientConn) if err != nil { log.Println("failed in handshake remoteClient tls", targetAddr.String(), ", Reason: ", err) @@ -306,7 +337,104 @@ func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Clie log.Println("远程->本地 转发结束", realTargetAddr.String(), n, e) */ + if tls_lazy_encrypt { + tryRaw(wrc, wlc, client.IsUseTLS()) + return + } + go io.Copy(wrc, wlc) io.Copy(wlc, wrc) } + +//和 xtls的splice 含义相同 +func tryRaw(wrc, wlc io.ReadWriter, isclient bool) { + + //如果用了 lazy_encrypt, 则不直接利用Copy,因为有两个阶段:判断阶段和直连阶段 + // 在判断阶段,因为还没确定是否是 tls,所以是要继续用tls加密的, + // 而直连阶段,只要能让 Copy使用 ReadFrom, 就能一步一步最终使用splice了 + + //首先判断我们的wlc(*tlsLayer.CopyConn) 是否得出来 IsTLS + wlccc := wlc.(*tlsLayer.CopyConn) + wlccc_raw := wlccc.RawConn + + var rawWRC *net.TCPConn + + //wrc 有两种情况,如果客户端那就是tls,服务端那就是direct。我们不讨论服务端 处于中间层的情况 + + if isclient { + // 不过实际上 wrc 是 vless的 UserConn, 而UserConn的底层连接才是TLS + + wrcVless := wrc.(*vless.UserConn) + + tlsConn := wrcVless.Conn.(*tlsLayer.Conn) + + rawWRC = tlsConn.GetRaw() + + } else { + rawWRC = wrc.(*net.TCPConn) + } + + if rawWRC == nil { + log.Println("splice fail reason 3 ") + io.Copy(wrc, wlc) + return + } + + go func() { + //从 wlccc 读取,向 wrc 写入 + // 此时如果ReadFrom,那就是 wrc.ReadFrom(wlccc) + //wrc 要实现 ReaderFrom才行, 或者把最底层TCPConn暴露,然后 wlccc 也要把最底层 TCPConn暴露出来 + + p := common.GetPacket() + isgood := false + for { + + if isgood { + break + } + + n, err := wlccc.Read(p) + if err != nil { + + break + } + wrc.Write(p[:n]) + + if wlccc.R.IsTls && wlccc.RawConn != nil { + isgood = true + + } + } + if isgood { + + log.Println("成功SpliceRead 方向1") + rawWRC.ReadFrom(wlccc_raw) + + } + }() + + isgood2 := false + p := common.GetPacket() + + //从 wrc 读取,向 wlccc 写入 + for { + if isgood2 { + break + } + n, err := wrc.Read(p) + if err != nil { + + break + } + wlccc.Write(p[:n]) + if wlccc.W.IsTls && wlccc.RawConn != nil { + isgood2 = true + + } + } + if isgood2 { + log.Println("成功SpliceRead 方向2") + wlccc_raw.ReadFrom(rawWRC) + } +} diff --git a/proxy/proxy.go b/proxy/proxy.go index b4f0e58..7b47a5d 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -80,7 +80,7 @@ ws和grpc文件夹(第七层) # proxy 文件夹内容 -接口 ProxyCommonFuncs 和 结构 ProxyCommonStruct 给 这个架构定义了标准 +接口 ProxyCommon 和 结构 ProxyCommonStruct 给 这个架构定义了标准 而 Client 和 Server 接口 是 具体利用该架构的 客户端 和 服务端,都位于VSI中的第八层 @@ -112,7 +112,7 @@ type UserConn interface { } // 给一个节点 提供 VSI中 第 5-7层 的支持 -type ProxyCommonFuncs interface { +type ProxyCommon interface { AddrStr() string //地址,在server就是监听地址,在client就是拨号地址 SetAddrStr(string) @@ -128,7 +128,7 @@ type ProxyCommonFuncs interface { GetTLS_Client() *tlsLayer.Client } -func PrepareTLS_forProxyCommon(u *url.URL, isclient bool, com ProxyCommonFuncs) error { +func PrepareTLS_forProxyCommon(u *url.URL, isclient bool, com ProxyCommon) error { insecureStr := u.Query().Get("insecure") insecure := false if insecureStr != "" && insecureStr != "false" && insecureStr != "0" { @@ -201,7 +201,7 @@ func (s *ProxyCommonStruct) SetUseTLS() { // Client 用于向 服务端 拨号 type Client interface { - ProxyCommonFuncs + ProxyCommon Name() string @@ -261,7 +261,7 @@ func ClientFromURL(s string) (Client, error) { // Server 用于监听 客户端 的连接 type Server interface { - ProxyCommonFuncs + ProxyCommon Name() string diff --git a/proxy/socks5/server.go b/proxy/socks5/server.go index a451e53..7fa6c4a 100644 --- a/proxy/socks5/server.go +++ b/proxy/socks5/server.go @@ -57,10 +57,11 @@ func (s *Server) Handshake(underlay net.Conn) (io.ReadWriter, *proxy.Addr, error } defer underlay.SetReadDeadline(time.Time{}) - buf := common.GetBytes(512) - defer common.PutBytes(buf) + buf := common.GetPacket() + defer common.PutPacket(buf) // Read hello message + // 一般握手包发来的是 [5 1 0] n, err := underlay.Read(buf) if err != nil || n == 0 { return nil, nil, fmt.Errorf("failed to read hello: %w", err) @@ -70,19 +71,22 @@ func (s *Server) Handshake(underlay net.Conn) (io.ReadWriter, *proxy.Addr, error return nil, nil, fmt.Errorf("unsupported socks version %v", version) } - // Write hello response + // Write hello response, [5 0] // TODO: Support Auth _, err = underlay.Write([]byte{Version5, AuthNone}) if err != nil { return nil, nil, fmt.Errorf("failed to write hello response: %w", err) } - // Read command message + // Read command message, n, err = underlay.Read(buf) if err != nil || n < 7 { // Shortest length is 7 - return nil, nil, fmt.Errorf("failed to read command: %w", err) + return nil, nil, fmt.Errorf("read socks5 failed, msgTooShort: %w", err) } + // 一般可以为 5 1 0 3 n,3表示域名,n是域名长度,然后域名很可能是 119 119 119 46 开头,表示 www. + // 比如百度就是 [5 1 0 3 13 119 119 119 46 98] + cmd := buf[1] switch cmd { case CmdBind: diff --git a/tlsLayer/client.go b/tlsLayer/client.go index 0957890..47c337e 100644 --- a/tlsLayer/client.go +++ b/tlsLayer/client.go @@ -21,8 +21,17 @@ func NewTlsClient(host string, insecure bool) *Client { return c } -func (c *Client) Handshake(underlay net.Conn) (tlsConn *tls.Conn, err error) { - tlsConn = tls.Client(underlay, c.tlsConfig) - err = tlsConn.Handshake() +func (c *Client) Handshake(underlay net.Conn) (tlsConn *Conn, err error) { + rawConn := tls.Client(underlay, c.tlsConfig) + err = rawConn.Handshake() + + if err != nil { + return + } + + tlsConn = &Conn{ + Conn: rawConn, + } + return } diff --git a/tlsLayer/conn.go b/tlsLayer/conn.go new file mode 100644 index 0000000..0180eb4 --- /dev/null +++ b/tlsLayer/conn.go @@ -0,0 +1,29 @@ +package tlsLayer + +import ( + "crypto/tls" + "log" + "net" + "unsafe" +) + +type Conn struct { + *tls.Conn +} + +type faketlsconn struct { + // constant + conn net.Conn + isClient bool +} + +func (c *Conn) GetRaw() *net.TCPConn { + rc := (*faketlsconn)(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)))) + if rc != nil { + if rc.conn != nil { + log.Println("成功获取到 *net.TCPConn!", rc.conn.(*net.TCPConn)) + return rc.conn.(*net.TCPConn) + } + } + return nil +} diff --git a/tlsLayer/detect.go b/tlsLayer/detect.go new file mode 100644 index 0000000..68c8d62 --- /dev/null +++ b/tlsLayer/detect.go @@ -0,0 +1,195 @@ +package tlsLayer + +import ( + "io" + "log" + "net" + "os" + "strings" +) + +type CopyConn struct { + net.Conn //这个 Conn本包中不会用到,只是为了能让CopyConn支持 net.Conn + io.ReadWriter + W *DetectWriter + R *DetectReader + + RawConn *net.TCPConn // 这个是为了让外界能够直接拿到底层的连接 +} + +func (cc *CopyConn) Read(p []byte) (int, error) { + return cc.R.Read(p) +} + +func (cc *CopyConn) Write(p []byte) (int, error) { + return cc.W.Write(p) +} + +func (cc *CopyConn) ReadFrom(r io.Reader) (int64, error) { + if cc.RawConn != nil { + return cc.RawConn.ReadFrom(r) + } + return 0, io.EOF +} + +func NewDetectConn(oldConn net.Conn, rw io.ReadWriter) *CopyConn { + + var validOne io.ReadWriter = rw + if rw == nil { + validOne = oldConn + } + + cc := &CopyConn{ + Conn: oldConn, + ReadWriter: rw, + W: &DetectWriter{ + Writer: validOne, + }, + R: &DetectReader{ + Reader: validOne, + }, + } + + if netConn := oldConn.(*net.TCPConn); netConn != nil { + //log.Println("get netConn!") // 如果是客户端的socks5,网页浏览的话这里一定能转成 TCPConn + cc.RawConn = netConn + } + + return cc +} + +// DetectReader 对每个Read的数据进行分析,判断是否是tls流量 +type DetectReader struct { + io.Reader + IsTls bool + + packetCount int +} + +func init() { + log.SetOutput(os.Stdout) +} + +// 总之,我们在客户端的 Read 操作,就是 我们试图使用 Read 读取客户的请求,然后试图发往 外界 +// 所以在socks5后面 使用的这个 Read,是读取客户端发送的请求,比如 clienthello之类 +// +// 我们直接判断23 3 3字节,然后直接推定tls!不管三七二十一,判断错误就错误吧!快就得了! +func (dr *DetectReader) Read(p []byte) (n int, err error) { + n, err = dr.Reader.Read(p) + if dr.IsTls { + return + } + + if dr.packetCount > 8 { + //都8个包了还没断定tls?直接推定不是! + return + } + + if n > 3 { + dr.packetCount++ + p0 := p[0] + p1 := p[1] + p2 := p[2] + + /* + if p0 == 22 || p0 == 23 || p0 == 20 || (p0 == 21 && n == 31) { + //少数情况首部会有21,首部为 [21 3 3 0 26 0 0 0 0 0], 一般总长度为31 + // 其它都是 能被捕捉到的。 + if p[1] == 3 { + min := 5 + if n < 5 { + min = n + } + log.Println(" TLS R,", n, err, p[:min]) + dr.IsTls = true + return + } + }*/ + + if p0 == 23 && p1 == 3 && p2 == 3 { + log.Println("R got TLS!") + dr.IsTls = true + return + } + } + if err != nil { + eStr := err.Error() + if strings.Contains(eStr, "use of closed") || strings.Contains(eStr, "reset by peer") || strings.Contains(eStr, "EOF") { + return + } + } + + min := 10 + if n < 10 { + min = n + } + log.Println(" ======== Read,", n, err, p[:min], string(p[:min])) + return +} + +// DetectReader 对每个Read的数据进行分析,判断是否是tls流量 +type DetectWriter struct { + io.Writer + IsTls bool + + packetCount int +} + +//我发现,数据基本就是 23 3 3, 22 3 3,22 3 1 , 20 3 3 +// 一个首包不为23 3 3 的包往往会出现在 1184长度的包的后面,而且一般 1184长度的包 的开头是 22 3 3 0 122,且总是在Write里面发生 +// 所以可以直接推测这个就是握手包; 实测 22 3 3 0 122 开头的,无一例外都是 1184长度,且后面接多个 开头任意的 Write +// 也有一些特殊情况,比如 22 3 1 首部的包,往往是 517 长度,后面也会紧跟着一些首部不为 22/23 的 Write +// +// 23 3 3 也是有可能 发生后面不为22/23的write,长度 不等 +// +// 总之,我们在客户端的 Write 操作,就是 外界试图使用我们的 Write 写入数据 +// 所以在socks5后面 使用的这个Write,应该是把 服务端的响应 写回 socks5,比如 serverhello之类 +// +// 根据之前讨论,23 3 3 就是 数据部分,TLSCiphertext +// https://halfrost.com/https_record_layer/ +// 总之我们依然判断 23 3 3 好了,但是不循环判断了,没那么多技巧,先判断是否存在握手包,握手完成后,遇到23 3 3 后,直接就 +// 进入direct模式; 目前从简,连握手包都不检测!测错就测错! +func (dr *DetectWriter) Write(p []byte) (n int, err error) { + n, err = dr.Writer.Write(p) + if dr.IsTls { + return + } + + if dr.packetCount > 8 { + //都8个包了还没断定tls?直接推定不是! + return + } + + if n > 3 { + dr.packetCount++ + + p0 := p[0] + p1 := p[1] + p2 := p[2] + + /* + if p0 == 22 || p0 == 23 || p0 == 20 { + if p[1] == 3 { + min := 5 + if n < 5 { + min = n + } + log.Println(" TLS W,", n, err, p[:min]) + return + } + }*/ + + if p0 == 23 && p1 == 3 && p2 == 3 { + log.Println("W got TLS!") + dr.IsTls = true + return + } + } + + min := 10 + if n < 10 { + min = n + } + log.Println(" ======== Write,", n, err, p[:min], string(p[:min])) + return +} diff --git a/tlsLayer/server.go b/tlsLayer/server.go index 84f324f..5f468ce 100644 --- a/tlsLayer/server.go +++ b/tlsLayer/server.go @@ -30,11 +30,18 @@ func NewServer(hostAndPort, host, certFile, keyFile string, isInsecure bool) (*S return s, nil } -func (s *Server) Handshake(underlay net.Conn) (tlsConn *tls.Conn, err error) { - tlsConn = tls.Server(underlay, s.tlsConfig) - err = tlsConn.Handshake() +func (s *Server) Handshake(underlay net.Conn) (tlsConn *Conn, err error) { + rawConn := tls.Server(underlay, s.tlsConfig) + err = rawConn.Handshake() if err != nil { - return tlsConn, common.NewErr("tls握手失败", err) + //return tlsConn, + err = common.NewErr("tls握手失败", err) + + return + } + + tlsConn = &Conn{ + Conn: rawConn, } return