git.prognetwork.ru/x0r/utls@v1.3.3/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  }