cloudflare/cloudflared
Publicmirrored from https://github.com/cloudflare/cloudflaredAvailable
connection/protocol.go
179lines · modecode
| 1 | package connection |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "hash/fnv" |
| 6 | "sync" |
| 7 | "time" |
| 8 | |
| 9 | "github.com/cloudflare/cloudflared/logger" |
| 10 | ) |
| 11 | |
| 12 | const ( |
| 13 | AvailableProtocolFlagMessage = "Available protocols: http2 - Go's implementation, h2mux - Cloudflare's implementation of HTTP/2, and auto - automatically select between http2 and h2mux" |
| 14 | // edgeH2muxTLSServerName is the server name to establish h2mux connection with edge |
| 15 | edgeH2muxTLSServerName = "cftunnel.com" |
| 16 | // edgeH2TLSServerName is the server name to establish http2 connection with edge |
| 17 | edgeH2TLSServerName = "h2.cftunnel.com" |
| 18 | // threshold to switch back to h2mux when the user intentionally pick --protocol http2 |
| 19 | explicitHTTP2FallbackThreshold = -1 |
| 20 | autoSelectFlag = "auto" |
| 21 | ) |
| 22 | |
| 23 | var ( |
| 24 | ProtocolList = []Protocol{H2mux, HTTP2} |
| 25 | ) |
| 26 | |
| 27 | type Protocol int64 |
| 28 | |
| 29 | const ( |
| 30 | H2mux Protocol = iota |
| 31 | HTTP2 |
| 32 | ) |
| 33 | |
| 34 | func (p Protocol) ServerName() string { |
| 35 | switch p { |
| 36 | case H2mux: |
| 37 | return edgeH2muxTLSServerName |
| 38 | case HTTP2: |
| 39 | return edgeH2TLSServerName |
| 40 | default: |
| 41 | return "" |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | // Fallback returns the fallback protocol and whether the protocol has a fallback |
| 46 | func (p Protocol) fallback() (Protocol, bool) { |
| 47 | switch p { |
| 48 | case H2mux: |
| 49 | return 0, false |
| 50 | case HTTP2: |
| 51 | return H2mux, true |
| 52 | default: |
| 53 | return 0, false |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | func (p Protocol) String() string { |
| 58 | switch p { |
| 59 | case H2mux: |
| 60 | return "h2mux" |
| 61 | case HTTP2: |
| 62 | return "http2" |
| 63 | default: |
| 64 | return fmt.Sprintf("unknown protocol") |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | type ProtocolSelector interface { |
| 69 | Current() Protocol |
| 70 | Fallback() (Protocol, bool) |
| 71 | } |
| 72 | |
| 73 | type staticProtocolSelector struct { |
| 74 | current Protocol |
| 75 | } |
| 76 | |
| 77 | func (s *staticProtocolSelector) Current() Protocol { |
| 78 | return s.current |
| 79 | } |
| 80 | |
| 81 | func (s *staticProtocolSelector) Fallback() (Protocol, bool) { |
| 82 | return 0, false |
| 83 | } |
| 84 | |
| 85 | type autoProtocolSelector struct { |
| 86 | lock sync.RWMutex |
| 87 | current Protocol |
| 88 | switchThrehold int32 |
| 89 | fetchFunc PercentageFetcher |
| 90 | refreshAfter time.Time |
| 91 | ttl time.Duration |
| 92 | logger logger.Service |
| 93 | } |
| 94 | |
| 95 | func newAutoProtocolSelector( |
| 96 | current Protocol, |
| 97 | switchThrehold int32, |
| 98 | fetchFunc PercentageFetcher, |
| 99 | ttl time.Duration, |
| 100 | logger logger.Service, |
| 101 | ) *autoProtocolSelector { |
| 102 | return &autoProtocolSelector{ |
| 103 | current: current, |
| 104 | switchThrehold: switchThrehold, |
| 105 | fetchFunc: fetchFunc, |
| 106 | refreshAfter: time.Now().Add(ttl), |
| 107 | ttl: ttl, |
| 108 | logger: logger, |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | func (s *autoProtocolSelector) Current() Protocol { |
| 113 | s.lock.Lock() |
| 114 | defer s.lock.Unlock() |
| 115 | if time.Now().Before(s.refreshAfter) { |
| 116 | return s.current |
| 117 | } |
| 118 | |
| 119 | percentage, err := s.fetchFunc() |
| 120 | if err != nil { |
| 121 | s.logger.Errorf("Failed to refresh protocol, err: %v", err) |
| 122 | return s.current |
| 123 | } |
| 124 | |
| 125 | if s.switchThrehold < percentage { |
| 126 | s.current = HTTP2 |
| 127 | } else { |
| 128 | s.current = H2mux |
| 129 | } |
| 130 | s.refreshAfter = time.Now().Add(s.ttl) |
| 131 | return s.current |
| 132 | } |
| 133 | |
| 134 | func (s *autoProtocolSelector) Fallback() (Protocol, bool) { |
| 135 | s.lock.RLock() |
| 136 | defer s.lock.RUnlock() |
| 137 | return s.current.fallback() |
| 138 | } |
| 139 | |
| 140 | type PercentageFetcher func() (int32, error) |
| 141 | |
| 142 | func NewProtocolSelector(protocolFlag string, namedTunnel *NamedTunnelConfig, fetchFunc PercentageFetcher, ttl time.Duration, logger logger.Service) (ProtocolSelector, error) { |
| 143 | if namedTunnel == nil { |
| 144 | return &staticProtocolSelector{ |
| 145 | current: H2mux, |
| 146 | }, nil |
| 147 | } |
| 148 | if protocolFlag == H2mux.String() { |
| 149 | return &staticProtocolSelector{ |
| 150 | current: H2mux, |
| 151 | }, nil |
| 152 | } |
| 153 | |
| 154 | http2Percentage, err := fetchFunc() |
| 155 | if err != nil { |
| 156 | return nil, err |
| 157 | } |
| 158 | if protocolFlag == HTTP2.String() { |
| 159 | if http2Percentage < 0 { |
| 160 | return newAutoProtocolSelector(H2mux, explicitHTTP2FallbackThreshold, fetchFunc, ttl, logger), nil |
| 161 | } |
| 162 | return newAutoProtocolSelector(HTTP2, explicitHTTP2FallbackThreshold, fetchFunc, ttl, logger), nil |
| 163 | } |
| 164 | |
| 165 | if protocolFlag != autoSelectFlag { |
| 166 | return nil, fmt.Errorf("Unknown protocol %s, %s", protocolFlag, AvailableProtocolFlagMessage) |
| 167 | } |
| 168 | threshold := switchThreshold(namedTunnel.Auth.AccountTag) |
| 169 | if threshold < http2Percentage { |
| 170 | return newAutoProtocolSelector(HTTP2, threshold, fetchFunc, ttl, logger), nil |
| 171 | } |
| 172 | return newAutoProtocolSelector(H2mux, threshold, fetchFunc, ttl, logger), nil |
| 173 | } |
| 174 | |
| 175 | func switchThreshold(accountTag string) int32 { |
| 176 | h := fnv.New32a() |
| 177 | h.Write([]byte(accountTag)) |
| 178 | return int32(h.Sum32() % 100) |
| 179 | } |
| 180 | |