package main import ( "context" "crypto/tls" "fmt" "net" "strings" "time" utls "github.com/refraction-networking/utls" ) // dialTLSChrome dials addr with a Chrome-like ClientHello so CDNs (Fastly) // are less likely to HTTP 403 Go's default TLS fingerprint. // Handshakes as HTTP/1.1 only so net/http can use the returned conn. func dialTLSChrome(ctx context.Context, dialCtx func(context.Context, string, string) (net.Conn, error), network, addr string) (net.Conn, error) { host := serverNameFromAddr(addr) raw, err := dialCtx(ctx, network, addr) if err != nil { return nil, err } cfg := &utls.Config{ ServerName: host, InsecureSkipVerify: true, NextProtos: []string{"http/1.1"}, } spec, err := utls.UTLSIdToSpec(utls.HelloChrome_120) if err != nil { _ = raw.Close() return dialTLSStd(ctx, dialCtx, network, addr) } for i := range spec.Extensions { if alpn, ok := spec.Extensions[i].(*utls.ALPNExtension); ok { alpn.AlpnProtocols = []string{"http/1.1"} } } uConn := utls.UClient(raw, cfg, utls.HelloCustom) if err := uConn.ApplyPreset(&spec); err != nil { _ = raw.Close() return nil, fmt.Errorf("utls preset %s: %w", host, err) } deadline, ok := ctx.Deadline() if !ok { deadline = time.Now().Add(15 * time.Second) } _ = raw.SetDeadline(deadline) if err := uConn.Handshake(); err != nil { _ = raw.Close() return nil, fmt.Errorf("utls handshake %s: %w", host, err) } _ = raw.SetDeadline(time.Time{}) return uConn, nil } func dialTLSStd(ctx context.Context, dialCtx func(context.Context, string, string) (net.Conn, error), network, addr string) (net.Conn, error) { host := serverNameFromAddr(addr) raw, err := dialCtx(ctx, network, addr) if err != nil { return nil, err } cfg := &tls.Config{ServerName: host, InsecureSkipVerify: true, NextProtos: []string{"http/1.1"}} c := tls.Client(raw, cfg) if err := c.HandshakeContext(ctx); err != nil { _ = raw.Close() return nil, err } return c, nil } func serverNameFromAddr(addr string) string { host, _, err := net.SplitHostPort(addr) if err != nil { return strings.TrimSpace(addr) } return host }