github.com/Carcraftz/utls@v0.0.0-20220413235215-6b7c52fd78b6/u_roller.go (about) 1 package tls 2 3 import ( 4 "net" 5 "sync" 6 "time" 7 ) 8 9 type 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. 20 func 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") 52 func (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 }