Skip to content

Commit 36f5171

Browse files
Draft Release (#400)
1 parent e2dd0e0 commit 36f5171

38 files changed

+1701
-726
lines changed

README.md

Lines changed: 181 additions & 53 deletions
Large diffs are not rendered by default.

cycletls/client.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99
"time"
1010

1111
"github.com/gorilla/websocket"
12-
utls "github.com/refraction-networking/utls"
1312
uquic "github.com/refraction-networking/uquic"
13+
utls "github.com/refraction-networking/utls"
1414
"golang.org/x/net/proxy"
1515
)
1616

@@ -46,13 +46,14 @@ type Browser struct {
4646
UserAgent string
4747

4848
// Connection options
49+
ServerName string
4950
Cookies []Cookie
5051
InsecureSkipVerify bool
5152
ForceHTTP1 bool
5253
ForceHTTP3 bool
5354

5455
// TLS 1.3 specific options
55-
TLS13AutoRetry bool
56+
TLS13AutoRetry bool
5657

5758
// Ordered HTTP header fields
5859
HeaderOrder []string
@@ -164,12 +165,13 @@ func generateClientKey(browser Browser, timeout int, disableRedirect bool, proxy
164165
}
165166

166167
// Create a hash of the configuration that affects connection behavior
167-
configStr := fmt.Sprintf("ja3:%s|ja4r:%s|http2:%s|quic:%s|ua:%s|proxy:%s|timeout:%d|redirect:%t|skipverify:%t|forcehttp1:%t|forcehttp3:%t%s",
168+
configStr := fmt.Sprintf("ja3:%s|ja4r:%s|http2:%s|quic:%s|ua:%s|sni:%s|proxy:%s|timeout:%d|redirect:%t|skipverify:%t|forcehttp1:%t|forcehttp3:%t%s",
168169
browser.JA3,
169170
browser.JA4r,
170171
browser.HTTP2Fingerprint,
171172
browser.QUICFingerprint,
172173
browser.UserAgent,
174+
browser.ServerName,
173175
proxyURL,
174176
timeout,
175177
disableRedirect,
@@ -300,6 +302,7 @@ func (browser Browser) WebSocketConnect(ctx context.Context, urlStr string) (*we
300302
// Create TLS config from browser settings
301303
tlsConfig := &utls.Config{
302304
InsecureSkipVerify: browser.InsecureSkipVerify,
305+
ServerName: browser.ServerName,
303306
}
304307

305308
// Create http headers directly

cycletls/errors.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,16 @@ func createErrorString(err error) (msg, debugger string) {
2929
}
3030

3131
func createErrorMessage(StatusCode int, err error, op string) errorMessage {
32-
msg := fmt.Sprintf("Request returned a Syscall Error: %s", err)
32+
var msg string
33+
34+
// For timeout errors, provide a clean, user-friendly message
35+
if op == "timeout" {
36+
msg = "Request timeout: deadline exceeded"
37+
} else {
38+
// For other errors, provide detailed debugging information
39+
msg = fmt.Sprintf("Request returned a Syscall Error: %s", err)
40+
}
41+
3342
debugger := fmt.Sprintf("%#v\n", err)
3443
return errorMessage{StatusCode: StatusCode, debugger: debugger, ErrorMsg: msg, Op: op}
3544
}
@@ -64,7 +73,9 @@ func parseError(err error) (errormessage errorMessage) {
6473

6574
// Check for common timeout error messages
6675
if strings.Contains(httpError, "context deadline exceeded") ||
67-
strings.Contains(httpError, "Client.Timeout exceeded") ||
76+
strings.Contains(httpError, "Client.Timeout") ||
77+
strings.Contains(httpError, "context cancellation while reading body") ||
78+
strings.Contains(httpError, "i/o timeout") ||
6879
strings.Contains(httpError, "timeout") {
6980
return createErrorMessage(408, err, "timeout")
7081
}

cycletls/extensions.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ func CreateExtensionFromID(extID uint16, tlsVersion uint16, components *JA4RComp
344344
case 0x0010: // ALPN
345345
alpnProtocols := []string{"h2", "http/1.1"}
346346
if components != nil {
347-
347+
348348
switch components.ALPN {
349349
case "h2":
350350
alpnProtocols = []string{"h2", "http/1.1"}
@@ -354,7 +354,7 @@ func CreateExtensionFromID(extID uint16, tlsVersion uint16, components *JA4RComp
354354
alpnProtocols = []string{"h3", "h2", "http/1.1"}
355355
}
356356
}
357-
357+
358358
return &utls.ALPNExtension{
359359
AlpnProtocols: alpnProtocols,
360360
}

cycletls/http3.go

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ func NewHTTP3Transport(tlsConfig *tls.Config) *HTTP3Transport {
5252
return &HTTP3Transport{
5353
TLSClientConfig: tlsConfig,
5454
QuicConfig: &quic.Config{
55-
HandshakeIdleTimeout: 30 * time.Second,
56-
MaxIdleTimeout: 90 * time.Second,
57-
KeepAlivePeriod: 15 * time.Second,
55+
HandshakeIdleTimeout: 30 * time.Second,
56+
MaxIdleTimeout: 90 * time.Second,
57+
KeepAlivePeriod: 15 * time.Second,
5858
},
5959
UQuicConfig: nil, // Will be set when QUIC fingerprint is provided
6060
QUICSpec: nil, // Will be set when QUIC fingerprint is provided
@@ -114,9 +114,9 @@ func (t *UQuicHTTP3Transport) RoundTrip(req *http.Request) (*http.Response, erro
114114
Transport: &http3.Transport{
115115
TLSClientConfig: t.TLSClientConfig,
116116
QUICConfig: &quic.Config{
117-
HandshakeIdleTimeout: 30 * time.Second,
118-
MaxIdleTimeout: 90 * time.Second,
119-
KeepAlivePeriod: 15 * time.Second,
117+
HandshakeIdleTimeout: 30 * time.Second,
118+
MaxIdleTimeout: 90 * time.Second,
119+
KeepAlivePeriod: 15 * time.Second,
120120
},
121121
},
122122
}
@@ -262,9 +262,9 @@ func ConfigureHTTP3Client(client *stdhttp.Client, tlsConfig *tls.Config) {
262262
client.Transport = &http3.Transport{
263263
TLSClientConfig: tlsConfig,
264264
QUICConfig: &quic.Config{
265-
HandshakeIdleTimeout: 30 * time.Second,
266-
MaxIdleTimeout: 90 * time.Second,
267-
KeepAlivePeriod: 15 * time.Second,
265+
HandshakeIdleTimeout: 30 * time.Second,
266+
MaxIdleTimeout: 90 * time.Second,
267+
KeepAlivePeriod: 15 * time.Second,
268268
},
269269
}
270270
}
@@ -408,13 +408,13 @@ func (rt *roundTripper) http3Dial(ctx context.Context, remoteAddr, port string,
408408
// TODO: Implement proper CONNECT-UDP proxy support for HTTP/3
409409
return nil, fmt.Errorf("HTTP/3 proxy support not yet implemented")
410410
}
411-
411+
412412
// Direct UDP connection
413413
conn, err := net.ListenPacket("udp", "")
414414
if err != nil {
415415
return nil, fmt.Errorf("failed to create UDP packet connection: %w", err)
416416
}
417-
417+
418418
return conn, nil
419419
}
420420

@@ -425,7 +425,7 @@ func (rt *roundTripper) ghttp3Dial(ctx context.Context, remoteAddr, port string,
425425
if err != nil {
426426
return nil, err
427427
}
428-
428+
429429
// Configure TLS - use crypto/tls.Config for standard QUIC (matches reference implementation)
430430
var tlsConfig *tls.Config
431431
if rt.TLSConfig != nil {
@@ -439,8 +439,12 @@ func (rt *roundTripper) ghttp3Dial(ctx context.Context, remoteAddr, port string,
439439
tlsConfig = &tls.Config{}
440440
}
441441
tlsConfig.NextProtos = []string{http3.NextProtoH3}
442-
tlsConfig.ServerName = remoteAddr
443-
442+
if rt.ServerName != "" {
443+
tlsConfig.ServerName = rt.ServerName
444+
} else {
445+
tlsConfig.ServerName = remoteAddr
446+
}
447+
444448
// Resolve remote address
445449
remoteHost := remoteAddr
446450
if net.ParseIP(remoteAddr) == nil {
@@ -457,15 +461,15 @@ func (rt *roundTripper) ghttp3Dial(ctx context.Context, remoteAddr, port string,
457461
// Use the first IP address
458462
remoteHost = ips[0].String()
459463
}
460-
464+
461465
// Convert port to integer
462466
portInt := 443
463467
if port != "" {
464468
if p, err := net.LookupPort("tcp", port); err == nil {
465469
portInt = p
466470
}
467471
}
468-
472+
469473
// Configure QUIC - conditional setup like reference implementation
470474
var quicConfig *quic.Config
471475
// TODO: Add support for rt.UquicConfig when it's available
@@ -486,19 +490,19 @@ func (rt *roundTripper) ghttp3Dial(ctx context.Context, remoteAddr, port string,
486490
Allow0RTT: false, // Security consideration
487491
}
488492
}
489-
493+
490494
// Establish QUIC connection
491495
remoteUDPAddr := &net.UDPAddr{
492496
IP: net.ParseIP(remoteHost),
493497
Port: portInt,
494498
}
495-
499+
496500
quicConn, err := quic.DialEarly(ctx, udpConn, remoteUDPAddr, tlsConfig, quicConfig)
497501
if err != nil {
498502
udpConn.Close()
499503
return nil, fmt.Errorf("failed to establish QUIC connection: %w", err)
500504
}
501-
505+
502506
return &HTTP3Connection{
503507
QuicConn: quicConn,
504508
RawConn: udpConn,
@@ -514,15 +518,19 @@ func (rt *roundTripper) uhttp3Dial(ctx context.Context, spec *uquic.QUICSpec, re
514518
if err != nil {
515519
return nil, err
516520
}
517-
521+
518522
// Configure TLS with uTLS config - use utls.Config directly (matches reference implementation)
519523
if rt.TLSConfig == nil {
520524
return nil, fmt.Errorf("TLS config is required for UQuic HTTP/3")
521525
}
522526
tlsConfig := rt.TLSConfig.Clone()
523527
tlsConfig.NextProtos = []string{http3.NextProtoH3}
524-
tlsConfig.ServerName = remoteAddr
525-
528+
if rt.ServerName != "" {
529+
tlsConfig.ServerName = rt.ServerName
530+
} else {
531+
tlsConfig.ServerName = remoteAddr
532+
}
533+
526534
// Resolve remote address
527535
remoteHost := remoteAddr
528536
if net.ParseIP(remoteAddr) == nil {
@@ -539,43 +547,43 @@ func (rt *roundTripper) uhttp3Dial(ctx context.Context, spec *uquic.QUICSpec, re
539547
// Use the first IP address
540548
remoteHost = ips[0].String()
541549
}
542-
550+
543551
// Convert port to integer
544552
portInt := 443
545553
if port != "" {
546554
if p, err := net.LookupPort("tcp", port); err == nil {
547555
portInt = p
548556
}
549557
}
550-
551-
// Configure UQuic - conditional setup like reference implementation
558+
559+
// Configure UQuic - conditional setup like reference implementation
552560
var uquicConfig *uquic.Config
553561
// TODO: Add support for rt.UquicConfig when it's available
554562
// For now, use default UQuic config similar to reference behavior
555563
if uquicConfig == nil {
556564
uquicConfig = &uquic.Config{}
557565
}
558-
566+
559567
// Create UQuic transport
560568
uTransport := &uquic.UTransport{
561569
Transport: &uquic.Transport{
562570
Conn: udpConn,
563571
},
564572
QUICSpec: spec,
565573
}
566-
574+
567575
// Establish QUIC connection with UQuic
568576
remoteUDPAddr := &net.UDPAddr{
569577
IP: net.ParseIP(remoteHost),
570578
Port: portInt,
571579
}
572-
580+
573581
quicConn, err := uTransport.DialEarly(ctx, remoteUDPAddr, tlsConfig, uquicConfig)
574582
if err != nil {
575583
udpConn.Close()
576584
return nil, fmt.Errorf("failed to establish UQuic connection: %w", err)
577585
}
578-
586+
579587
return &HTTP3Connection{
580588
QuicConn: quicConn,
581589
RawConn: udpConn,

0 commit comments

Comments
 (0)