Skip to content

crypto/tls: add support for bypassing some kind of SNI blocking #188

@fumiama

Description

@fumiama

I've got a feature request I'd love to share. I'm using cloudflared and encountered this problem

# cloudflared update
2025-02-15T06:22:36Z ERR update failed to apply error="Get \"https://github.com/cloudflare/cloudflared/releases/download/2025.2.0/cloudflared-linux-arm64\": read tcp x.x.x.x:43954->140.82.116.4:443: read: connection reset by peer"
failed to update cloudflared: Get "https://github.com/cloudflare/cloudflared/releases/download/2025.2.0/cloudflared-linux-arm64": read tcp x.x.x.x:43954->140.82.116.4:443: read: connection reset by peer

This is caused by the SNI blocking in our region.

When I discovered this repository, I noticed that it primarily focuses on enhancing the TLS stack with intriguing new features, mostly derived from RFC specifications.

I'm especially excited about this because I know a simple yet effective method to bypass certain SNI blocking techniques — though not mentioned in the RFCs. The approach is straightforward and interesting enough that I'd like to demonstrate it directly through a draft PR #189.

I understand that this repository values easy synchronization with the upstream. However, since my proposed changes are minimal and this specific code has remained stable for years (from Go 1.19 through 1.24, as far as I know), I was hoping that we could discuss this design together.

Specifically, in my current proposal, when creating a specialized HTTP TLS client, you could make a client like

import (
	"context"
	"crypto/tls"
	"io"
	"net"
	"net/http"
	"os"
	"time"
)

var defaultDialer = net.Dialer{
	Timeout: time.Minute,
}

var DefaultClient = http.Client{
	Transport: &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
			if defaultDialer.Timeout != 0 {
				var cancel context.CancelFunc
				ctx, cancel = context.WithTimeout(ctx, defaultDialer.Timeout)
				defer cancel()
			}

			if !defaultDialer.Deadline.IsZero() {
				var cancel context.CancelFunc
				ctx, cancel = context.WithDeadline(ctx, defaultDialer.Deadline)
				defer cancel()
			}

			host, port, err := net.SplitHostPort(addr)
			if err != nil {
				return nil, err
			}
			// The host it looked up must be correct.
			addrs, err := net.DefaultResolver.LookupHost(ctx, host)
			if err != nil {
				return nil, err
			}
			var conn net.Conn
			var tlsConn *tls.Conn
			for _, a := range addrs {
				conn, err = defaultDialer.DialContext(ctx, network, net.JoinHostPort(a, port))
				if err != nil {
					continue
				}
				// MAINLY THIS PART ⬇
				tlsConn = tls.Client(conn, &tls.Config{
					ServerName: host,
				})
				tlsConn.SetFirstHandshakeRecordMaxPayloadSize(4) // the proposed feature
				err = tlsConn.HandshakeContext(ctx)
				if err == nil {
					break
				}
				_ = tlsConn.Close()
				// MAINLY THIS PART ⬆
				tlsConn = nil
				conn, err = defaultDialer.DialContext(ctx, network, net.JoinHostPort(a, port))
				if err != nil {
					continue
				}
				tlsConn = tls.Client(conn, &tls.Config{
					ServerName: host,
				})
				err = tlsConn.HandshakeContext(ctx)
				if err == nil {
					break
				}
				_ = tlsConn.Close()
				tlsConn = nil
			}
			return tlsConn, err
		},
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	},
}

Then I compiled it using the edited go and download that file again

func main() {
	resp, err := DefaultClient.Get("https://github.com/cloudflare/cloudflared/releases/download/2025.2.0/cloudflared-linux-arm64")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	f, err := os.Create("cloudflared-linux-arm64")
	if err != nil {
		panic(err)
	}
	defer f.Close()
	_, err = io.Copy(f, resp.Body)
}

At this time it works well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions