update tls lazy encrypt code.

目前该特性尚不稳定,会导致一些网页无法访问(不是速度慢,是有些tls连接因为检测tls措施不够好的问题, 给搞丢了,导致加载不出来,需要进一步调试;实测一般刷新一下页面就能加载出来,也不知道什么情况)
This commit is contained in:
hahahrfool
2022-03-11 19:57:12 +08:00
parent e558ba21cf
commit 6ac929a397
4 changed files with 166 additions and 137 deletions

View File

@@ -10,6 +10,10 @@ verysimple项目大大简化了 转发机制,能提高运行速度。
在本项目里 制定 并实现了 vless v1标准。
在最新代码里,我还实现了 双向 tls lazy encrypt, 即另一种 xtls的 splice的实现。因为是双向的而xtls的splice是单向所以 理论上 tls lazy encrypt 比xtls 还快。
目前该特性尚不稳定会导致一些网页无法访问不是速度慢是有些tls连接因为检测tls措施不够好的问题 给搞丢了,导致加载不出来,需要进一步调试;实测一般刷新一下页面就能加载出来,也不知道什么情况),运行时可以用 -lazy 参数打开(服务端客户端都要打开),然后可以用 -pdd 参数 打印 tls 探测输出
安装方式:
@@ -101,15 +105,6 @@ verysimple 继承 v2simple的一个优点就是服务端的配置也可以用
对于功能的golang test请使用 `go test ./...` 命令。如果要详细的打印出test的过程可以添加 -v 参数
## 腾讯视频问题
如果用此代理打开腾讯视频网页的话会发现视频加载不出来。bilibili是没问题的。而且按理说和udp没关系因为websocket还是基于tcp的。
经过我测试, verysimple 返回的错误是:
```
failed to dail 'apd-9d8f8b192cbf63303ebad8d58b51293f.v.sm:443': dial tcp: lookup apd-9d8f8b192cbf63303ebad8d58b51293f.v.sm: no such host
```
此问题有待考证解决。也不知道是不是只有我自己有这个问题。。
## 交叉编译

79
main.go
View File

@@ -33,13 +33,13 @@ var (
conf *Config
directClient proxy.Client
tls_lazy_encryptPtr = flag.Bool("lazy", false, "tls lazy encrypt (splice)")
tls_lazy_encrypt bool
tls_lazy_encrypt bool
)
func init() {
directClient, _ = proxy.ClientFromURL("direct://")
flag.BoolVar(&tls_lazy_encrypt, "lazy", false, "tls lazy encrypt (splice)")
}
func printVersion() {
@@ -78,7 +78,6 @@ func main() {
printVersion()
flag.Parse()
tls_lazy_encrypt = *tls_lazy_encryptPtr
var err error
@@ -109,7 +108,7 @@ func main() {
log.Println(localServer.Name(), "is listening TCP on ", localServer.AddrStr())
// 后台运行主代码而main函数只监听中断信号
// TODO: 未来main函数可以推出 交互模式,未来推出动态增删用户、查询流量等功能时就有用
// TODO: 未来main函数可以推出 交互模式,未来推出动态增删用户、查询流量等功能时就有用
go func() {
for {
lc, err := listener.Accept()
@@ -142,7 +141,7 @@ func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Clie
baseLocalConn := thisLocalConnectionInstance
log.Println("got new", thisLocalConnectionInstance.RemoteAddr().String())
//log.Println("got new", thisLocalConnectionInstance.RemoteAddr().String())
var err error
@@ -166,26 +165,24 @@ func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Clie
wlc, targetAddr, err := localServer.Handshake(thisLocalConnectionInstance)
if err != nil {
log.Printf("failed in handshake from %v: %v", localServer.AddrStr(), err)
log.Println("failed in handshake from", localServer.AddrStr(), err)
thisLocalConnectionInstance.Close()
return
}
// 我们在客户端 lazy_encrypt 探测时读取socks5 传来的信息因为这个和要发送到tls的信息一模一样的所以就不需要等包上vless、tls后再判断了, 直接解包 socks5进行判断
// 我们在客户端 lazy_encrypt 探测时读取socks5 传来的信息因为这个和要发送到tls的信息一模一样的所以就不需要等包上vless、tls后再判断了, 直接解包 socks5进行判断
//
// 而在服务端探测时,因为包了 tls所以要在tls解包后, vless 解包后,再进行判断;
// 所以总之都是要在 localServer判断 wlc,只不过理由不一样
var thecc *tlsLayer.CopyConn
// 而在服务端探测时,因为 客户端传来的连接 包了 tls所以要在tls解包后, vless 解包后,再进行判断;
// 所以总之都是要在 localServer判断 wlc;总之,含义就是,去检索“用户承载数据”的来源
if tls_lazy_encrypt {
thecc = tlsLayer.NewDetectConn(baseLocalConn, wlc)
wlc = tlsLayer.NewDetectConn(baseLocalConn, wlc, tlsLayer.OnlyTest)
wlc = thecc
//clientConn = cc
}
//如果目标是udp则要分情况讨论
if targetAddr.IsUDP {
switch localServer.Name() {
@@ -250,9 +247,9 @@ func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Clie
var client proxy.Client
client = remoteClient
client = remoteClient //如果加了白名单等过滤方式则client可能会等于direct等再说
log.Printf("%s want to dial %s", client.Name(), targetAddr.UrlString())
log.Println(client.Name(), " want to dial ", targetAddr.UrlString())
// 如果目标是udp 则我们单独处理。这里为了简便,不考虑多级串联的关系,直接只考虑 直接转发到direct
// 根据 vless_v1的讨论vless_v1 的udp转发的 通信方式 也是与tcp连接类似的分离信道方式
@@ -338,7 +335,7 @@ func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Clie
*/
if tls_lazy_encrypt {
tryRaw(wrc, wlc, client.IsUseTLS())
tryRawCopy(wrc, wlc, client.IsUseTLS())
return
}
@@ -347,15 +344,17 @@ func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Clie
}
// tryRawCopy 尝试能否直接对拷,对拷 直接使用 原始 TCPConn
//和 xtls的splice 含义相同
func tryRaw(wrc, wlc io.ReadWriter, isclient bool) {
func tryRawCopy(wrc, wlc io.ReadWriter, isclient bool) {
//如果用了 lazy_encrypt 则不直接利用Copy因为有两个阶段判断阶段和直连阶段
// 在判断阶段,因为还没确定是否是 tls所以是要继续用tls加密的
// 而直连阶段,只要能让 Copy使用 ReadFrom, 就能一步一步最终使用splice
// 而直连阶段,只要能让 Copy使用 net.TCPConn的 ReadFrom, 就不用管了, golang最终就会使用splice
// 之所以可以对拷直连,是因为无论是 socks5 还是vless只是在最开始的部分 加了目标头后面的所有tcp连接都是直接传输的数据就是说一开始握手什么的是不能直接对拷的等到后期就可以了
// 而且之所以能对拷,还有个原因就是,远程服务器 与 客户端 总是源源不断地 为 我们的 原始 TCP 连接 提供数据,我们只是一个中间商而已,左手倒右手
//首先判断我们的wlc*tlsLayer.CopyConn) 是否得出来 IsTLS
wlccc := wlc.(*tlsLayer.CopyConn)
wlccc := wlc.(*tlsLayer.DetectConn)
wlccc_raw := wlccc.RawConn
var rawWRC *net.TCPConn
@@ -363,21 +362,23 @@ func tryRaw(wrc, wlc io.ReadWriter, isclient bool) {
//wrc 有两种情况如果客户端那就是tls服务端那就是direct。我们不讨论服务端 处于中间层的情况
if isclient {
// 不过实际上 wrc 是 vless的 UserConn 而UserConn的底层连接才是TLS
// 不过实际上客户端 wrc 是 vless的 UserConn 而UserConn的底层连接才是TLS
// 很明显目前我们只支持vless所以才这么操作以后再说。
wrcVless := wrc.(*vless.UserConn)
tlsConn := wrcVless.Conn.(*tlsLayer.Conn)
rawWRC = tlsConn.GetRaw()
} else {
rawWRC = wrc.(*net.TCPConn)
rawWRC = wrc.(*net.TCPConn) //因为是direct
}
if rawWRC == nil {
log.Println("splice fail reason 3 ")
io.Copy(wrc, wlc)
log.Println("splice fail reason 1 ")
//退化回原始状态
go io.Copy(wrc, wlc)
io.Copy(wlc, wrc)
return
}
@@ -385,6 +386,7 @@ func tryRaw(wrc, wlc io.ReadWriter, isclient bool) {
//从 wlccc 读取,向 wrc 写入
// 此时如果ReadFrom那就是 wrc.ReadFrom(wlccc)
//wrc 要实现 ReaderFrom才行, 或者把最底层TCPConn暴露然后 wlccc 也要把最底层 TCPConn暴露出来
// 这里就直接采取底层方式
p := common.GetPacket()
isgood := false
@@ -396,20 +398,24 @@ func tryRaw(wrc, wlc io.ReadWriter, isclient bool) {
n, err := wlccc.Read(p)
if err != nil {
break
}
wrc.Write(p[:n])
if wlccc.R.IsTls && wlccc.RawConn != nil {
isgood = true
}
}
common.PutPacket(p)
if isgood {
log.Println("成功SpliceRead 方向1")
rawWRC.ReadFrom(wlccc_raw)
if tlsLayer.PDD {
log.Println("成功SpliceRead 方向1")
num, _ := rawWRC.ReadFrom(wlccc_raw)
log.Println("SpliceRead 方向1 读完,", num)
} else {
rawWRC.ReadFrom(wlccc_raw)
}
}
}()
@@ -424,17 +430,22 @@ func tryRaw(wrc, wlc io.ReadWriter, isclient bool) {
}
n, err := wrc.Read(p)
if err != nil {
break
}
wlccc.Write(p[:n])
if wlccc.W.IsTls && wlccc.RawConn != nil {
isgood2 = true
}
}
common.PutPacket(p)
if isgood2 {
log.Println("成功SpliceRead 方向2")
wlccc_raw.ReadFrom(rawWRC)
if tlsLayer.PDD {
log.Println("成功SpliceRead 方向2")
num, _ := wlccc_raw.ReadFrom(rawWRC)
log.Println("SpliceRead 方向2 读完,", num)
} else {
wlccc_raw.ReadFrom(rawWRC)
}
}
}

View File

@@ -2,7 +2,6 @@ package tlsLayer
import (
"crypto/tls"
"log"
"net"
"unsafe"
)
@@ -21,7 +20,7 @@ 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))
//log.Println("成功获取到 *net.TCPConn", rc.conn.(*net.TCPConn)) //经测试,是毫无问题的
return rc.conn.(*net.TCPConn)
}
}

View File

@@ -1,6 +1,7 @@
package tlsLayer
import (
"flag"
"io"
"log"
"net"
@@ -8,40 +9,55 @@ import (
"strings"
)
type CopyConn struct {
net.Conn //这个 Conn本包中不会用到只是为了能让CopyConn支持 net.Conn
io.ReadWriter
W *DetectWriter
R *DetectReader
var PDD bool //print tls detect detail
var OnlyTest bool
func init() {
log.SetOutput(os.Stdout) //主要是日志太多如果都能直接用管道放到文件中就好了默认不是Stdout所以优点尴尬操作麻烦点
flag.BoolVar(&PDD, "pdd", false, "print tls detect detail")
flag.BoolVar(&OnlyTest, "ot", false, "only detect tls, doesn't actually mark tls")
RawConn *net.TCPConn // 这个是为了让外界能够直接拿到底层的连接
}
func (cc *CopyConn) Read(p []byte) (int, error) {
// 用于 探测 承载数据是否使用了tls
// 可以参考 https://www.baeldung.com/linux/tcpdump-capture-ssl-handshake
type DetectConn struct {
net.Conn //这个 Conn本DetectConn 中不会用到只是为了能让CopyConn支持 net.Conn
W *DetectWriter
R *DetectReader
RawConn *net.TCPConn // 这个是为了让外界能够直接拿到底层的连接
onlyTest bool //如果此开关打开则不会去真实修改IsTLS而只是去过滤所有的包。
}
func (cc *DetectConn) Read(p []byte) (int, error) {
return cc.R.Read(p)
}
func (cc *CopyConn) Write(p []byte) (int, error) {
func (cc *DetectConn) Write(p []byte) (int, error) {
return cc.W.Write(p)
}
func (cc *CopyConn) ReadFrom(r io.Reader) (int64, error) {
//这个暂时没用到,先留着
func (cc *DetectConn) 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 {
//可选两个参数传入优先使用rw 为nil的话 再使用oldConn作为 底层ReadWrite的 主体
func NewDetectConn(oldConn net.Conn, rw io.ReadWriter, onlyTest bool) *DetectConn {
var validOne io.ReadWriter = rw
if rw == nil {
validOne = oldConn
}
cc := &CopyConn{
Conn: oldConn,
ReadWriter: rw,
cc := &DetectConn{
Conn: oldConn,
W: &DetectWriter{
Writer: validOne,
},
@@ -50,6 +66,9 @@ func NewDetectConn(oldConn net.Conn, rw io.ReadWriter) *CopyConn {
},
}
cc.W.onlyTest = onlyTest
cc.R.onlyTest = onlyTest
if netConn := oldConn.(*net.TCPConn); netConn != nil {
//log.Println("get netConn!") // 如果是客户端的socks5网页浏览的话这里一定能转成 TCPConn
cc.RawConn = netConn
@@ -58,81 +77,107 @@ func NewDetectConn(oldConn net.Conn, rw io.ReadWriter) *CopyConn {
return cc
}
// DetectReader 对每个Read的数据进行分析判断是否是tls流量
type DetectReader struct {
io.Reader
IsTls bool
type ComDetectStruct struct {
IsTls bool
onlyTest bool
packetCount int
}
func init() {
log.SetOutput(os.Stdout)
// DetectReader 对每个Read的数据进行分析判断是否是tls流量
type DetectReader struct {
io.Reader
ComDetectStruct
}
func commonDetect(dr *ComDetectStruct, p []byte, isRead bool) {
dr.packetCount++
p0 := p[0]
p1 := p[1]
p2 := p[2]
/*
if p0 == 22 || p0 == 23 || p0 == 20 || (p0 == 21 && n == 31) {
//客户端Read 时 少数情况首部会有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 {
if PDD {
str := "W"
if isRead {
str = "R"
}
log.Println(str, "got TLS!")
}
if !dr.onlyTest {
dr.IsTls = true
}
return
}
n := len(p)
// 打印没过滤到的数据
if PDD || dr.onlyTest {
min := 10
if n < 10 {
min = n
}
str := "Write,"
if isRead {
str = "Read,"
}
log.Println(" ======== ", str, n, p[:min])
}
}
// 总之,我们在客户端的 Read 操作,就是 我们试图使用 Read 读取客户的请求,然后试图发往 外界
// 所以在socks5后面 使用的这个 Read是读取客户端发送的请求比如 clienthello之类
// 服务端的 Read 操作,是把 远程目标服务器 发来的 数据 发送到 客户端,比如 serverhello 之类
//
// 我们直接判断23 3 3字节然后直接推定tls不管三七二十一判断错误就错误吧快就得了
func (dr *DetectReader) Read(p []byte) (n int, err error) {
n, err = dr.Reader.Read(p)
if dr.IsTls {
if !dr.onlyTest && 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
if !dr.onlyTest && dr.packetCount > 8 {
//都8个包了还没断定tls直接推定不是
return
}
log.Println(" ======== Read,", n, err, p[:min], string(p[:min]))
if n > 3 {
commonDetect(&dr.ComDetectStruct, p, true)
}
return
}
// DetectReader 对每个Read的数据进行分析判断是否是tls流量
type DetectWriter struct {
io.Writer
IsTls bool
packetCount int
ComDetectStruct
}
//我发现,数据基本就是 23 3 3 22 3 322 3 1 20 3 3
@@ -144,6 +189,7 @@ type DetectWriter struct {
//
// 总之,我们在客户端的 Write 操作,就是 外界试图使用我们的 Write 写入数据
// 所以在socks5后面 使用的这个Write应该是把 服务端的响应 写回 socks5比如 serverhello之类
// 服务端的 Write操作是把客户端发来的 数据 发送到远程目标服务器,比如 clienthello之类
//
// 根据之前讨论23 3 3 就是 数据部分,TLSCiphertext
// https://halfrost.com/https_record_layer/
@@ -151,45 +197,23 @@ type DetectWriter struct {
// 进入direct模式; 目前从简,连握手包都不检测!测错就测错!
func (dr *DetectWriter) Write(p []byte) (n int, err error) {
n, err = dr.Writer.Write(p)
if dr.IsTls {
if !dr.onlyTest && dr.IsTls {
return
}
if dr.packetCount > 8 {
if err != nil {
eStr := err.Error()
if strings.Contains(eStr, "use of closed") || strings.Contains(eStr, "reset by peer") || strings.Contains(eStr, "EOF") {
return
}
}
if !dr.onlyTest && 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
}
commonDetect(&dr.ComDetectStruct, p, false)
}
min := 10
if n < 10 {
min = n
}
log.Println(" ======== Write,", n, err, p[:min], string(p[:min]))
return
}