add utls anti-fingerprinting

This commit is contained in:
p4gefau1t
2020-04-26 03:45:04 -04:00
parent 7c61cfb25a
commit 19c6e37f8e
11 changed files with 253 additions and 34 deletions
+11 -9
View File
@@ -36,15 +36,17 @@ func (s *ClientAPIService) QueryStats(ctx context.Context, req *StatsRequest) (*
}
func (s *ClientAPIService) calcSpeed() {
select {
case <-time.After(time.Second):
sent, recv := s.meter.Query("")
s.uploadSpeed = sent - s.lastSent
s.downloadSpeed = recv - s.lastRecv
s.lastSent = sent
s.lastRecv = recv
case <-s.ctx.Done():
return
for {
select {
case <-time.After(time.Second):
sent, recv := s.meter.Query("")
s.uploadSpeed = sent - s.lastSent
s.downloadSpeed = recv - s.lastRecv
s.lastSent = sent
s.lastRecv = recv
case <-s.ctx.Done():
return
}
}
}
+3 -1
View File
@@ -48,4 +48,6 @@ for name in trojan-go-*;do
zip $name.zip client.json server.json trojan-go.service geoip.dat geosite.dat $name
sha1sum $name.zip > $name.zip.sha1
rm $name
done
done
rm *.json *.service *.dat
+3
View File
@@ -5,6 +5,7 @@ import (
"crypto/x509"
"github.com/p4gefau1t/trojan-go/common"
utls "github.com/refraction-networking/utls"
)
type RunType string
@@ -31,7 +32,9 @@ type TLSConfig struct {
FallbackHost string `json:"fallback_addr"`
FallbackPort int `json:"fallback_port"`
ReuseSession bool `json:"reuse_session"`
Fingerprint string `json:"fingerprint"`
ClientHelloID utls.ClientHelloID
FallbackAddress *common.Address
CertPool *x509.CertPool
KeyPair []tls.Certificate
+25 -2
View File
@@ -14,6 +14,7 @@ import (
"github.com/p4gefau1t/trojan-go/common"
"github.com/p4gefau1t/trojan-go/log"
utls "github.com/refraction-networking/utls"
"golang.org/x/crypto/pbkdf2"
)
@@ -46,7 +47,11 @@ func loadCommonConfig(config *GlobalConfig) error {
config.TargetAddress = common.NewAddress(config.TargetHost, config.TargetPort, "tcp")
if config.TLS.FallbackPort != 0 {
config.TLS.FallbackAddress = common.NewAddress(config.RemoteHost, config.TLS.FallbackPort, "tcp")
if config.TLS.FallbackHost == "" {
config.TLS.FallbackAddress = common.NewAddress(config.RemoteHost, config.TLS.FallbackPort, "tcp")
} else {
config.TLS.FallbackAddress = common.NewAddress(config.TLS.FallbackHost, config.TLS.FallbackPort, "tcp")
}
}
//api settings
@@ -84,7 +89,7 @@ func loadCommonConfig(config *GlobalConfig) error {
for _, c := range supportedSuites {
list += c.Name + ":"
}
log.Warn(list[0 : len(list)-1])
log.Warn(list[:len(list)-1])
config.TLS.CipherSuites = nil
}
} else {
@@ -192,6 +197,24 @@ func loadClientConfig(config *GlobalConfig) error {
}
//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
+1
View File
@@ -7,6 +7,7 @@ require (
github.com/go-acme/lego/v3 v3.5.0
github.com/go-sql-driver/mysql v1.5.0
github.com/golang/protobuf v1.4.0
github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57
github.com/smartystreets/goconvey v1.6.4
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
github.com/xtaci/smux v1.5.12
+2
View File
@@ -229,6 +229,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57 h1:SL1K0QAuC1b54KoY1pjPWe6kSlsFHwK9/oC960fKrTY=
github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+1 -1
View File
@@ -1,4 +1,4 @@
// +build linux
// +build linux darwin
package tproxy
+151 -16
View File
@@ -3,30 +3,118 @@ package client
import (
"crypto/tls"
"io"
"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/trojan"
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
tlsConfig *tls.Config
config *conf.GlobalConfig
utlsConfig *utls.Config
tlsConfig *tls.Config
autoClientHelloID *utls.ClientHelloID
config *conf.GlobalConfig
roller *Roller
}
func (m *TLSManager) DialToServer() (io.ReadWriteCloser, error) {
network := "tcp"
if m.config.TCP.PreferIPV4 {
network = "tcp4"
func (m *TLSManager) printConnInfo(conn net.Conn) {
if m.config.LogLevel != 0 {
return
}
tlsConn, err := tls.Dial(network, m.config.RemoteAddress.String(), m.tlsConfig)
if err != nil {
return nil, common.NewError("cannot dial to the remote server").Base(err)
}
if m.config.LogLevel == 0 {
switch conn.(type) {
case *tls.Conn:
tlsConn := conn.(*tls.Conn)
state := tlsConn.ConnectionState()
chain := state.VerifiedChains
log.Debug("tls handshaked", "cipher:", tls.CipherSuiteName(state.CipherSuite), "resume:", state.DidResume)
@@ -35,19 +123,63 @@ func (m *TLSManager) DialToServer() (io.ReadWriteCloser, error) {
log.Debug("subject:", chain[i][j].Subject, ", issuer:", chain[i][j].Issuer)
}
}
case *utls.UConn:
tlsConn := conn.(*utls.UConn)
state := tlsConn.ConnectionState()
chain := state.VerifiedChains
log.Debug("tls handshaked", "cipher:", tls.CipherSuiteName(state.CipherSuite), "resume:", state.DidResume)
for i := range chain {
for j := range chain[i] {
log.Debug("subject:", chain[i][j].Subject, ", issuer:", chain[i][j].Issuer)
}
}
default:
panic(conn)
}
var conn io.ReadWriteCloser = tlsConn
}
func (m *TLSManager) DialToServer() (io.ReadWriteCloser, 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.IsSet() {
//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
tlsConn, err = tls.Dial(network, m.config.RemoteAddress.String(), m.tlsConfig)
}
m.printConnInfo(tlsConn)
if err != nil {
return nil, common.NewError("cannot dial to the remote server").Base(err)
}
var transport io.ReadWriteCloser = tlsConn
if m.config.Websocket.Enabled {
ws, err := trojan.NewOutboundWebosocket(tlsConn, m.config)
if err != nil {
return nil, common.NewError("failed to start websocket connection").Base(err)
}
conn = ws
transport = ws
}
return conn, 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,
}
tlsConfig := &tls.Config{
CipherSuites: config.TLS.CipherSuites,
RootCAs: config.TLS.CertPool,
@@ -56,11 +188,14 @@ func NewTLSManager(config *conf.GlobalConfig) *TLSManager {
SessionTicketsDisabled: !config.TLS.SessionTicket,
}
if config.TLS.ReuseSession {
utlsConfig.ClientSessionCache = utls.NewLRUClientSessionCache(-1)
tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(-1)
}
m := &TLSManager{
config: config,
tlsConfig: tlsConfig,
config: config,
utlsConfig: utlsConfig,
tlsConfig: tlsConfig,
roller: NewRoller(utlsConfig),
}
return m
}
+1
View File
@@ -0,0 +1 @@
package sockopt
+50 -4
View File
@@ -8,6 +8,7 @@ import (
"crypto/x509"
"io/ioutil"
"net"
"sync"
"testing"
"time"
@@ -98,6 +99,7 @@ func getTLSConfig() conf.TLSConfig {
ReuseSession: true,
SessionTicket: true,
FallbackAddress: common.NewAddress("127.0.0.1", 10080, "tcp"),
Fingerprint: "auto",
}
return c
}
@@ -194,7 +196,6 @@ func RunServer(ctx context.Context, config *conf.GlobalConfig) {
}
func CheckClientServer(t *testing.T, clientConfig *conf.GlobalConfig, serverConfig *conf.GlobalConfig) {
time.Sleep(time.Second)
ctx, cancel := context.WithCancel(context.Background())
go RunEchoTCPServer(ctx)
go RunServer(ctx, serverConfig)
@@ -216,8 +217,8 @@ func CheckClientServer(t *testing.T, clientConfig *conf.GlobalConfig, serverConf
t.Fatal("not equal")
}
conn.Close()
cancel()
time.Sleep(time.Second)
}
func CheckForwardServer(t *testing.T, clientConfig *conf.GlobalConfig, serverConfig *conf.GlobalConfig) {
@@ -267,17 +268,51 @@ func SingleThreadSpeedTestClientServer(b *testing.B, clientConfig *conf.GlobalCo
common.Must(err)
conn, err := dialer.Dial("tcp", "127.0.0.1:5000")
common.Must(err)
mbytes := 512
mbytes := 2048
payload := GeneratePayload(1024 * 1024 * mbytes)
t1 := time.Now()
conn.Write(payload)
t2 := time.Now()
speed := float64(mbytes) / t2.Sub(t1).Seconds()
b.Log("Single thread link speed:", speed*8/1024, "Gbps")
b.Log("single-thread link speed:", speed*8/1024, "Gbps")
conn.Close()
cancel()
}
func MultiThreadSpeedTestClientServer(b *testing.B, clientConfig *conf.GlobalConfig, serverConfig *conf.GlobalConfig) {
time.Sleep(time.Second)
ctx, cancel := context.WithCancel(context.Background())
go RunBlackHoleTCPServer(ctx)
go RunServer(ctx, serverConfig)
go RunClient(ctx, clientConfig)
time.Sleep(time.Second)
dialer, err := proxy.SOCKS5("tcp", "127.0.0.1:4444", nil, nil)
common.Must(err)
mbytes := 2048
threads := 16
payload := GeneratePayload(1024 * 1024 * mbytes / threads)
wg := sync.WaitGroup{}
wg.Add(threads)
t1 := time.Now()
for i := 0; i < threads; i++ {
go func() {
conn, err := dialer.Dial("tcp", "127.0.0.1:5000")
common.Must(err)
common.Must2(conn.Write(payload))
wg.Done()
conn.Close()
}()
}
wg.Wait()
t2 := time.Now()
speed := float64(mbytes) / t2.Sub(t1).Seconds()
b.Log("multi-thread link speed:", speed*8/1024, "Gbps")
cancel()
}
func TestIt(t *testing.T) {
clientConfig := getBasicClientConfig()
serverConfig := getBasicServerConfig()
@@ -315,24 +350,28 @@ func BenchmarkNormal(b *testing.B) {
clientConfig := getBasicClientConfig()
serverConfig := getBasicServerConfig()
SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig)
MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig)
}
func BenchmarkMux(b *testing.B) {
clientConfig := addMuxConfig(getBasicClientConfig())
serverConfig := getBasicServerConfig()
SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig)
MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig)
}
func BenchmarkWebsocket(b *testing.B) {
clientConfig := addWsConfig(getBasicClientConfig())
serverConfig := addWsConfig(getBasicServerConfig())
SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig)
MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig)
}
func BenchmarkMuxWebsocket(b *testing.B) {
clientConfig := addMuxConfig(addWsConfig(getBasicClientConfig()))
serverConfig := addWsConfig(getBasicServerConfig())
SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig)
MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig)
}
func TestWebsocketShadow(t *testing.T) {
@@ -410,3 +449,10 @@ func TestShadow(t *testing.T) {
}
cancel()
}
func TestAutoClientID(t *testing.T) {
serverConfig := getBasicServerConfig()
clientConfig := getBasicClientConfig()
clientConfig.TLS.Fingerprint = "auto"
CheckClientServer(t, clientConfig, serverConfig)
}
+5 -1
View File
@@ -69,7 +69,10 @@ func RunBlackHoleTCPServer(ctx context.Context) {
common.Must(err)
go func() {
for {
conn, _ := listener.Accept()
conn, err := listener.Accept()
if err != nil {
return
}
go func(conn net.Conn) {
io.Copy(ioutil.Discard, conn)
conn.Close()
@@ -77,6 +80,7 @@ func RunBlackHoleTCPServer(ctx context.Context) {
}
}()
<-ctx.Done()
listener.Close()
}
func RunHelloHTTPServer(ctx context.Context) {