Stabilize cloudflare edge transport fallback

This commit is contained in:
世界
2026-03-24 13:09:09 +08:00
parent 289101fc56
commit d017cbe008
3 changed files with 80 additions and 11 deletions

View File

@@ -67,9 +67,8 @@ func NewHTTP2Connection(
}
tlsConfig := &tls.Config{
RootCAs: rootCAs,
ServerName: h2EdgeSNI,
CurvePreferences: []tls.CurveID{tls.CurveP256},
RootCAs: rootCAs,
ServerName: h2EdgeSNI,
}
dialer := &net.Dialer{}

View File

@@ -5,8 +5,10 @@ package cloudflare
import (
"context"
"crypto/tls"
"fmt"
"io"
"net"
"runtime"
"sync"
"time"
@@ -36,7 +38,7 @@ func quicInitialPacketSize(ipVersion int) uint16 {
// QUICConnection manages a single QUIC connection to the Cloudflare edge.
type QUICConnection struct {
conn *quic.Conn
conn quicConnection
logger log.ContextLogger
edgeAddr *EdgeAddr
connIndex uint8
@@ -51,6 +53,31 @@ type QUICConnection struct {
closeOnce sync.Once
}
type quicConnection interface {
OpenStream() (*quic.Stream, error)
AcceptStream(ctx context.Context) (*quic.Stream, error)
ReceiveDatagram(ctx context.Context) ([]byte, error)
SendDatagram(data []byte) error
LocalAddr() net.Addr
CloseWithError(code quic.ApplicationErrorCode, reason string) error
}
type closeableQUICConn struct {
*quic.Conn
udpConn *net.UDPConn
}
func (c *closeableQUICConn) CloseWithError(code quic.ApplicationErrorCode, reason string) error {
err := c.Conn.CloseWithError(code, reason)
_ = c.udpConn.Close()
return err
}
var (
quicPortByConnIndex = make(map[uint8]int)
quicPortAccess sync.Mutex
)
// NewQUICConnection dials the edge and establishes a QUIC connection.
func NewQUICConnection(
ctx context.Context,
@@ -69,10 +96,9 @@ func NewQUICConnection(
}
tlsConfig := &tls.Config{
RootCAs: rootCAs,
ServerName: quicEdgeSNI,
NextProtos: []string{quicEdgeALPN},
CurvePreferences: []tls.CurveID{tls.CurveP256},
RootCAs: rootCAs,
ServerName: quicEdgeSNI,
NextProtos: []string{quicEdgeALPN},
}
quicConfig := &quic.Config{
@@ -85,13 +111,19 @@ func NewQUICConnection(
InitialPacketSize: quicInitialPacketSize(edgeAddr.IPVersion),
}
conn, err := quic.DialAddr(ctx, edgeAddr.UDP.String(), tlsConfig, quicConfig)
udpConn, err := createUDPConnForConnIndex(connIndex, edgeAddr)
if err != nil {
return nil, E.Cause(err, "listen UDP for QUIC edge")
}
conn, err := quic.Dial(ctx, udpConn, edgeAddr.UDP, tlsConfig, quicConfig)
if err != nil {
udpConn.Close()
return nil, E.Cause(err, "dial QUIC edge")
}
return &QUICConnection{
conn: conn,
conn: &closeableQUICConn{Conn: conn, udpConn: udpConn},
logger: logger,
edgeAddr: edgeAddr,
connIndex: connIndex,
@@ -103,6 +135,39 @@ func NewQUICConnection(
}, nil
}
func createUDPConnForConnIndex(connIndex uint8, edgeAddr *EdgeAddr) (*net.UDPConn, error) {
quicPortAccess.Lock()
defer quicPortAccess.Unlock()
network := "udp"
if runtime.GOOS == "darwin" {
if edgeAddr.IPVersion == 4 {
network = "udp4"
} else {
network = "udp6"
}
}
if port, loaded := quicPortByConnIndex[connIndex]; loaded {
udpConn, err := net.ListenUDP(network, &net.UDPAddr{Port: port})
if err == nil {
return udpConn, nil
}
}
udpConn, err := net.ListenUDP(network, &net.UDPAddr{Port: 0})
if err != nil {
return nil, err
}
udpAddr, ok := udpConn.LocalAddr().(*net.UDPAddr)
if !ok {
udpConn.Close()
return nil, fmt.Errorf("unexpected local UDP address type %T", udpConn.LocalAddr())
}
quicPortByConnIndex[connIndex] = udpAddr.Port
return udpConn, nil
}
// Serve runs the QUIC connection: registers, accepts streams, handles datagrams.
// Blocks until the context is cancelled or a fatal error occurs.
func (q *QUICConnection) Serve(ctx context.Context, handler StreamHandler) error {

View File

@@ -268,7 +268,12 @@ func (i *Inbound) serveConnection(connIndex uint8, edgeAddr *EdgeAddr, features
switch protocol {
case "quic":
return i.serveQUIC(connIndex, edgeAddr, features, numPreviousAttempts)
err := i.serveQUIC(connIndex, edgeAddr, features, numPreviousAttempts)
if err == nil || i.ctx.Err() != nil {
return err
}
i.logger.Warn("QUIC connection failed, falling back to HTTP/2: ", err)
return i.serveHTTP2(connIndex, edgeAddr, features, numPreviousAttempts)
case "http2":
return i.serveHTTP2(connIndex, edgeAddr, features, numPreviousAttempts)
default: