初步实现 tls lazy encrypt 功能 (splice)

This commit is contained in:
hahahrfool
2022-03-11 14:06:55 +08:00
parent 1dfc31a89d
commit e558ba21cf
10 changed files with 406 additions and 21 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,6 @@
*.DS_Store
v2ray_simple
v2ray_simple_linux*
v2ray_simple_win*
client.json
server.json

View File

@@ -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
```
## 交流

View File

@@ -7,7 +7,11 @@ import (
var (
standardBytesPool sync.Pool //1500
// 实际上tcp默认是 16384, 16k实际上范围是1k128k之间我们64k已经够了
//而 udp则最大还不到 64k。(65535208)
standardPacketPool sync.Pool //64*1024
customBytesPool sync.Pool // >1500
bufPool sync.Pool

132
main.go
View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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 n3表示域名n是域名长度然后域名很可能是 119 119 119 46 开头,表示 www.
// 比如百度就是 [5 1 0 3 13 119 119 119 46 98]
cmd := buf[1]
switch cmd {
case CmdBind:

View File

@@ -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
}

29
tlsLayer/conn.go Normal file
View File

@@ -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
}

195
tlsLayer/detect.go Normal file
View File

@@ -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 322 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
}

View File

@@ -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