cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2018.10.3

Branches

Tags

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

Clone

HTTPS

Download ZIP

h2mux/h2mux.go

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