cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2019.11.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

h2mux/h2mux.go

460lines · modecode

1package h2mux
2
3import (
4 "context"
5 "io"
6 "strings"
7 "sync"
8 "time"
9
10 log "github.com/sirupsen/logrus"
11 "golang.org/x/net/http2"
12 "golang.org/x/net/http2/hpack"
13 "golang.org/x/sync/errgroup"
14)
15
16const (
17 defaultFrameSize uint32 = 1 << 14 // Minimum frame size in http2 spec
18 defaultWindowSize uint32 = (1 << 16) - 1 // Minimum window size in http2 spec
19 maxWindowSize uint32 = (1 << 31) - 1 // 2^31-1 = 2147483647, max window size in http2 spec
20 defaultTimeout time.Duration = 5 * time.Second
21 defaultRetries uint64 = 5
22 defaultWriteBufferMaxLen int = 1024 * 1024 // 1mb
23
24 SettingMuxerMagic http2.SettingID = 0x42db
25 MuxerMagicOrigin uint32 = 0xa2e43c8b
26 MuxerMagicEdge uint32 = 0x1088ebf9
27)
28
29type MuxedStreamHandler interface {
30 ServeStream(*MuxedStream) error
31}
32
33type MuxedStreamFunc func(stream *MuxedStream) error
34
35func (f MuxedStreamFunc) ServeStream(stream *MuxedStream) error {
36 return f(stream)
37}
38
39type MuxerConfig struct {
40 Timeout time.Duration
41 Handler MuxedStreamHandler
42 IsClient bool
43 // Name is used to identify this muxer instance when logging.
44 Name string
45 // The minimum time this connection can be idle before sending a heartbeat.
46 HeartbeatInterval time.Duration
47 // The minimum number of heartbeats to send before terminating the connection.
48 MaxHeartbeats uint64
49 // Logger to use
50 Logger *log.Entry
51 CompressionQuality CompressionSetting
52 // Initial size for HTTP2 flow control windows
53 DefaultWindowSize uint32
54 // Largest allowable size for HTTP2 flow control windows
55 MaxWindowSize uint32
56 // Largest allowable capacity for the buffer of data to be sent
57 StreamWriteBufferMaxLen int
58}
59
60type Muxer struct {
61 // f is used to read and write HTTP2 frames on the wire.
62 f *http2.Framer
63 // config is the MuxerConfig given in Handshake.
64 config MuxerConfig
65 // w, r are references to the underlying connection used.
66 w io.WriteCloser
67 r io.ReadCloser
68 // muxReader is the read process.
69 muxReader *MuxReader
70 // muxWriter is the write process.
71 muxWriter *MuxWriter
72 // muxMetricsUpdater is the process to update metrics
73 muxMetricsUpdater muxMetricsUpdater
74 // newStreamChan is used to create new streams on the writer thread.
75 // The writer will assign the next available stream ID.
76 newStreamChan chan MuxedStreamRequest
77 // abortChan is used to abort the writer event loop.
78 abortChan chan struct{}
79 // abortOnce is used to ensure abortChan is closed once only.
80 abortOnce sync.Once
81 // readyList is used to signal writable streams.
82 readyList *ReadyList
83 // streams tracks currently-open streams.
84 streams *activeStreamMap
85 // explicitShutdown records whether the Muxer is closing because Shutdown was called, or due to another
86 // error.
87 explicitShutdown *BooleanFuse
88
89 compressionQuality CompressionPreset
90}
91
92type Header struct {
93 Name, Value string
94}
95
96func RPCHeaders() []Header {
97 return []Header{
98 {Name: ":method", Value: "RPC"},
99 {Name: ":scheme", Value: "capnp"},
100 {Name: ":path", Value: "*"},
101 }
102}
103
104// Handshake establishes a muxed connection with the peer.
105// After the handshake completes, it is possible to open and accept streams.
106func Handshake(
107 w io.WriteCloser,
108 r io.ReadCloser,
109 config MuxerConfig,
110) (*Muxer, error) {
111 // Set default config values
112 if config.Timeout == 0 {
113 config.Timeout = defaultTimeout
114 }
115 if config.DefaultWindowSize == 0 {
116 config.DefaultWindowSize = defaultWindowSize
117 }
118 if config.MaxWindowSize == 0 {
119 config.MaxWindowSize = maxWindowSize
120 }
121 if config.StreamWriteBufferMaxLen == 0 {
122 config.StreamWriteBufferMaxLen = defaultWriteBufferMaxLen
123 }
124 // Initialise connection state fields
125 m := &Muxer{
126 f: http2.NewFramer(w, r), // A framer that writes to w and reads from r
127 config: config,
128 w: w,
129 r: r,
130 newStreamChan: make(chan MuxedStreamRequest),
131 abortChan: make(chan struct{}),
132 readyList: NewReadyList(),
133 streams: newActiveStreamMap(config.IsClient),
134 }
135
136 m.f.ReadMetaHeaders = hpack.NewDecoder(4096, func(hpack.HeaderField) {})
137 // Initialise the settings to identify this connection and confirm the other end is sane.
138 handshakeSetting := http2.Setting{ID: SettingMuxerMagic, Val: MuxerMagicEdge}
139 compressionSetting := http2.Setting{ID: SettingCompression, Val: config.CompressionQuality.toH2Setting()}
140 if CompressionIsSupported() {
141 log.Debug("Compression is supported")
142 m.compressionQuality = config.CompressionQuality.getPreset()
143 } else {
144 log.Debug("Compression is not supported")
145 compressionSetting = http2.Setting{ID: SettingCompression, Val: 0}
146 }
147
148 expectedMagic := MuxerMagicOrigin
149 if config.IsClient {
150 handshakeSetting.Val = MuxerMagicOrigin
151 expectedMagic = MuxerMagicEdge
152 }
153 errChan := make(chan error, 2)
154 // Simultaneously send our settings and verify the peer's settings.
155 go func() { errChan <- m.f.WriteSettings(handshakeSetting, compressionSetting) }()
156 go func() { errChan <- m.readPeerSettings(expectedMagic) }()
157 err := joinErrorsWithTimeout(errChan, 2, config.Timeout, ErrHandshakeTimeout)
158 if err != nil {
159 return nil, err
160 }
161 // Confirm sanity by ACKing the frame and expecting an ACK for our frame.
162 // Not strictly necessary, but let's pretend to be H2-like.
163 go func() { errChan <- m.f.WriteSettingsAck() }()
164 go func() { errChan <- m.readPeerSettingsAck() }()
165 err = joinErrorsWithTimeout(errChan, 2, config.Timeout, ErrHandshakeTimeout)
166 if err != nil {
167 return nil, err
168 }
169
170 // set up reader/writer pair ready for serve
171 streamErrors := NewStreamErrorMap()
172 goAwayChan := make(chan http2.ErrCode, 1)
173 inBoundCounter := NewAtomicCounter(0)
174 outBoundCounter := NewAtomicCounter(0)
175 pingTimestamp := NewPingTimestamp()
176 connActive := NewSignal()
177 idleDuration := config.HeartbeatInterval
178 // Sanity check to enusre idelDuration is sane
179 if idleDuration == 0 || idleDuration < defaultTimeout {
180 idleDuration = defaultTimeout
181 config.Logger.Warn("Minimum idle time has been adjusted to ", defaultTimeout)
182 }
183 maxRetries := config.MaxHeartbeats
184 if maxRetries == 0 {
185 maxRetries = defaultRetries
186 config.Logger.Warn("Minimum number of unacked heartbeats to send before closing the connection has been adjusted to ", maxRetries)
187 }
188
189 compBytesBefore, compBytesAfter := NewAtomicCounter(0), NewAtomicCounter(0)
190
191 m.muxMetricsUpdater = newMuxMetricsUpdater(
192 m.abortChan,
193 compBytesBefore,
194 compBytesAfter,
195 )
196
197 m.explicitShutdown = NewBooleanFuse()
198 m.muxReader = &MuxReader{
199 f: m.f,
200 handler: m.config.Handler,
201 streams: m.streams,
202 readyList: m.readyList,
203 streamErrors: streamErrors,
204 goAwayChan: goAwayChan,
205 abortChan: m.abortChan,
206 pingTimestamp: pingTimestamp,
207 connActive: connActive,
208 initialStreamWindow: m.config.DefaultWindowSize,
209 streamWindowMax: m.config.MaxWindowSize,
210 streamWriteBufferMaxLen: m.config.StreamWriteBufferMaxLen,
211 r: m.r,
212 metricsUpdater: m.muxMetricsUpdater,
213 bytesRead: inBoundCounter,
214 }
215 m.muxWriter = &MuxWriter{
216 f: m.f,
217 streams: m.streams,
218 streamErrors: streamErrors,
219 readyStreamChan: m.readyList.ReadyChannel(),
220 newStreamChan: m.newStreamChan,
221 goAwayChan: goAwayChan,
222 abortChan: m.abortChan,
223 pingTimestamp: pingTimestamp,
224 idleTimer: NewIdleTimer(idleDuration, maxRetries),
225 connActiveChan: connActive.WaitChannel(),
226 maxFrameSize: defaultFrameSize,
227 metricsUpdater: m.muxMetricsUpdater,
228 bytesWrote: outBoundCounter,
229 }
230 m.muxWriter.headerEncoder = hpack.NewEncoder(&m.muxWriter.headerBuffer)
231
232 if m.compressionQuality.dictSize > 0 && m.compressionQuality.nDicts > 0 {
233 nd, sz := m.compressionQuality.nDicts, m.compressionQuality.dictSize
234 writeDicts, dictChan := newH2WriteDictionaries(
235 nd,
236 sz,
237 m.compressionQuality.quality,
238 compBytesBefore,
239 compBytesAfter,
240 )
241 readDicts := newH2ReadDictionaries(nd, sz)
242 m.muxReader.dictionaries = h2Dictionaries{read: &readDicts, write: writeDicts}
243 m.muxWriter.useDictChan = dictChan
244 }
245
246 return m, nil
247}
248
249func (m *Muxer) readPeerSettings(magic uint32) error {
250 frame, err := m.f.ReadFrame()
251 if err != nil {
252 return err
253 }
254 settingsFrame, ok := frame.(*http2.SettingsFrame)
255 if !ok {
256 return ErrBadHandshakeNotSettings
257 }
258 if settingsFrame.Header().Flags != 0 {
259 return ErrBadHandshakeUnexpectedAck
260 }
261 peerMagic, ok := settingsFrame.Value(SettingMuxerMagic)
262 if !ok {
263 return ErrBadHandshakeNoMagic
264 }
265 if magic != peerMagic {
266 return ErrBadHandshakeWrongMagic
267 }
268 peerCompression, ok := settingsFrame.Value(SettingCompression)
269 if !ok {
270 m.compressionQuality = compressionPresets[CompressionNone]
271 return nil
272 }
273 ver, fmt, sz, nd := parseCompressionSettingVal(peerCompression)
274 if ver != compressionVersion || fmt != compressionFormat || sz == 0 || nd == 0 {
275 m.compressionQuality = compressionPresets[CompressionNone]
276 return nil
277 }
278 // Values used for compression are the mimimum between the two peers
279 if sz < m.compressionQuality.dictSize {
280 m.compressionQuality.dictSize = sz
281 }
282 if nd < m.compressionQuality.nDicts {
283 m.compressionQuality.nDicts = nd
284 }
285 return nil
286}
287
288func (m *Muxer) readPeerSettingsAck() error {
289 frame, err := m.f.ReadFrame()
290 if err != nil {
291 return err
292 }
293 settingsFrame, ok := frame.(*http2.SettingsFrame)
294 if !ok {
295 return ErrBadHandshakeNotSettingsAck
296 }
297 if settingsFrame.Header().Flags != http2.FlagSettingsAck {
298 return ErrBadHandshakeUnexpectedSettings
299 }
300 return nil
301}
302
303func joinErrorsWithTimeout(errChan <-chan error, receiveCount int, timeout time.Duration, timeoutError error) error {
304 for i := 0; i < receiveCount; i++ {
305 select {
306 case err := <-errChan:
307 if err != nil {
308 return err
309 }
310 case <-time.After(timeout):
311 return timeoutError
312 }
313 }
314 return nil
315}
316
317// Serve runs the event loops that comprise h2mux:
318// - MuxReader.run()
319// - MuxWriter.run()
320// - muxMetricsUpdater.run()
321// In the normal case, Shutdown() is called concurrently with Serve() to stop
322// these loops.
323func (m *Muxer) Serve(ctx context.Context) error {
324 errGroup, _ := errgroup.WithContext(ctx)
325 errGroup.Go(func() error {
326 err := m.muxReader.run(m.config.Logger)
327 m.explicitShutdown.Fuse(false)
328 m.r.Close()
329 m.abort()
330 return err
331 })
332
333 errGroup.Go(func() error {
334 err := m.muxWriter.run(m.config.Logger)
335 m.explicitShutdown.Fuse(false)
336 m.w.Close()
337 m.abort()
338 return err
339 })
340
341 errGroup.Go(func() error {
342 err := m.muxMetricsUpdater.run(m.config.Logger)
343 return err
344 })
345
346 err := errGroup.Wait()
347 if isUnexpectedTunnelError(err, m.explicitShutdown.Value()) {
348 return err
349 }
350 return nil
351}
352
353// Shutdown is called to initiate the "happy path" of muxer termination.
354func (m *Muxer) Shutdown() {
355 m.explicitShutdown.Fuse(true)
356 m.muxReader.Shutdown()
357}
358
359// IsUnexpectedTunnelError identifies errors that are expected when shutting down the h2mux tunnel.
360// The set of expected errors change depending on whether we initiated shutdown or not.
361func isUnexpectedTunnelError(err error, expectedShutdown bool) bool {
362 if err == nil {
363 return false
364 }
365 if !expectedShutdown {
366 return true
367 }
368 return !isConnectionClosedError(err)
369}
370
371func isConnectionClosedError(err error) bool {
372 if err == io.EOF {
373 return true
374 }
375 if err == io.ErrClosedPipe {
376 return true
377 }
378 if err.Error() == "tls: use of closed connection" {
379 return true
380 }
381 if strings.HasSuffix(err.Error(), "use of closed network connection") {
382 return true
383 }
384 return false
385}
386
387// OpenStream opens a new data stream with the given headers.
388// Called by proxy server and tunnel
389func (m *Muxer) OpenStream(ctx context.Context, headers []Header, body io.Reader) (*MuxedStream, error) {
390 stream := m.NewStream(headers)
391 if err := m.MakeMuxedStreamRequest(ctx, MuxedStreamRequest{stream, body}); err != nil {
392 return nil, err
393 }
394 if err := m.AwaitResponseHeaders(ctx, stream); err != nil {
395 return nil, err
396 }
397 return stream, nil
398}
399
400func (m *Muxer) OpenRPCStream(ctx context.Context) (*MuxedStream, error) {
401 stream := m.NewStream(RPCHeaders())
402 if err := m.MakeMuxedStreamRequest(ctx, MuxedStreamRequest{stream: stream, body: nil}); err != nil {
403 return nil, err
404 }
405 if err := m.AwaitResponseHeaders(ctx, stream); err != nil {
406 return nil, err
407 }
408 return stream, nil
409}
410
411func (m *Muxer) NewStream(headers []Header) *MuxedStream {
412 return NewStream(m.config, headers, m.readyList, m.muxReader.dictionaries)
413}
414
415func (m *Muxer) MakeMuxedStreamRequest(ctx context.Context, request MuxedStreamRequest) error {
416 select {
417 case <-ctx.Done():
418 return ErrStreamRequestTimeout
419 case <-m.abortChan:
420 return ErrStreamRequestConnectionClosed
421 // Will be received by mux writer
422 case m.newStreamChan <- request:
423 return nil
424 }
425}
426
427func (m *Muxer) CloseStreamRead(stream *MuxedStream) {
428 stream.CloseRead()
429 if stream.WriteClosed() {
430 m.streams.Delete(stream.streamID)
431 }
432}
433
434func (m *Muxer) AwaitResponseHeaders(ctx context.Context, stream *MuxedStream) error {
435 select {
436 case <-ctx.Done():
437 return ErrResponseHeadersTimeout
438 case <-m.abortChan:
439 return ErrResponseHeadersConnectionClosed
440 case <-stream.responseHeadersReceived:
441 return nil
442 }
443}
444
445func (m *Muxer) Metrics() *MuxerMetrics {
446 return m.muxMetricsUpdater.metrics()
447}
448
449func (m *Muxer) abort() {
450 m.abortOnce.Do(func() {
451 close(m.abortChan)
452 m.readyList.Close()
453 m.streams.Abort()
454 })
455}
456
457// Return how many retries/ticks since the connection was last marked active
458func (m *Muxer) TimerRetries() uint64 {
459 return m.muxWriter.idleTimer.RetryCount()
460}
461