Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
fumiama opened this issue Feb 15, 2025 · 0 comments
Open

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

fumiama opened this issue Feb 15, 2025 · 0 comments

Comments

@fumiama
Copy link

fumiama commented Feb 15, 2025

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant