1package tls
2
3import (
4	"net"
5	"sync"
6	"time"
7)
8
9type Roller struct {
10	HelloIDs            []ClientHelloID
11	HelloIDMu           sync.Mutex
12	WorkingHelloID      *ClientHelloID
13	TcpDialTimeout      time.Duration
14	TlsHandshakeTimeout time.Duration
15	r                   *prng
16}
17
18// NewRoller creates Roller object with default range of HelloIDs to cycle through until a
19// working/unblocked one is found.
20func NewRoller() (*Roller, error) {
21	r, err := newPRNG()
22	if err != nil {
23		return nil, err
24	}
25
26	tcpDialTimeoutInc := r.Intn(14)
27	tcpDialTimeoutInc = 7 + tcpDialTimeoutInc
28
29	tlsHandshakeTimeoutInc := r.Intn(20)
30	tlsHandshakeTimeoutInc = 11 + tlsHandshakeTimeoutInc
31
32	return &Roller{
33		HelloIDs: []ClientHelloID{
34			HelloChrome_Auto,
35			HelloFirefox_Auto,
36			HelloIOS_Auto,
37			HelloRandomized,
38		},
39		TcpDialTimeout:      time.Second * time.Duration(tcpDialTimeoutInc),
40		TlsHandshakeTimeout: time.Second * time.Duration(tlsHandshakeTimeoutInc),
41		r:                   r,
42	}, nil
43}
44
45// Dial attempts to establish connection to given address using different HelloIDs.
46// If a working HelloID is found, it is used again for subsequent Dials.
47// If tcp connection fails or all HelloIDs are tried, returns with last error.
48//
49// Usage examples:
50//    Dial("tcp4", "google.com:443", "google.com")
51//    Dial("tcp", "10.23.144.22:443", "mywebserver.org")
52func (c *Roller) Dial(network, addr, serverName string) (*UConn, error) {
53	helloIDs := make([]ClientHelloID, len(c.HelloIDs))
54	copy(helloIDs, c.HelloIDs)
55	c.r.rand.Shuffle(len(c.HelloIDs), func(i, j int) {
56		helloIDs[i], helloIDs[j] = helloIDs[j], helloIDs[i]
57	})
58
59	c.HelloIDMu.Lock()
60	workingHelloId := c.WorkingHelloID // keep using same helloID, if it works
61	c.HelloIDMu.Unlock()
62	if workingHelloId != nil {
63		helloIDFound := false
64		for i, ID := range helloIDs {
65			if ID == *workingHelloId {
66				helloIDs[i] = helloIDs[0]
67				helloIDs[0] = *workingHelloId // push working hello ID first
68				helloIDFound = true
69				break
70			}
71		}
72		if !helloIDFound {
73			helloIDs = append([]ClientHelloID{*workingHelloId}, helloIDs...)
74		}
75	}
76
77	var tcpConn net.Conn
78	var err error
79	for _, helloID := range helloIDs {
80		tcpConn, err = net.DialTimeout(network, addr, c.TcpDialTimeout)
81		if err != nil {
82			return nil, err // on tcp Dial failure return with error right away
83		}
84
85		client := UClient(tcpConn, nil, helloID)
86		client.SetSNI(serverName)
87		client.SetDeadline(time.Now().Add(c.TlsHandshakeTimeout))
88		err = client.Handshake()
89		client.SetDeadline(time.Time{}) // unset timeout
90		if err != nil {
91			continue // on tls Dial error keep trying HelloIDs
92		}
93
94		c.HelloIDMu.Lock()
95		c.WorkingHelloID = &client.ClientHelloID
96		c.HelloIDMu.Unlock()
97		return client, err
98	}
99	return nil, err
100}
101