1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-11-03 16:57:38 -05:00
v2fly/external/github.com/refraction-networking/utls/u_roller.go
2019-02-17 00:58:02 +01:00

107 lines
2.7 KiB
Go

package tls
import (
"net"
"sync"
"time"
)
type Roller struct {
HelloIDs []ClientHelloID
HelloIDMu sync.Mutex
WorkingHelloID *ClientHelloID
TcpDialTimeout time.Duration
TlsHandshakeTimeout time.Duration
}
// NewRoller creates Roller object with default range of HelloIDs to cycle through until a
// working/unblocked one is found.
func NewRoller() (*Roller, error) {
tcpDialTimeoutInc, err := getRandInt(14)
if err != nil {
return nil, err
}
tcpDialTimeoutInc = 7 + tcpDialTimeoutInc
tlsHandshakeTimeoutInc, err := getRandInt(20)
if err != nil {
return nil, err
}
tlsHandshakeTimeoutInc = 11 + tlsHandshakeTimeoutInc
return &Roller{
HelloIDs: []ClientHelloID{
HelloChrome_Auto,
HelloFirefox_Auto,
HelloIOS_Auto,
HelloRandomized,
},
TcpDialTimeout: time.Second * time.Duration(tcpDialTimeoutInc),
TlsHandshakeTimeout: time.Second * time.Duration(tlsHandshakeTimeoutInc),
}, nil
}
// Dial attempts to establish connection to given address using different HelloIDs.
// If a working HelloID is found, it is used again for subsequent Dials.
// If tcp connection fails or all HelloIDs are tried, returns with last error.
//
// Usage examples:
// Dial("tcp4", "google.com:443", "google.com")
// Dial("tcp", "10.23.144.22:443", "mywebserver.org")
func (c *Roller) Dial(network, addr, serverName string) (*UConn, error) {
helloIDs, err := shuffleClientHelloIDs(c.HelloIDs)
if err != nil {
return nil, err
}
c.HelloIDMu.Lock()
workingHelloId := c.WorkingHelloID // keep using same helloID, if it works
c.HelloIDMu.Unlock()
if workingHelloId != nil {
for i, ID := range helloIDs {
if ID == *workingHelloId {
helloIDs[i] = helloIDs[0]
helloIDs[0] = *workingHelloId // push working hello ID first
break
}
}
}
var tcpConn net.Conn
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 := UClient(tcpConn, nil, helloID)
client.SetSNI(serverName)
client.SetDeadline(time.Now().Add(c.TlsHandshakeTimeout))
err = client.Handshake()
client.SetDeadline(time.Time{}) // unset timeout
if err != nil {
continue // on tls Dial error keep trying HelloIDs
}
c.HelloIDMu.Lock()
c.WorkingHelloID = &helloID
c.HelloIDMu.Unlock()
return client, err
}
return nil, err
}
// returns a shuffled copy of input
func shuffleClientHelloIDs(helloIDs []ClientHelloID) ([]ClientHelloID, error) {
perm, err := getRandPerm(len(helloIDs))
if err != nil {
return nil, err
}
shuffled := make([]ClientHelloID, len(helloIDs))
for i, randI := range perm {
shuffled[i] = helloIDs[randI]
}
return shuffled, nil
}