cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2019.8.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

h2mux/h2mux.go

475lines · modecode

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