Skip to content

Commit 0f95f8b

Browse files
committed
TUN-6938: Force h2mux protocol to http2 for named tunnels
Going forward, the only protocols supported will be QUIC and HTTP2, defaulting to QUIC for "auto". Selecting h2mux protocol will be forcibly upgraded to http2 internally.
1 parent ae46af9 commit 0f95f8b

File tree

9 files changed

+139
-419
lines changed

9 files changed

+139
-419
lines changed

cmd/cloudflared/tunnel/configuration.go

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ import (
2121
"golang.org/x/crypto/ssh/terminal"
2222

2323
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
24-
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
25-
2624
"github.com/cloudflare/cloudflared/config"
2725
"github.com/cloudflare/cloudflared/connection"
2826
"github.com/cloudflare/cloudflared/edgediscovery"
27+
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
2928
"github.com/cloudflare/cloudflared/h2mux"
3029
"github.com/cloudflare/cloudflared/ingress"
3130
"github.com/cloudflare/cloudflared/orchestration"
@@ -218,34 +217,13 @@ func prepareTunnelConfig(
218217
transportProtocol = connection.QUIC.String()
219218
}
220219

221-
protocolFetcher := edgediscovery.ProtocolPercentage
222-
223-
features := append(c.StringSlice("features"), defaultFeatures...)
220+
features := dedup(append(c.StringSlice("features"), defaultFeatures...))
224221
if needPQ {
225222
features = append(features, supervisor.FeaturePostQuantum)
226223
}
227-
if c.IsSet(TunnelTokenFlag) {
228-
if transportProtocol == connection.AutoSelectFlag {
229-
protocolFetcher = func() (edgediscovery.ProtocolPercents, error) {
230-
// If the Tunnel is remotely managed and no protocol is set, we prefer QUIC, but still allow fall-back.
231-
preferQuic := []edgediscovery.ProtocolPercent{
232-
{
233-
Protocol: connection.QUIC.String(),
234-
Percentage: 100,
235-
},
236-
{
237-
Protocol: connection.HTTP2.String(),
238-
Percentage: 100,
239-
},
240-
}
241-
return preferQuic, nil
242-
}
243-
}
244-
log.Info().Msg("Will be fetching remotely managed configuration from Cloudflare API. Defaulting to protocol: quic")
245-
}
246224
namedTunnel.Client = tunnelpogs.ClientInfo{
247225
ClientID: clientID[:],
248-
Features: dedup(features),
226+
Features: features,
249227
Version: info.Version(),
250228
Arch: info.OSArch(),
251229
}
@@ -268,7 +246,7 @@ func prepareTunnelConfig(
268246
}
269247
}
270248

271-
protocolSelector, err := connection.NewProtocolSelector(transportProtocol, cfg.WarpRouting.Enabled, namedTunnel, protocolFetcher, supervisor.ResolveTTL, log, c.Bool("post-quantum"))
249+
protocolSelector, err := connection.NewProtocolSelector(transportProtocol, namedTunnel.Credentials.AccountTag, c.IsSet(TunnelTokenFlag), c.Bool("post-quantum"), edgediscovery.ProtocolPercentage, connection.ResolveTTL, log)
272250
if err != nil {
273251
return nil, nil, err
274252
}

connection/protocol.go

Lines changed: 74 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package connection
22

