mirror of
https://github.com/p4gefau1t/trojan-go.git
synced 2026-10-19 16:15:38 +00:00
fix tls alpn problem, randomize timeout
This commit is contained in:
@@ -2,6 +2,7 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
name: release-build
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Trojan-Go
|
||||
|
||||
使用Golang实现的完整Trojan代理,和Trojan协议以及原版的配置文件格式兼容。安全,高效,轻巧,易用。
|
||||
使用Go实现的完整Trojan代理,和Trojan协议以及Trojan-GFW版本的配置文件格式兼容。安全,高效,轻巧,易用。
|
||||
|
||||
支持使用[多路复用](#多路复用)提升并发性能,使用[路由模块](#路由模块)实现国内直连。
|
||||
|
||||
支持CDN[流量中转](#Websocket)(基于WebSocket over TLS/SSL)
|
||||
支持[CDN流量中转](#Websocket)(基于WebSocket over TLS/SSL)
|
||||
|
||||
支持使用ACME协议从Let's Encrypt[自动申请和更新](#证书申请)HTTPS证书,只需提供域名和邮箱。
|
||||
|
||||
@@ -28,7 +28,7 @@ Trojan-Go支持并且兼容原版Trojan-GFW的绝大多数功能,包括但不
|
||||
|
||||
- TCP性能方面的选项,如TCP Fast Open,端口复用等
|
||||
|
||||
同时,Trojan-Go还有更多高效易用的功能特性:
|
||||
同时,Trojan-Go还扩展了更多高效易用的功能特性:
|
||||
|
||||
- 简易模式,快速部署使用
|
||||
|
||||
@@ -166,7 +166,7 @@ Linux下,绑定80和443端口需要root权限,因此你需要使用sudo执
|
||||
如果证书过期了,使用
|
||||
|
||||
```
|
||||
sudo ./trojan-go -cert renew
|
||||
sudo ./trojan-go -autocert renew
|
||||
```
|
||||
|
||||
更新证书,确保上面提到的四个文件在trojan-go所在目录,运行后trojan-go将自动更新证书文件。
|
||||
@@ -187,7 +187,7 @@ Trojan-Go支持使用TLS+Websocket承载Trojan协议,使得利用CDN进行流
|
||||
}
|
||||
```
|
||||
|
||||
完整的选项说明参见[Github Pages](https://p4gefau1t.github.io/trojan-go)。
|
||||
完整的选项说明参见[Trojan-Go 文档](https://p4gefau1t.github.io/trojan-go)。
|
||||
|
||||
服务端可以省略```hostname```, 但是服务器和客户端的```path```必须相同。服务器开启Websocket支持后可以同时支持Websocket和一般Trojan流量,未配置Websocket选项的客户端依然可以正常使用。
|
||||
|
||||
@@ -211,7 +211,7 @@ Trojan-Go支持多路复用([smux](https://github.com/xtaci/smux))。通过使
|
||||
}
|
||||
```
|
||||
|
||||
完整的选项说明参见[Github Pages](https://p4gefau1t.github.io/trojan-go)。
|
||||
完整的选项说明参见[Trojan-Go 文档](https://p4gefau1t.github.io/trojan-go)。
|
||||
|
||||
你只需要设置客户端的配置文件即可,服务端会自动检测是否启用多路复用并提供支持。
|
||||
|
||||
@@ -251,7 +251,7 @@ Trojan-Go的客户端内建一个简单实用的路由模块用以方便实现
|
||||
|
||||
其中```bypass```,```block```, ```proxy```字段中填入相应的列表文件或者geo数据库tag。列表文件每行是一个域名或者IP地址段(CIDR)。geo数据库geoip和geosite为IP数据库和域名数据库。一旦匹配,则执行相应策略。
|
||||
|
||||
完整的选项说明参见[Github Pages](https://p4gefau1t.github.io/trojan-go)。
|
||||
完整的选项说明参见[Trojan-Go 文档](https://p4gefau1t.github.io/trojan-go)。
|
||||
|
||||
下面是一个实现国内直连的选项,它将绕过中国大陆IP地址,中国大陆域名,以及内网IP等保留的私有IP地址,直接连接远端而不通过隧道代理。
|
||||
|
||||
@@ -270,7 +270,7 @@ Trojan-Go的客户端内建一个简单实用的路由模块用以方便实现
|
||||
|
||||
## 构建
|
||||
|
||||
确保你的Golang版本 >= 1.14,推荐使用snap安装Golang保持与上游同步。
|
||||
确保你的Go版本 >= 1.14,推荐使用snap安装Go保持与上游同步。
|
||||
|
||||
```
|
||||
git clone https://github.com/p4gefau1t/trojan-go.git
|
||||
@@ -278,7 +278,7 @@ cd trojan-go
|
||||
go build
|
||||
```
|
||||
|
||||
Golang支持通过设置环境变量进行交叉编译,例如
|
||||
Go支持通过设置环境变量进行交叉编译,例如
|
||||
|
||||
```
|
||||
CGO_ENABLE=0 GOOS=windows GOARCH=amd64 go build -o trojan-go.exe
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
|
||||
"github.com/p4gefau1t/trojan-go/common"
|
||||
"github.com/p4gefau1t/trojan-go/log"
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
@@ -205,25 +204,6 @@ func loadClientConfig(config *GlobalConfig) error {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
||||
//tls settings
|
||||
if config.TLS.Fingerprint != "" && config.TLS.Fingerprint != "auto" {
|
||||
table := map[string]*utls.ClientHelloID{
|
||||
"chrome": &utls.HelloChrome_Auto,
|
||||
"firefox": &utls.HelloFirefox_Auto,
|
||||
"ios": &utls.HelloIOS_Auto,
|
||||
"randomized": &utls.HelloRandomized,
|
||||
"randomized_alpn": &utls.HelloRandomizedALPN,
|
||||
"randomized_no_alpn": &utls.HelloRandomizedNoALPN,
|
||||
}
|
||||
id, found := table[config.TLS.Fingerprint]
|
||||
if found {
|
||||
log.Debug("tls fingerprint loaded:", id.Str())
|
||||
config.TLS.ClientHelloID = id
|
||||
} else {
|
||||
log.Warn("invalid tls fingerprint:", config.TLS.Fingerprint, ", using default fingerprint")
|
||||
}
|
||||
}
|
||||
|
||||
if config.TLS.SNI == "" {
|
||||
log.Warn("SNI is unspecified, using remote_addr as SNI")
|
||||
config.TLS.SNI = config.RemoteHost
|
||||
@@ -333,10 +313,6 @@ func ParseJSON(data []byte) (*GlobalConfig, error) {
|
||||
config.TLS.VerifyHostname = true
|
||||
config.TLS.SessionTicket = true
|
||||
config.TLS.ReuseSession = true
|
||||
config.TLS.ALPN = []string{
|
||||
"http/1.1",
|
||||
"h2",
|
||||
}
|
||||
config.Mux.IdleTimeout = 60
|
||||
config.Mux.Concurrency = 8
|
||||
config.MySQL.CheckRate = 60
|
||||
|
||||
@@ -8,7 +8,7 @@ weight: 10
|
||||
|
||||
这里是Trojan-Go的文档,你可以在左侧的导航栏中找到一些使用技巧,以及完整的配置文件说明。
|
||||
|
||||
Trojan-Go是使用Golang实现的完整的Trojan代理,和Trojan协议以及原版的配置文件格式兼容。
|
||||
Trojan-Go是使用Go语言实现的完整的Trojan代理,和Trojan协议以及原版的配置文件格式兼容。
|
||||
|
||||
Trojan-Go的的首要目标是保障传输安全性和隐蔽性。在此前提下,尽可能提升传输性能和易用性。
|
||||
|
||||
|
||||
@@ -95,9 +95,10 @@ sudo ./trojan-go -autocert renew
|
||||
"remote_addr": "your_awesome_server",
|
||||
"remote_port": 443,
|
||||
"password": [
|
||||
"your_awesome_password"
|
||||
"your_awesome_password"
|
||||
],
|
||||
"ssl": {
|
||||
"fingerprint": "firefox",
|
||||
"sni": "your_domain_name"
|
||||
}
|
||||
}
|
||||
@@ -107,4 +108,6 @@ sudo ./trojan-go -autocert renew
|
||||
|
||||
如果你在```remote_addr```中填写的是域名,```sni```可以省略。```sni```字段应当填写你申请证书的对应域名,或者你自己签发证书时证书的Common Name,而且必须一致。注意,```sni```字段目前的在TLS协议中是规定**明文传送**的(目的是使服务器提供相应证书),所以不要填写类似google.com等已经被封锁的域名,否则很有可能导致你的服务器也被封锁。
|
||||
|
||||
更多关于配置文件的信息,可以在左侧导航栏中找到相应介绍。
|
||||
```fingerprint```将设置Trojan-Go伪造Firefox浏览器的TLS请求指纹,使得Trojan-Go的流量混杂在正常的HTTPS流量中无法被识别。还可以设置为```ios```,```chrome```等。
|
||||
|
||||
更多关于配置文件的信息,可以在左侧导航栏中找到相应介绍。
|
||||
|
||||
@@ -47,10 +47,7 @@ weight: 30
|
||||
"curves": "",
|
||||
"prefer_server_cipher": false,
|
||||
"sni": "",
|
||||
"alpn": [
|
||||
"http/1.1",
|
||||
"h2"
|
||||
]
|
||||
"alpn": [],
|
||||
"session_ticket": true,
|
||||
"reuse_session": true,
|
||||
"plain_http_response": "",
|
||||
@@ -128,11 +125,11 @@ weight: 30
|
||||
|
||||
```prefer_server_cipher```客户端是否偏好选择服务端在协商中提供的密码学套件。
|
||||
|
||||
```cipher```和```cipher13```指TLS使用的密码学套件。只有在你明确知道自己在做什么的情况下,才应该去填写此项以修改trojan-go使用的TLS密码学套件。**正常情况下,你应该将其留空或者不填**,trojan-go会根据当前硬件平台以及远端的情况,自动选择最合适的加密算法以提升性能和安全性。如果需要填写,密码学套件名用分号(":")分隔。Golang的TLS库中弃用了TLS1.2中不安全的密码学套件,并完全支持TLS1.3。默认情况下,trojan-go将优先使用更安全的TLS1.3
|
||||
```cipher```和```cipher13```指TLS使用的密码学套件。只有在你明确知道自己在做什么的情况下,才应该去填写此项以修改trojan-go使用的TLS密码学套件。**正常情况下,你应该将其留空或者不填**,trojan-go会根据当前硬件平台以及远端的情况,自动选择最合适的加密算法以提升性能和安全性。如果需要填写,密码学套件名用分号(":")分隔。Go的TLS库中弃用了TLS1.2中不安全的密码学套件,并完全支持TLS1.3。默认情况下,trojan-go将优先使用更安全的TLS1.3
|
||||
|
||||
```curves```指定TLS在ECDHE中偏好使用的椭圆曲线。只有你明确知道自己在做什么的情况下,才应该填写此项。曲线名称用分号(":")分隔。
|
||||
|
||||
```fingerprint```用于指定TLS Client Hello指纹伪造类型。trojan-go使用[utls](https://github.com/refraction-networking/utls)进行指纹伪造,避免遭到针对golang的TLS库的识别。合法的值有
|
||||
```fingerprint```用于指定TLS Client Hello指纹伪造类型,以抵抗GFW对于TLS Client Hello指纹的特征识别和阻断。trojan-go使用[utls](https://github.com/refraction-networking/utls)进行指纹伪造。合法的值有
|
||||
|
||||
- ""(空),默认,不使用指纹伪造
|
||||
|
||||
@@ -146,9 +143,9 @@ weight: 30
|
||||
|
||||
- "randomized",随机指纹
|
||||
|
||||
一旦指纹的值被设置,```cipher```,```curves```,```alpn```等可能影响指纹的字段将使用该指纹的特定设置覆写。设置该选项有可能导致与服务器密钥协商失败,使用auto选项将自动尝试所有指纹并选出合适的一项。
|
||||
一旦指纹的值被设置,```cipher```,```curves```,```alpn```,```session_ticket```等有可能影响指纹的字段将使用该指纹的特定设置覆写。设置该选项有可能导致与服务器密钥协商失败,使用auto选项将自动尝试所有指纹并选出合适的一项。
|
||||
|
||||
```plain_http_response```指定了当TLS握手失败时,明文发送的原始数据(原始TCP数据),这个字段填入该文件路径。推荐使用```fallback_port```而不是该字段。
|
||||
```plain_http_response```指定了当TLS握手失败时,明文发送的原始数据(原始TCP数据)。这个字段填入该文件路径。推荐使用```fallback_port```而不是该字段。
|
||||
|
||||
```fallback_port```指TLS握手失败时,trojan-go将该连接代理到该端口上。这是trojan-go的特性,以便更好地隐蔽Trojan服务器,抵抗GFW的主动检测,使得服务器的443端口在遭遇非TLS协议的探测时,行为与正常服务器完全一致。当服务器接受了一个连接但无法进行TLS握手时,如果```fallback_port```不为空,则流量将会被代理至remote_addr:fallback_port。例如,你可以在本地使用nginx开启一个https服务,当你的服务器443端口被非TLS协议请求时(比如http请求),trojan-go将代理至本地https服务器,nginx将使用http协议明文返回一个400 Bad Request页面。你可以通过使用浏览器访问 http://your_domain_name.com:443 进行验证。
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ draft: false
|
||||
weight: 10
|
||||
---
|
||||
|
||||
编译需要Golang版本号高于0.14.x,请在编译前确认你的编译器版本。推荐使用snap安装和更新go。
|
||||
编译需要Go版本号高于0.14.x,请在编译前确认你的编译器版本。推荐使用snap安装和更新go。
|
||||
|
||||
编译过程非常简单
|
||||
|
||||
|
||||
@@ -176,8 +176,13 @@ func ParsePort(addr net.Addr) (uint16, error) {
|
||||
|
||||
var timeout time.Duration
|
||||
|
||||
func RandomizedTimeout(conn net.Conn) {
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
func GetRandomTimeoutDuration() time.Duration {
|
||||
offset := time.Duration(rand.Intn(3000)) * time.Millisecond
|
||||
return timeout + offset
|
||||
}
|
||||
|
||||
func SetRandomizedTimeout(conn net.Conn) {
|
||||
conn.SetDeadline(time.Now().Add(GetRandomTimeoutDuration()))
|
||||
}
|
||||
|
||||
func CancelTimeout(conn net.Conn) {
|
||||
|
||||
@@ -190,9 +190,9 @@ func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.Global
|
||||
defer rewindConn.R.StopBuffering()
|
||||
|
||||
bufrw := bufio.NewReadWriter(bufio.NewReader(rewindConn), bufio.NewWriter(rewindConn))
|
||||
httpRequest, obfErr := http.ReadRequest(bufrw.Reader)
|
||||
if obfErr != nil {
|
||||
log.Debug(common.NewError("not a http request:").Base(obfErr))
|
||||
httpRequest, err := http.ReadRequest(bufrw.Reader)
|
||||
if err != nil {
|
||||
log.Debug(common.NewError("not a http request:").Base(err))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.Global
|
||||
shadowMan.CommitScapegoat(&shadow.Scapegoat{
|
||||
Conn: rewindConn,
|
||||
ShadowAddress: config.RemoteAddress,
|
||||
Info: "not a valid http upgrade request from " + conn.RemoteAddr().String(),
|
||||
Info: "invalid http upgrade request from " + conn.RemoteAddr().String(),
|
||||
})
|
||||
return nil, common.NewError("invalid ws url or hostname")
|
||||
}
|
||||
@@ -215,7 +215,7 @@ func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.Global
|
||||
rewindConn.R.SetBufferSize(0)
|
||||
url := "wss://" + config.Websocket.HostName + config.Websocket.Path
|
||||
origin := "https://" + config.Websocket.HostName
|
||||
wsConfig, obfErr := websocket.NewConfig(url, origin)
|
||||
wsConfig, err := websocket.NewConfig(url, origin)
|
||||
|
||||
handshaked := make(chan struct{})
|
||||
|
||||
@@ -253,7 +253,7 @@ func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.Global
|
||||
return nil, common.NewError("failed to perform websocket handshake")
|
||||
}
|
||||
|
||||
//use ws to transport
|
||||
//use ws to transfer
|
||||
var transport net.Conn
|
||||
rewindConn = common.NewRewindConn(wsConn)
|
||||
transport = rewindConn
|
||||
@@ -266,19 +266,19 @@ func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.Global
|
||||
log.Debug("ws obfs")
|
||||
|
||||
//deadline for sending the iv and hash
|
||||
rewindConn.SetDeadline(time.Now().Add(protocol.TCPTimeout))
|
||||
transport, obfErr = NewInboundObfReadWriteCloser(config.Websocket.ObfuscationKey, transport)
|
||||
rewindConn.SetDeadline(time.Time{})
|
||||
protocol.SetRandomizedTimeout(rewindConn)
|
||||
transport, err = NewInboundObfReadWriteCloser(config.Websocket.ObfuscationKey, transport)
|
||||
protocol.CancelTimeout(rewindConn)
|
||||
|
||||
if obfErr != nil {
|
||||
if err != nil {
|
||||
rewindConn.R.Rewind()
|
||||
//proxy this to our own ws server
|
||||
obfErr = common.NewError("remote websocket conn:" + conn.RemoteAddr().String() + "didn't send any valid iv/hash").Base(obfErr)
|
||||
err = common.NewError("remote websocket " + conn.RemoteAddr().String() + "didn't send any valid iv").Base(err)
|
||||
goat, err := getWebsocketScapegoat(
|
||||
config,
|
||||
url,
|
||||
origin,
|
||||
obfErr.Error(),
|
||||
err.Error(),
|
||||
rewindConn,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -287,7 +287,7 @@ func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.Global
|
||||
} else {
|
||||
shadowMan.CommitScapegoat(goat)
|
||||
}
|
||||
return nil, obfErr
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !config.Websocket.DoubleTLS {
|
||||
@@ -301,6 +301,7 @@ func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.Global
|
||||
SessionTicketsDisabled: !config.TLS.SessionTicket,
|
||||
}
|
||||
tlsConn := tls.Server(transport, tlsConfig)
|
||||
protocol.SetRandomizedTimeout(tlsConn)
|
||||
if tlsErr := tlsConn.Handshake(); tlsErr != nil {
|
||||
rewindConn.R.Rewind()
|
||||
//proxy this to our own ws server
|
||||
@@ -320,6 +321,7 @@ func NewInboundWebsocket(ctx context.Context, conn net.Conn, config *conf.Global
|
||||
}
|
||||
return nil, tlsErr
|
||||
}
|
||||
protocol.CancelTimeout(tlsConn)
|
||||
rewindConn.R.SetBufferSize(0)
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ func (m *MuxManager) newMuxClient() (*muxClientInfo, error) {
|
||||
}
|
||||
conn, err := trojan.NewOutboundConnSession(req, rwc, m.config)
|
||||
if err != nil {
|
||||
rwc.Close()
|
||||
log.Error(common.NewError("failed to dial tls tunnel").Base(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+124
-124
@@ -6,107 +6,25 @@ import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/p4gefau1t/trojan-go/common"
|
||||
"github.com/p4gefau1t/trojan-go/conf"
|
||||
"github.com/p4gefau1t/trojan-go/log"
|
||||
"github.com/p4gefau1t/trojan-go/protocol"
|
||||
"github.com/p4gefau1t/trojan-go/protocol/trojan"
|
||||
"github.com/p4gefau1t/trojan-go/sockopt"
|
||||
utls "github.com/refraction-networking/utls"
|
||||
)
|
||||
|
||||
type Roller struct {
|
||||
HelloIDs []utls.ClientHelloID
|
||||
HelloIDMu sync.Mutex
|
||||
WorkingHelloID *utls.ClientHelloID
|
||||
TCPDialTimeout time.Duration
|
||||
TLSHandshakeTimeout time.Duration
|
||||
TLSConfig *utls.Config
|
||||
}
|
||||
|
||||
// NewRoller creates Roller object with default range of HelloIDs to cycle through until a
|
||||
// working/unblocked one is found.
|
||||
func NewRoller(config *utls.Config) *Roller {
|
||||
tcpDialTimeoutInc := rand.Intn(14)
|
||||
tcpDialTimeoutInc = 7 + tcpDialTimeoutInc
|
||||
|
||||
tlsHandshakeTimeoutInc := rand.Intn(20)
|
||||
tlsHandshakeTimeoutInc = 11 + tlsHandshakeTimeoutInc
|
||||
|
||||
return &Roller{
|
||||
HelloIDs: []utls.ClientHelloID{
|
||||
utls.HelloChrome_Auto,
|
||||
utls.HelloFirefox_Auto,
|
||||
utls.HelloIOS_Auto,
|
||||
utls.HelloRandomized,
|
||||
},
|
||||
TCPDialTimeout: time.Second * time.Duration(tcpDialTimeoutInc),
|
||||
TLSHandshakeTimeout: time.Second * time.Duration(tlsHandshakeTimeoutInc),
|
||||
TLSConfig: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Roller) Dial(network, addr, serverName string) (*utls.UConn, error) {
|
||||
helloIDs := make([]utls.ClientHelloID, len(c.HelloIDs))
|
||||
copy(helloIDs, c.HelloIDs)
|
||||
rand.Shuffle(len(c.HelloIDs), func(i, j int) {
|
||||
helloIDs[i], helloIDs[j] = helloIDs[j], helloIDs[i]
|
||||
})
|
||||
|
||||
c.HelloIDMu.Lock()
|
||||
workingHelloID := c.WorkingHelloID // keep using same helloID, if it works
|
||||
c.HelloIDMu.Unlock()
|
||||
if workingHelloID != nil {
|
||||
helloIDFound := false
|
||||
for i, ID := range helloIDs {
|
||||
if ID == *workingHelloID {
|
||||
helloIDs[i] = helloIDs[0]
|
||||
helloIDs[0] = *workingHelloID // push working hello ID first
|
||||
helloIDFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !helloIDFound {
|
||||
helloIDs = append([]utls.ClientHelloID{*workingHelloID}, helloIDs...)
|
||||
}
|
||||
}
|
||||
|
||||
var tcpConn net.Conn
|
||||
var err error
|
||||
for _, helloID := range helloIDs {
|
||||
tcpConn, err = net.DialTimeout(network, addr, c.TCPDialTimeout)
|
||||
if err != nil {
|
||||
return nil, err // on tcp Dial failure return with error right away
|
||||
}
|
||||
|
||||
client := utls.UClient(tcpConn, c.TLSConfig, helloID)
|
||||
client.SetSNI(serverName)
|
||||
client.SetDeadline(time.Now().Add(c.TLSHandshakeTimeout))
|
||||
err = client.Handshake()
|
||||
client.SetDeadline(time.Time{}) // unset timeout
|
||||
if err != nil {
|
||||
log.Debug("hello id", helloID.Str(), "failed, err:", err)
|
||||
continue // on tls Dial error keep trying HelloIDs
|
||||
}
|
||||
|
||||
log.Debug("found avaliable hello id:", helloID.Str())
|
||||
c.HelloIDMu.Lock()
|
||||
c.WorkingHelloID = &client.ClientHelloID
|
||||
c.HelloIDMu.Unlock()
|
||||
return client, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type TLSManager struct {
|
||||
TransportManager
|
||||
|
||||
utlsConfig *utls.Config
|
||||
tlsConfig *tls.Config
|
||||
autoClientHelloID *utls.ClientHelloID
|
||||
config *conf.GlobalConfig
|
||||
roller *Roller
|
||||
helloIDs []utls.ClientHelloID
|
||||
helloIDLock sync.Mutex
|
||||
workingHelloID *utls.ClientHelloID
|
||||
utlsConfig *utls.Config
|
||||
tlsConfig *tls.Config
|
||||
config *conf.GlobalConfig
|
||||
}
|
||||
|
||||
func (m *TLSManager) printConnInfo(conn net.Conn) {
|
||||
@@ -139,57 +57,114 @@ func (m *TLSManager) printConnInfo(conn net.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TLSManager) DialToServer() (io.ReadWriteCloser, error) {
|
||||
func (m *TLSManager) dialTCP() (net.Conn, error) {
|
||||
network := "tcp"
|
||||
if m.config.TCP.PreferIPV4 {
|
||||
network = "tcp4"
|
||||
}
|
||||
var tlsConn net.Conn
|
||||
var err error
|
||||
if m.config.TLS.Fingerprint == "auto" {
|
||||
//use utls roller
|
||||
tlsConn, err = m.roller.Dial(network, m.config.RemoteAddress.String(), m.config.TLS.SNI)
|
||||
} else if m.config.TLS.ClientHelloID != nil {
|
||||
//use utls fixed fingerprint
|
||||
log.Debug("using fingerprint", m.config.TLS.ClientHelloID.Str())
|
||||
var conn net.Conn
|
||||
conn, err = net.Dial(network, m.config.RemoteAddress.String())
|
||||
tlsConn = utls.UClient(conn, m.utlsConfig, *m.config.TLS.ClientHelloID)
|
||||
} else {
|
||||
//normal golang tls
|
||||
conn, err := net.Dial(network, m.config.RemoteAddress.String())
|
||||
conn, err := net.DialTimeout(network, m.config.RemoteAddress.String(), protocol.GetRandomTimeoutDuration())
|
||||
if err != nil {
|
||||
return nil, common.NewError("failed to dial to remote server").Base(err)
|
||||
}
|
||||
if err := sockopt.ApplyTCPConnOption(conn.(*net.TCPConn), &m.config.TCP); err != nil {
|
||||
log.Warn(common.NewError("failed to apply tcp options").Base(err))
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (m *TLSManager) dialTLSWithFakeFingerprint() (*utls.UConn, error) {
|
||||
helloIDs := make([]utls.ClientHelloID, len(m.helloIDs))
|
||||
copy(helloIDs, m.helloIDs)
|
||||
rand.Shuffle(len(m.helloIDs), func(i, j int) {
|
||||
helloIDs[i], helloIDs[j] = helloIDs[j], helloIDs[i]
|
||||
})
|
||||
|
||||
m.helloIDLock.Lock()
|
||||
workingHelloID := m.workingHelloID // keep using same helloID, if it works
|
||||
m.helloIDLock.Unlock()
|
||||
if workingHelloID != nil {
|
||||
helloIDFound := false
|
||||
for i, ID := range helloIDs {
|
||||
if ID == *workingHelloID {
|
||||
helloIDs[i] = helloIDs[0]
|
||||
helloIDs[0] = *workingHelloID // push working hello ID first
|
||||
helloIDFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !helloIDFound {
|
||||
helloIDs = append([]utls.ClientHelloID{*workingHelloID}, helloIDs...)
|
||||
helloIDs[0], helloIDs[len(helloIDs)-1] = helloIDs[len(helloIDs)-1], helloIDs[0]
|
||||
}
|
||||
}
|
||||
for _, helloID := range helloIDs {
|
||||
tcpConn, err := m.dialTCP()
|
||||
if err != nil {
|
||||
return nil, err // on tcp Dial failure return with error right away
|
||||
}
|
||||
|
||||
client := utls.UClient(tcpConn, m.utlsConfig, helloID)
|
||||
if m.config.Websocket.Enabled {
|
||||
// HACK disable alpn (http/1.1, h2) to support websocket
|
||||
client.HandshakeState.Hello.AlpnProtocols = []string{}
|
||||
}
|
||||
protocol.SetRandomizedTimeout(client)
|
||||
err = client.Handshake()
|
||||
protocol.CancelTimeout(client)
|
||||
if err != nil {
|
||||
log.Debug("hello id", helloID.Str(), "failed, err:", err)
|
||||
continue // on tls Dial error keep trying HelloIDs
|
||||
}
|
||||
|
||||
log.Debug("found avaliable hello id:", helloID.Str())
|
||||
m.helloIDLock.Lock()
|
||||
m.workingHelloID = &client.ClientHelloID
|
||||
m.helloIDLock.Unlock()
|
||||
return client, err
|
||||
}
|
||||
return nil, common.NewError("all client hello id tried but failed")
|
||||
}
|
||||
|
||||
func (m *TLSManager) DialToServer() (io.ReadWriteCloser, error) {
|
||||
var transport net.Conn
|
||||
if m.config.TLS.Fingerprint != "" {
|
||||
//use utls fingerprints
|
||||
tlsConn, err := m.dialTLSWithFakeFingerprint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = sockopt.ApplyTCPConnOption(conn.(*net.TCPConn), &m.config.TCP)
|
||||
m.printConnInfo(tlsConn)
|
||||
transport = tlsConn
|
||||
} else {
|
||||
//normal golang tls
|
||||
tcpConn, err := m.dialTCP()
|
||||
if err != nil {
|
||||
return nil, common.NewError("failed to apply tcp option").Base(err)
|
||||
return nil, err
|
||||
}
|
||||
tlsConn = tls.Client(conn, m.tlsConfig)
|
||||
err = tlsConn.(*tls.Conn).Handshake()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, common.NewError("cannot dial to the remote server").Base(err)
|
||||
}
|
||||
m.printConnInfo(tlsConn)
|
||||
var transport io.ReadWriteCloser = tlsConn
|
||||
if m.config.Websocket.Enabled {
|
||||
ws, err := trojan.NewOutboundWebosocket(tlsConn, m.config)
|
||||
tlsConn := tls.Client(tcpConn, m.tlsConfig)
|
||||
err = tlsConn.Handshake()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport = tlsConn
|
||||
m.printConnInfo(tlsConn)
|
||||
}
|
||||
if m.config.Websocket.Enabled {
|
||||
ws, err := trojan.NewOutboundWebosocket(transport, m.config)
|
||||
if err != nil {
|
||||
transport.Close()
|
||||
return nil, common.NewError("failed to start websocket connection").Base(err)
|
||||
}
|
||||
transport = ws
|
||||
return ws, nil
|
||||
}
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
func NewTLSManager(config *conf.GlobalConfig) *TLSManager {
|
||||
utlsConfig := &utls.Config{
|
||||
RootCAs: config.TLS.CertPool,
|
||||
ServerName: config.TLS.SNI,
|
||||
InsecureSkipVerify: !config.TLS.Verify,
|
||||
SessionTicketsDisabled: !config.TLS.SessionTicket,
|
||||
ClientSessionCache: utls.NewLRUClientSessionCache(-1),
|
||||
RootCAs: config.TLS.CertPool,
|
||||
ServerName: config.TLS.SNI,
|
||||
InsecureSkipVerify: !config.TLS.Verify,
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
CipherSuites: config.TLS.CipherSuites,
|
||||
@@ -199,13 +174,38 @@ func NewTLSManager(config *conf.GlobalConfig) *TLSManager {
|
||||
SessionTicketsDisabled: !config.TLS.SessionTicket,
|
||||
CurvePreferences: config.TLS.CurvePreferences,
|
||||
NextProtos: config.TLS.ALPN,
|
||||
ClientSessionCache: tls.NewLRUClientSessionCache(-1),
|
||||
ClientSessionCache: tls.NewLRUClientSessionCache(192),
|
||||
}
|
||||
|
||||
m := &TLSManager{
|
||||
config: config,
|
||||
utlsConfig: utlsConfig,
|
||||
tlsConfig: tlsConfig,
|
||||
roller: NewRoller(utlsConfig),
|
||||
}
|
||||
|
||||
if config.TLS.Fingerprint == "auto" {
|
||||
m.helloIDs = []utls.ClientHelloID{
|
||||
utls.HelloChrome_Auto,
|
||||
utls.HelloFirefox_Auto,
|
||||
utls.HelloIOS_Auto,
|
||||
utls.HelloRandomizedNoALPN,
|
||||
}
|
||||
} else if config.TLS.Fingerprint != "" {
|
||||
table := map[string]*utls.ClientHelloID{
|
||||
"chrome": &utls.HelloChrome_Auto,
|
||||
"firefox": &utls.HelloFirefox_Auto,
|
||||
"ios": &utls.HelloIOS_Auto,
|
||||
"randomized": &utls.HelloRandomizedNoALPN,
|
||||
}
|
||||
id, found := table[config.TLS.Fingerprint]
|
||||
if found {
|
||||
log.Debug("tls fingerprint loaded:", id.Str())
|
||||
m.helloIDs = []utls.ClientHelloID{*id}
|
||||
} else {
|
||||
log.Warn("invalid tls fingerprint:", config.TLS.Fingerprint, ", using default fingerprint")
|
||||
config.TLS.Fingerprint = ""
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func (s *Server) handleMuxConn(stream *smux.Stream) {
|
||||
}
|
||||
|
||||
func (s *Server) handleConn(conn *tls.Conn) {
|
||||
protocol.RandomizedTimeout(conn)
|
||||
protocol.SetRandomizedTimeout(conn)
|
||||
inboundConn, req, err := trojan.NewInboundConnSession(s.ctx, conn, s.config, s.auth, s.shadow)
|
||||
if err != nil {
|
||||
//once the auth is failed, the conn will be took over by shadow manager. don't close it
|
||||
@@ -171,7 +171,7 @@ func (s *Server) Run() error {
|
||||
log.Info("conn accepted from", conn.RemoteAddr())
|
||||
go func(conn net.Conn) {
|
||||
//using randomized timeout
|
||||
protocol.RandomizedTimeout(conn)
|
||||
protocol.SetRandomizedTimeout(conn)
|
||||
|
||||
rewindConn := common.NewRewindConn(conn)
|
||||
rewindConn.R.SetBufferSize(512)
|
||||
|
||||
Reference in New Issue
Block a user