cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2019.3.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

h2mux/h2mux.go

427lines · 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
97// Handshake establishes a muxed connection with the peer.
98// After the handshake completes, it is possible to open and accept streams.
99func Handshake(
100 w io.WriteCloser,
101 r io.ReadCloser,
102 config MuxerConfig,
103) (*Muxer, error) {
104 // Set default config values
105 if config.Timeout == 0 {
106 config.Timeout = defaultTimeout
107 }
108 if config.DefaultWindowSize == 0 {
109 config.DefaultWindowSize = defaultWindowSize
110 }
111 if config.MaxWindowSize == 0 {
112 config.MaxWindowSize = maxWindowSize
113 }
114 if config.StreamWriteBufferMaxLen == 0 {
115 config.StreamWriteBufferMaxLen = defaultWriteBufferMaxLen
116 }
117 // Initialise connection state fields
118 m := &Muxer{
119 f: http2.NewFramer(w, r), // A framer that writes to w and reads from r
120 config: config,
121 w: w,
122 r: r,
123 newStreamChan: make(chan MuxedStreamRequest),
124 abortChan: make(chan struct{}),
125 readyList: NewReadyList(),
126 streams: newActiveStreamMap(config.IsClient),
127 }
128
129 m.f.ReadMetaHeaders = hpack.NewDecoder(4096, func(hpack.HeaderField) {})
130 // Initialise the settings to identify this connection and confirm the other end is sane.
131 handshakeSetting := http2.Setting{ID: SettingMuxerMagic, Val: MuxerMagicEdge}
132 compressionSetting := http2.Setting{ID: SettingCompression, Val: config.CompressionQuality.toH2Setting()}
133 if CompressionIsSupported() {
134 log.Debug("Compression is supported")
135 m.compressionQuality = config.CompressionQuality.getPreset()
136 } else {
137 log.Debug("Compression is not supported")
138 compressionSetting = http2.Setting{ID: SettingCompression, Val: 0}
139 }
140
141 expectedMagic := MuxerMagicOrigin
142 if config.IsClient {
143 handshakeSetting.Val = MuxerMagicOrigin
144 expectedMagic = MuxerMagicEdge
145 }
146 errChan := make(chan error, 2)
147 // Simultaneously send our settings and verify the peer's settings.
148 go func() { errChan <- m.f.WriteSettings(handshakeSetting, compressionSetting) }()
149 go func() { errChan <- m.readPeerSettings(expectedMagic) }()
150 err := joinErrorsWithTimeout(errChan, 2, config.Timeout, ErrHandshakeTimeout)
151 if err != nil {
152 return nil, err
153 }
154 // Confirm sanity by ACKing the frame and expecting an ACK for our frame.
155 // Not strictly necessary, but let's pretend to be H2-like.
156 go func() { errChan <- m.f.WriteSettingsAck() }()
157 go func() { errChan <- m.readPeerSettingsAck() }()
158 err = joinErrorsWithTimeout(errChan, 2, config.Timeout, ErrHandshakeTimeout)
159 if err != nil {
160 return nil, err
161 }
162
163 // set up reader/writer pair ready for serve
164 streamErrors := NewStreamErrorMap()
165 goAwayChan := make(chan http2.ErrCode, 1)
166 inBoundCounter := NewAtomicCounter(0)
167 outBoundCounter := NewAtomicCounter(0)
168 pingTimestamp := NewPingTimestamp()
169 connActive := NewSignal()
170 idleDuration := config.HeartbeatInterval
171 // Sanity check to enusre idelDuration is sane
172 if idleDuration == 0 || idleDuration < defaultTimeout {
173 idleDuration = defaultTimeout
174 config.Logger.Warn("Minimum idle time has been adjusted to ", defaultTimeout)
175 }
176 maxRetries := config.MaxHeartbeats
177 if maxRetries == 0 {
178 maxRetries = defaultRetries
179 config.Logger.Warn("Minimum number of unacked heartbeats to send before closing the connection has been adjusted to ", maxRetries)
180 }
181
182 compBytesBefore, compBytesAfter := NewAtomicCounter(0), NewAtomicCounter(0)
183
184 m.muxMetricsUpdater = newMuxMetricsUpdater(
185 m.abortChan,
186 compBytesBefore,
187 compBytesAfter,
188 )
189
190 m.explicitShutdown = NewBooleanFuse()
191 m.muxReader = &MuxReader{
192 f: m.f,
193 handler: m.config.Handler,
194 streams: m.streams,
195 readyList: m.readyList,
196 streamErrors: streamErrors,
197 goAwayChan: goAwayChan,
198 abortChan: m.abortChan,
199 pingTimestamp: pingTimestamp,
200 connActive: connActive,
201 initialStreamWindow: m.config.DefaultWindowSize,
202 streamWindowMax: m.config.MaxWindowSize,
203 streamWriteBufferMaxLen: m.config.StreamWriteBufferMaxLen,
204 r: m.r,
205 metricsUpdater: m.muxMetricsUpdater,
206 bytesRead: inBoundCounter,
207 }
208 m.muxWriter = &MuxWriter{
209 f: m.f,
210 streams: m.streams,
211 streamErrors: streamErrors,
212 readyStreamChan: m.readyList.ReadyChannel(),
213 newStreamChan: m.newStreamChan,
214 goAwayChan: goAwayChan,
215 abortChan: m.abortChan,
216 pingTimestamp: pingTimestamp,
217 idleTimer: NewIdleTimer(idleDuration, maxRetries),
218 connActiveChan: connActive.WaitChannel(),
219 maxFrameSize: defaultFrameSize,
220 metricsUpdater: m.muxMetricsUpdater,
221 bytesWrote: outBoundCounter,
222 }
223 m.muxWriter.headerEncoder = hpack.NewEncoder(&m.muxWriter.headerBuffer)
224
225 if m.compressionQuality.dictSize > 0 && m.compressionQuality.nDicts > 0 {
226 nd, sz := m.compressionQuality.nDicts, m.compressionQuality.dictSize
227 writeDicts, dictChan := newH2WriteDictionaries(
228 nd,
229 sz,
230 m.compressionQuality.quality,
231 compBytesBefore,
232 compBytesAfter,
233 )
234 readDicts := newH2ReadDictionaries(nd, sz)
235 m.muxReader.dictionaries = h2Dictionaries{read: &readDicts, write: writeDicts}
236 m.muxWriter.useDictChan = dictChan
237 }
238
239 return m, nil
240}
241
242func (m *Muxer) readPeerSettings(magic uint32) error {
243 frame, err := m.f.ReadFrame()
244 if err != nil {
245 return err
246 }
247 settingsFrame, ok := frame.(*http2.SettingsFrame)
248 if !ok {
249 return ErrBadHandshakeNotSettings
250 }
251 if settingsFrame.Header().Flags != 0 {
252 return ErrBadHandshakeUnexpectedAck
253 }
254 peerMagic, ok := settingsFrame.Value(SettingMuxerMagic)
255 if !ok {
256 return ErrBadHandshakeNoMagic
257 }
258 if magic != peerMagic {
259 return ErrBadHandshakeWrongMagic
260 }
261 peerCompression, ok := settingsFrame.Value(SettingCompression)
262 if !ok {
263 m.compressionQuality = compressionPresets[CompressionNone]
264 return nil
265 }
266 ver, fmt, sz, nd := parseCompressionSettingVal(peerCompression)
267 if ver != compressionVersion || fmt != compressionFormat || sz == 0 || nd == 0 {
268 m.compressionQuality = compressionPresets[CompressionNone]
269 return nil
270 }
271 // Values used for compression are the mimimum between the two peers
272 if sz < m.compressionQuality.dictSize {
273 m.compressionQuality.dictSize = sz
274 }
275 if nd < m.compressionQuality.nDicts {
276 m.compressionQuality.nDicts = nd
277 }
278 return nil
279}
280
281func (m *Muxer) readPeerSettingsAck() error {
282 frame, err := m.f.ReadFrame()
283 if err != nil {
284 return err
285 }
286 settingsFrame, ok := frame.(*http2.SettingsFrame)
287 if !ok {
288 return ErrBadHandshakeNotSettingsAck
289 }
290 if settingsFrame.Header().Flags != http2.FlagSettingsAck {
291 return ErrBadHandshakeUnexpectedSettings
292 }
293 return nil
294}
295
296func joinErrorsWithTimeout(errChan <-chan error, receiveCount int, timeout time.Duration, timeoutError error) error {
297 for i := 0; i < receiveCount; i++ {
298 select {
299 case err := <-errChan:
300 if err != nil {
301 return err
302 }
303 case <-time.After(timeout):
304 return timeoutError
305 }
306 }
307 return nil
308}
309
310// Serve runs the event loops that comprise h2mux:
311// - MuxReader.run()
312// - MuxWriter.run()
313// - muxMetricsUpdater.run()
314// In the normal case, Shutdown() is called concurrently with Serve() to stop
315// these loops.
316func (m *Muxer) Serve(ctx context.Context) error {
317 errGroup, _ := errgroup.WithContext(ctx)
318 errGroup.Go(func() error {
319 err := m.muxReader.run(m.config.Logger)
320 m.explicitShutdown.Fuse(false)
321 m.r.Close()
322 m.abort()
323 return err
324 })
325
326 errGroup.Go(func() error {
327 err := m.muxWriter.run(m.config.Logger)
328 m.explicitShutdown.Fuse(false)
329 m.w.Close()
330 m.abort()
331 return err
332 })
333
334 errGroup.Go(func() error {
335 err := m.muxMetricsUpdater.run(m.config.Logger)
336 return err
337 })
338
339 err := errGroup.Wait()
340 if isUnexpectedTunnelError(err, m.explicitShutdown.Value()) {
341 return err
342 }
343 return nil
344}
345
346// Shutdown is called to initiate the "happy path" of muxer termination.
347func (m *Muxer) Shutdown() {
348 m.explicitShutdown.Fuse(true)
349 m.muxReader.Shutdown()
350}
351
352// IsUnexpectedTunnelError identifies errors that are expected when shutting down the h2mux tunnel.
353// The set of expected errors change depending on whether we initiated shutdown or not.
354func isUnexpectedTunnelError(err error, expectedShutdown bool) bool {
355 if err == nil {
356 return false
357 }
358 if !expectedShutdown {
359 return true
360 }
361 return !isConnectionClosedError(err)
362}
363
364func isConnectionClosedError(err error) bool {
365 if err == io.EOF {
366 return true
367 }
368 if err == io.ErrClosedPipe {
369 return true
370 }
371 if err.Error() == "tls: use of closed connection" {
372 return true
373 }
374 if strings.HasSuffix(err.Error(), "use of closed network connection") {
375 return true
376 }
377 return false
378}
379
380// OpenStream opens a new data stream with the given headers.
381// Called by proxy server and tunnel
382func (m *Muxer) OpenStream(headers []Header, body io.Reader) (*MuxedStream, error) {
383 stream := &MuxedStream{
384 responseHeadersReceived: make(chan struct{}),
385 readBuffer: NewSharedBuffer(),
386 writeBuffer: &bytes.Buffer{},
387 writeBufferMaxLen: m.config.StreamWriteBufferMaxLen,
388 writeBufferHasSpace: make(chan struct{}, 1),
389 receiveWindow: m.config.DefaultWindowSize,
390 receiveWindowCurrentMax: m.config.DefaultWindowSize,
391 receiveWindowMax: m.config.MaxWindowSize,
392 sendWindow: m.config.DefaultWindowSize,
393 readyList: m.readyList,
394 writeHeaders: headers,
395 dictionaries: m.muxReader.dictionaries,
396 }
397
398 select {
399 // Will be received by mux writer
400 case m.newStreamChan <- MuxedStreamRequest{stream: stream, body: body}:
401 case <-m.abortChan:
402 return nil, ErrConnectionClosed
403 }
404 select {
405 case <-stream.responseHeadersReceived:
406 return stream, nil
407 case <-m.abortChan:
408 return nil, ErrConnectionClosed
409 }
410}
411
412func (m *Muxer) Metrics() *MuxerMetrics {
413 return m.muxMetricsUpdater.metrics()
414}
415
416func (m *Muxer) abort() {
417 m.abortOnce.Do(func() {
418 close(m.abortChan)
419 m.readyList.Close()
420 m.streams.Abort()
421 })
422}
423
424// Return how many retries/ticks since the connection was last marked active
425func (m *Muxer) TimerRetries() uint64 {
426 return m.muxWriter.idleTimer.RetryCount()
427}
428