33
import (
4-
"errors"
54
"fmt"
65
"hash/fnv"
76
"sync"
@@ -13,63 +12,49 @@ import (
1312
)
1413

1514
const (
16-
AvailableProtocolFlagMessage = "Available protocols: 'auto' - automatically chooses the best protocol over time (the default; and also the recommended one); 'quic' - based on QUIC, relying on UDP egress to Cloudflare edge; 'http2' - using Go's HTTP2 library, relying on TCP egress to Cloudflare edge; 'h2mux' - Cloudflare's implementation of HTTP/2, deprecated"
15+
AvailableProtocolFlagMessage = "Available protocols: 'auto' - automatically chooses the best protocol over time (the default; and also the recommended one); 'quic' - based on QUIC, relying on UDP egress to Cloudflare edge; 'http2' - using Go's HTTP2 library, relying on TCP egress to Cloudflare edge"
1716
// edgeH2muxTLSServerName is the server name to establish h2mux connection with edge
1817
edgeH2muxTLSServerName = "cftunnel.com"
1918
// edgeH2TLSServerName is the server name to establish http2 connection with edge
2019
edgeH2TLSServerName = "h2.cftunnel.com"
2120
// edgeQUICServerName is the server name to establish quic connection with edge.
2221
edgeQUICServerName = "quic.cftunnel.com"
2322
AutoSelectFlag = "auto"
23+
// SRV and TXT record resolution TTL
24+
ResolveTTL = time.Hour
2425
)
2526

2627
var (
2728
// ProtocolList represents a list of supported protocols for communication with the edge.
28-
ProtocolList = []Protocol{H2mux, HTTP2, HTTP2Warp, QUIC, QUICWarp}
29+
ProtocolList = []Protocol{HTTP2, QUIC}
2930
)
3031

3132
type Protocol int64
3233

3334
const (
34-
// H2mux protocol can be used both with Classic and Named Tunnels. .
35-
H2mux Protocol = iota
36-
// HTTP2 is used only with named tunnels. It's more efficient than H2mux for L4 proxying.
37-
HTTP2
38-
// QUIC is used only with named tunnels.
35+
// HTTP2 using golang HTTP2 library for edge connections.
36+
HTTP2 Protocol = iota
37+
// QUIC using quic-go for edge connections.
3938
QUIC
40-
// HTTP2Warp is used only with named tunnels. It's useful for warp-routing where we don't want to fallback to
41-
// H2mux on HTTP2 failure to connect.
42-
HTTP2Warp
43-
//QUICWarp is used only with named tunnels. It's useful for warp-routing where we want to fallback to HTTP2 but
44-
// don't want HTTP2 to fallback to H2mux
45-
QUICWarp
4639
)
4740

4841
// Fallback returns the fallback protocol and whether the protocol has a fallback
4942
func (p Protocol) fallback() (Protocol, bool) {
5043
switch p {
51-
case H2mux:
52-
return 0, false
5344
case HTTP2:
54-
return H2mux, true
55-
case HTTP2Warp:
5645
return 0, false
5746
case QUIC:
5847
return HTTP2, true
59-
case QUICWarp:
60-
return HTTP2Warp, true
6148
default:
6249
return 0, false
6350
}
6451
}
6552

6653
func (p Protocol) String() string {
6754
switch p {
68-
case H2mux:
69-
return "h2mux"
70-
case HTTP2, HTTP2Warp:
55+
case HTTP2:
7156
return "http2"
72-
case QUIC, QUICWarp:
57+
case QUIC:
7358
return "quic"
7459
default:
7560
return fmt.Sprintf("unknown protocol")
@@ -78,15 +63,11 @@ func (p Protocol) String() string {
7863

7964
func (p Protocol) TLSSettings() *TLSSettings {
8065
switch p {
81-
case H2mux:
82-
return &TLSSettings{
83-
ServerName: edgeH2muxTLSServerName,
84-
}
85-
case HTTP2, HTTP2Warp:
66+
case HTTP2:
8667
return &TLSSettings{
8768
ServerName: edgeH2TLSServerName,
8869
}
89-
case QUIC, QUICWarp:
70+
case QUIC:
9071
return &TLSSettings{
9172
ServerName: edgeQUICServerName,
9273
NextProtos: []string{"argotunnel"},
@@ -106,6 +87,7 @@ type ProtocolSelector interface {
10687
Fallback() (Protocol, bool)
10788
}
10889

90+
// staticProtocolSelector will not provide a different protocol for Fallback
10991
type staticProtocolSelector struct {
11092
current Protocol
11193
}
@@ -115,10 +97,11 @@ func (s *staticProtocolSelector) Current() Protocol {
11597
}
11698

11799
func (s *staticProtocolSelector) Fallback() (Protocol, bool) {
118-
return 0, false
100+
return s.current, false
119101
}
120102

121-
type autoProtocolSelector struct {
103+
// remoteProtocolSelector will fetch a list of remote protocols to provide for edge discovery
104+
type remoteProtocolSelector struct {
122105
lock sync.RWMutex
123106

124107
current Protocol
@@ -127,35 +110,32 @@ type autoProtocolSelector struct {
127110
protocolPool []Protocol
128111

129112
switchThreshold int32
130-
fetchFunc PercentageFetcher
113+
fetchFunc edgediscovery.PercentageFetcher
131114
refreshAfter time.Time
132115
ttl time.Duration
133116
log *zerolog.Logger
134-
needPQ bool
135117
}
136118

137-
func newAutoProtocolSelector(
119+
func newRemoteProtocolSelector(
138120
current Protocol,
139121
protocolPool []Protocol,
140122
switchThreshold int32,
141-
fetchFunc PercentageFetcher,
123+
fetchFunc edgediscovery.PercentageFetcher,
142124
ttl time.Duration,
143125
log *zerolog.Logger,
144-
needPQ bool,
145-
) *autoProtocolSelector {
146-
return &autoProtocolSelector{
126+
) *remoteProtocolSelector {
127+
return &remoteProtocolSelector{
147128
current: current,
148129
protocolPool: protocolPool,
149130
switchThreshold: switchThreshold,
150131
fetchFunc: fetchFunc,
151132
refreshAfter: time.Now().Add(ttl),
152133
ttl: ttl,
153134
log: log,
154-
needPQ: needPQ,
155135
}
156136
}
157137

158-
func (s *autoProtocolSelector) Current() Protocol {
138+
func (s *remoteProtocolSelector) Current() Protocol {
159139
s.lock.Lock()
160140
defer s.lock.Unlock()
161141
if time.Now().Before(s.refreshAfter) {
@@ -173,7 +153,13 @@ func (s *autoProtocolSelector) Current() Protocol {
173153
return s.current
174154
}
175155

176-
func getProtocol(protocolPool []Protocol, fetchFunc PercentageFetcher, switchThreshold int32) (Protocol, error) {
156+
func (s *remoteProtocolSelector) Fallback() (Protocol, bool) {
157+
s.lock.RLock()
158+
defer s.lock.RUnlock()
159+
return s.current.fallback()
160+
}
161+
162+
func getProtocol(protocolPool []Protocol, fetchFunc edgediscovery.PercentageFetcher, switchThreshold int32) (Protocol, error) {
177163
protocolPercentages, err := fetchFunc()
178164
if err != nil {
179165
return 0, err
@@ -188,109 +174,74 @@ func getProtocol(protocolPool []Protocol, fetchFunc PercentageFetcher, switchThr
188174
return protocolPool[len(protocolPool)-1], nil
189175
}
190176

191-
func (s *autoProtocolSelector) Fallback() (Protocol, bool) {
177+
// defaultProtocolSelector will allow for a protocol to have a fallback
178+
type defaultProtocolSelector struct {
179+
lock sync.RWMutex
180+
current Protocol
181+
}
182+
183+
func newDefaultProtocolSelector(
184+
current Protocol,
185+
) *defaultProtocolSelector {
186+
return &defaultProtocolSelector{
187+
current: current,
188+
}
189+
}
190+
191+
func (s *defaultProtocolSelector) Current() Protocol {
192+
s.lock.Lock()
193+
defer s.lock.Unlock()
194+
return s.current
195+
}
196+
197+
func (s *defaultProtocolSelector) Fallback() (Protocol, bool) {
192198
s.lock.RLock()
193199
defer s.lock.RUnlock()
194-
if s.needPQ {
195-
return 0, false
196-
}
197200
return s.current.fallback()
198201
}
199202

200-
type PercentageFetcher func() (edgediscovery.ProtocolPercents, error)
201-
202203
func NewProtocolSelector(
203204
protocolFlag string,
204-
warpRoutingEnabled bool,
205-
namedTunnel *NamedTunnelProperties,
206-
fetchFunc PercentageFetcher,
207-
ttl time.Duration,
208-
log *zerolog.Logger,
205+
accountTag string,
206+
tunnelTokenProvided bool,
209207
needPQ bool,
208+
protocolFetcher edgediscovery.PercentageFetcher,
209+
resolveTTL time.Duration,
210+
log *zerolog.Logger,
210211
) (ProtocolSelector, error) {
211-
// Classic tunnel is only supported with h2mux
212-
if namedTunnel == nil {
213-
if needPQ {
214-
return nil, errors.New("Classic tunnel does not support post-quantum")
215-
}
216-
212+
// With --post-quantum, we force quic
213+
if needPQ {
217214
return &staticProtocolSelector{
218-
current: H2mux,
215+
current: QUIC,
219216
}, nil
220217
}
221218

222-
threshold := switchThreshold(namedTunnel.Credentials.AccountTag)
223-
fetchedProtocol, err := getProtocol([]Protocol{QUIC, HTTP2}, fetchFunc, threshold)
224-
if err != nil && protocolFlag == "auto" {
225-
log.Err(err).Msg("Unable to lookup protocol. Defaulting to `http2`. If this fails, you can attempt `--protocol quic` instead.")
226-
if needPQ {
227-
return nil, errors.New("http2 does not support post-quantum")
228-
}
229-
return &staticProtocolSelector{
230-
current: HTTP2,
231-
}, nil
232-
}
233-
if warpRoutingEnabled {
234-
if protocolFlag == H2mux.String() || fetchedProtocol == H2mux {
235-
log.Warn().Msg("Warp routing is not supported in h2mux protocol. Upgrading to http2 to allow it.")
236-
protocolFlag = HTTP2.String()
237-
fetchedProtocol = HTTP2Warp
238-
}
239-
return selectWarpRoutingProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol, needPQ)
219+
// When a --token is provided, we want to start with QUIC but have fallback to HTTP2
220+
if tunnelTokenProvided {
221+
return newDefaultProtocolSelector(QUIC), nil
240222
}
241223

242-
return selectNamedTunnelProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol, needPQ)
243-
}
224+
threshold := switchThreshold(accountTag)
225+
fetchedProtocol, err := getProtocol(ProtocolList, protocolFetcher, threshold)
226+
if err != nil {
227+
log.Warn().Msg("Unable to lookup protocol percentage.")
228+
// Falling through here since 'auto' is handled in the switch and failing
229+
// to do the protocol lookup isn't a failure since it can be triggered again
230+
// after the TTL.
231+
}
244232

245-
func selectNamedTunnelProtocols(
246-
protocolFlag string,
247-
fetchFunc PercentageFetcher,
248-
ttl time.Duration,
249-
log *zerolog.Logger,
250-
threshold int32,
251-
protocol Protocol,
252-
needPQ bool,
253-
) (ProtocolSelector, error) {
254233
// If the user picks a protocol, then we stick to it no matter what.
255234
switch protocolFlag {
256-
case H2mux.String():
257-
return &staticProtocolSelector{current: H2mux}, nil
235+
case "h2mux":
236+
// Any users still requesting h2mux will be upgraded to http2 instead
237+
log.Warn().Msg("h2mux is no longer a supported protocol: upgrading edge connection to http2. Please remove '--protocol h2mux' from runtime arguments to remove this warning.")
238+
return &staticProtocolSelector{current: HTTP2}, nil
258239
case QUIC.String():
259240
return &staticProtocolSelector{current: QUIC}, nil
260241
case HTTP2.String():
261242
return &staticProtocolSelector{current: HTTP2}, nil
262-
}
263-
264-
// If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and
265-
// fallback on failures.
266-
if protocolFlag == AutoSelectFlag {
267-
return newAutoProtocolSelector(protocol, []Protocol{QUIC, HTTP2, H2mux}, threshold, fetchFunc, ttl, log, needPQ), nil
268-
}
269-
270-
return nil, fmt.Errorf("Unknown protocol %s, %s", protocolFlag, AvailableProtocolFlagMessage)
271-
}
272-
273-
func selectWarpRoutingProtocols(
274-
protocolFlag string,
275-
fetchFunc PercentageFetcher,
276-
ttl time.Duration,
277-
log *zerolog.Logger,
278-
threshold int32,
279-
protocol Protocol,
280-
needPQ bool,
281-
) (ProtocolSelector, error) {
282-
// If the user picks a protocol, then we stick to it no matter what.
283-
switch protocolFlag {
284-
case QUIC.String():
285-
return &staticProtocolSelector{current: QUICWarp}, nil
286-
case HTTP2.String():
287-
return &staticProtocolSelector{current: HTTP2Warp}, nil
288-
}
289-
290-
// If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and
291-
// fallback on failures.
292-
if protocolFlag == AutoSelectFlag {
293-
return newAutoProtocolSelector(protocol, []Protocol{QUICWarp, HTTP2Warp}, threshold, fetchFunc, ttl, log, needPQ), nil
243+
case AutoSelectFlag:
244+
return newRemoteProtocolSelector(fetchedProtocol, ProtocolList, threshold, protocolFetcher, resolveTTL, log), nil
294245
}
295246

296247
return nil, fmt.Errorf("Unknown protocol %s, %s", protocolFlag, AvailableProtocolFlagMessage)

0 commit comments

Comments
 (0)