@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 975 lines 30 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // Most of the code in this file was initially borrowed from the Go
16 // standard library and modified; It had this copyright notice:
17 // Copyright 2011 The Go Authors
18
19 package reverseproxy
20
21 import (
22 "bufio"
23 "context"
24 "errors"
25 "fmt"
26 "io"
27 weakrand "math/rand/v2"
28 "mime"
29 "net"
30 "net/http"
31 "sync"
32 "sync/atomic"
33 "time"
34 "unsafe"
35
36 "go.uber.org/zap"
37 "go.uber.org/zap/zapcore"
38 "golang.org/x/net/http/httpguts"
39
40 "github.com/caddyserver/caddy/v2"
41 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
42 )
43
44 type extendedConnectReadWriteCloser struct {
45 io.ReadCloser
46 http.ResponseWriter
47 }
48
49 func (rwc extendedConnectReadWriteCloser) Write(p []byte) (n int, err error) {
50 n, err = rwc.ResponseWriter.Write(p)
51 if err != nil {
52 return 0, err
53 }
54
55 //nolint:bodyclose
56 err = http.NewResponseController(rwc.ResponseWriter).Flush()
57 if err != nil {
58 return 0, err
59 }
60 return n, nil
61 }
62
63 // SetWriteDeadline bounds writes to the extended CONNECT stream, so a peer
64 // that stopped reading cannot block the close message forever.
65 func (rwc extendedConnectReadWriteCloser) SetWriteDeadline(deadline time.Time) error {
66 //nolint:bodyclose
67 return http.NewResponseController(rwc.ResponseWriter).SetWriteDeadline(deadline)
68 }
69
70 // upstreamConnCtxKey is the context key for the *atomic.Pointer[net.Conn]
71 // recording the connection a websocket upgrade's round trip used.
72 const upstreamConnCtxKey caddy.CtxKey = "reverse_proxy.upstream_conn"
73
74 // upstreamWriteCloser returns where to write the close message to the
75 // upstream. The write must be bounded by a deadline, but for HTTP/1
76 // upgrades net/http hands the upstream socket back inside a body type that
77 // hides its deadline methods and writes straight through to the socket, so
78 // the close message goes to the socket recorded during the round trip
79 // instead. An HTTP/1 connection is dedicated to this request; HTTP/2 and
80 // HTTP/3 share theirs between streams, so their bodies are used as they are.
81 func upstreamWriteCloser(req *http.Request, res *http.Response, backConn io.ReadWriteCloser) io.ReadWriteCloser {
82 if res.ProtoMajor != 1 {
83 return backConn
84 }
85 recorded, ok := req.Context().Value(upstreamConnCtxKey).(*atomic.Pointer[net.Conn])
86 if !ok {
87 return backConn
88 }
89 if uc := recorded.Load(); uc != nil {
90 return *uc
91 }
92 return backConn
93 }
94
95 func (h *Handler) handleUpgradeResponse(logger *zap.Logger, rw http.ResponseWriter, req *http.Request, res *http.Response, upstreamAddr string) {
96 // Until the tunnel takes ownership, every setup failure must close the
97 // backend body and any hijacked client connection. net/http no longer
98 // owns the client socket after a successful hijack.
99 var conn io.ReadWriteCloser
100 tunnelOwnsConnections := false
101 defer func() {
102 if !tunnelOwnsConnections {
103 res.Body.Close()
104 if conn != nil {
105 conn.Close()
106 }
107 }
108 }()
109 reqUpType := upgradeType(req.Header)
110 resUpType := upgradeType(res.Header)
111
112 // Taken from https://github.com/golang/go/commit/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a
113 // We know reqUpType is ASCII, it's checked by the caller.
114 if !asciiIsPrint(resUpType) {
115 if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to invalid protocol"); c != nil {
116 c.Write(zap.String("backend_upgrade", resUpType))
117 }
118 return
119 }
120 if !asciiEqualFold(reqUpType, resUpType) {
121 if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to unexpected protocol via Upgrade header"); c != nil {
122 c.Write(
123 zap.String("backend_upgrade", resUpType),
124 zap.String("requested_upgrade", reqUpType),
125 )
126 }
127 return
128 }
129
130 backConn, ok := res.Body.(io.ReadWriteCloser)
131 if !ok {
132 logger.Error("internal error: 101 switching protocols response with non-writable body")
133 return
134 }
135
136 // write header first, response headers should not be counted in size
137 // like the rest of handler chain.
138 copyHeader(rw.Header(), res.Header)
139
140 // Capture all h fields needed by the tunnel now, so that the Handler (h)
141 // is not referenced after this function returns (for HTTP/1.1 hijacked
142 // connections the tunnel runs in a detached goroutine).
143 tunnel := h.tunnelTracker
144 bufferSize := h.StreamBufferSize
145 streamTimeout := time.Duration(h.StreamTimeout)
146
147 var (
148 brw *bufio.ReadWriter
149 detached bool
150 )
151 // websocket over http2 or http3 if extended connect is enabled,
152 // assuming backend doesn't support this, the request will be
153 // modified to http1.1 upgrade
154 // TODO: once we can reliably detect backend support this, it can
155 // be removed for those backends
156 if body, ok := caddyhttp.GetVar(req.Context(), "extended_connect_websocket_body").(io.ReadCloser); ok {
157 // websocket over extended connect can't be detached. rw and req.Body
158 // are only valid while the handler goroutine is running
159 req.Body = body
160 rw.Header().Del("Upgrade")
161 rw.Header().Del("Connection")
162 rw.Header().Del("Sec-WebSocket-Accept")
163 rw.WriteHeader(http.StatusOK)
164
165 if c := logger.Check(zap.DebugLevel, "upgrading connection"); c != nil {
166 c.Write(zap.Int("http_version", req.ProtoMajor))
167 }
168
169 //nolint:bodyclose
170 flushErr := http.NewResponseController(rw).Flush()
171 if flushErr != nil {
172 if c := h.logger.Check(zap.ErrorLevel, "failed to flush extended_connect websocket response"); c != nil {
173 c.Write(zap.Error(flushErr))
174 }
175 return
176 }
177 conn = extendedConnectReadWriteCloser{req.Body, rw}
178 // bufio is not needed, use minimal buffer
179 brw = bufio.NewReadWriter(bufio.NewReaderSize(conn, 1), bufio.NewWriterSize(conn, 1))
180 } else {
181 normalizeWebsocketHeaders(rw.Header())
182 if h.StreamDetached {
183 detached = caddyhttp.DetachResponseWriterAfterHijack(rw, req, true)
184 if !detached {
185 // Some outer writers may have accepted detachment before an
186 // inner writer refused it. Restore attached byte accounting
187 // and keep the handler running for the lifetime of the tunnel.
188 caddyhttp.DetachResponseWriterAfterHijack(rw, req, false)
189 if c := logger.Check(zap.DebugLevel, "detaching connection failed; keeping stream attached"); c != nil {
190 c.Write(zap.String("tip", "check if your response writers have an Unwrap method or if already hijacked"))
191 }
192 }
193 }
194 rw.WriteHeader(res.StatusCode)
195
196 if c := logger.Check(zap.DebugLevel, "upgrading connection"); c != nil {
197 c.Write(zap.Int("http_version", req.ProtoMajor))
198 }
199
200 var hijackErr error
201 //nolint:bodyclose
202 conn, brw, hijackErr = http.NewResponseController(rw).Hijack()
203 if errors.Is(hijackErr, http.ErrNotSupported) {
204 if c := h.logger.Check(zap.ErrorLevel, "can't switch protocols using non-Hijacker ResponseWriter"); c != nil {
205 c.Write(zap.String("type", fmt.Sprintf("%T", rw)))
206 }
207 return
208 }
209
210 if hijackErr != nil {
211 if c := h.logger.Check(zap.ErrorLevel, "hijack failed on protocol switch"); c != nil {
212 c.Write(zap.Error(hijackErr))
213 }
214 return
215 }
216 }
217
218 if err := brw.Flush(); err != nil {
219 if c := logger.Check(zapcore.DebugLevel, "response flush"); c != nil {
220 c.Write(zap.Error(err))
221 }
222 return
223 }
224
225 // There may be buffered data in the *bufio.Reader
226 // see: https://github.com/caddyserver/caddy/issues/6273
227 if buffered := brw.Reader.Buffered(); buffered > 0 {
228 data, _ := brw.Peek(buffered)
229 _, err := backConn.Write(data)
230 if err != nil {
231 if c := logger.Check(zapcore.DebugLevel, "backConn write failed"); c != nil {
232 c.Write(zap.Error(err))
233 }
234 return
235 }
236 }
237
238 // Register both connections with the tunnel tracker. We also try to
239 // gracefully close connections we recognize as websockets. We need to make
240 // sure the client connection messages (i.e. to upstream) are masked, so we
241 // need to know whether the connection is considered the server or the
242 // client side of the proxy. Note that gracefulClose must not capture h,
243 // since the tunnel may outlive the handler instance.
244 gracefulClose := func(conn io.ReadWriteCloser, isClient bool) func(time.Time) error {
245 if isWebsocket(req) {
246 return func(deadline time.Time) error {
247 return writeCloseControl(conn, isClient, deadline)
248 }
249 }
250 return nil
251 }
252 deleteFrontConn := tunnel.registerConnection(conn, gracefulClose(conn, false), detached, upstreamAddr)
253 deleteBackConn := tunnel.registerConnection(backConn, gracefulClose(upstreamWriteCloser(req, res, backConn), true), detached, upstreamAddr)
254 if h.streamLogsSkipHandshake() {
255 caddyhttp.SetVar(req.Context(), caddyhttp.LogSkipVar, true)
256 }
257 repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
258 repl.Set("http.reverse_proxy.upgraded", true)
259 streamUUID, _ := repl.GetString("http.request.uuid")
260 streamFields := makeStreamLogFields(streamUUID)
261 streamLogger := h.streamLoggerForRequest(req)
262 streamLevel := h.streamLogLevel
263 finishMetrics := trackActiveStream(upstreamAddr)
264
265 start := time.Now()
266
267 tunnelOwnsConnections = true
268 if !detached {
269 handleUpgradeTunnel(
270 streamLogger,
271 streamLevel,
272 conn,
273 backConn,
274 deleteFrontConn,
275 deleteBackConn,
276 bufferSize,
277 streamTimeout,
278 start,
279 finishMetrics,
280 streamFields,
281 )
282 } else {
283 // start a new goroutine
284 go handleUpgradeTunnel(
285 streamLogger,
286 streamLevel,
287 conn,
288 backConn,
289 deleteFrontConn,
290 deleteBackConn,
291 bufferSize,
292 streamTimeout,
293 start,
294 finishMetrics,
295 streamFields,
296 )
297 }
298 }
299
300 // handleUpgradeTunnel returns when transfer is done.
301 func handleUpgradeTunnel(
302 streamLogger *zap.Logger,
303 streamLevel zapcore.Level,
304 conn io.ReadWriteCloser,
305 backConn io.ReadWriteCloser,
306 deleteFrontConn func(),
307 deleteBackConn func(),
308 bufferSize int,
309 streamTimeout time.Duration,
310 start time.Time,
311 finishMetrics func(result string, duration time.Duration, toBackend int64, fromBackend int64),
312 streamFields []zap.Field,
313 ) {
314 defer deleteBackConn()
315 defer deleteFrontConn()
316 var (
317 wg sync.WaitGroup
318 toBackend int64
319 fromBackend int64
320 result string
321 )
322
323 // when a stream timeout is encountered, no error will be read from errc
324 // a buffer size of 2 will allow both the read and write goroutines to
325 // send the error and exit
326 // see: https://github.com/caddyserver/caddy/issues/7418
327 errc := make(chan error, 2)
328 spc := switchProtocolCopier{
329 user: conn,
330 backend: backConn,
331 wg: &wg,
332 bufferSize: bufferSize,
333 sent: &toBackend,
334 received: &fromBackend,
335 }
336 wg.Add(2)
337
338 var timeoutc <-chan time.Time
339 if streamTimeout > 0 {
340 timer := time.NewTimer(streamTimeout)
341 defer timer.Stop()
342 timeoutc = timer.C
343 }
344
345 go spc.copyToBackend(errc)
346 go spc.copyFromBackend(errc)
347 // Wait for both directions to finish. A clean EOF in one direction is a
348 // half-close of that direction only, not the end of the tunnel: the copier
349 // propagates it to the destination with CloseWrite and reports nil, so the
350 // other direction is left open and pending bytes can still drain. Anything
351 // else ends the tunnel: a copy error, a failed CloseWrite, or errCopyDone
352 // when the destination has no write half to close.
353 //
354 // net/http/httputil expresses the same wait as:
355 //
356 // err := <-errc
357 // if err == nil {
358 // err = <-errc
359 // }
360 //
361 // which cannot be used verbatim here because this handler also selects on the
362 // stream timeout, so the wait is repeated inside that select instead. Both
363 // accept the same sequences; the loop only additionally lets the timeout win
364 // at either wait.
365 //
366 // See https://github.com/caddyserver/caddy/issues/8026, and the same class
367 // fixed upstream in net/http/httputil (https://go.dev/issue/35892).
368 result = "closed"
369 copyLoop:
370 for range 2 {
371 select {
372 case err := <-errc:
373 if err != nil {
374 result = classifyStreamResult(err)
375 if c := streamLogger.Check(streamLevel, "streaming error"); c != nil && !errors.Is(err, errCopyDone) {
376 c.Write(zap.Error(err))
377 }
378 break copyLoop
379 }
380 case t := <-timeoutc:
381 result = "timeout"
382 if c := streamLogger.Check(streamLevel, "stream timed out"); c != nil {
383 c.Write(zap.Time("timeout", t))
384 }
385 break copyLoop
386 }
387 }
388
389 // Close both ends to unblock the still-running copy goroutine,
390 // then wait for it so byte counts are final before metrics/logging.
391 conn.Close()
392 backConn.Close()
393 wg.Wait()
394
395 finishMetrics(result, time.Since(start), toBackend, fromBackend)
396 if c := streamLogger.Check(streamLevel, "connection closed"); c != nil {
397 fields := append([]zap.Field{}, streamFields...)
398 fields = append(fields,
399 zap.Duration("duration", time.Since(start)),
400 zap.Int64("bytes_to_backend", toBackend),
401 zap.Int64("bytes_from_backend", fromBackend),
402 )
403 c.Write(fields...)
404 }
405 }
406
407 func classifyStreamResult(err error) string {
408 if err == nil ||
409 errors.Is(err, errCopyDone) ||
410 errors.Is(err, io.EOF) ||
411 errors.Is(err, net.ErrClosed) ||
412 errors.Is(err, context.Canceled) {
413 return "closed"
414 }
415 return "error"
416 }
417
418 func makeStreamLogFields(streamUUID string) []zap.Field {
419 fields := make([]zap.Field, 0, 1)
420 if streamUUID != "" {
421 fields = append(fields, zap.String("uuid", streamUUID))
422 }
423 return fields
424 }
425
426 // flushInterval returns the p.FlushInterval value, conditionally
427 // overriding its value for a specific request/response.
428 func (h Handler) flushInterval(req *http.Request, res *http.Response, incremental bool) time.Duration {
429 // The upstream asked for the response to be forwarded incrementally.
430 // See RFC 10036: https://www.rfc-editor.org/rfc/rfc10036.html
431 if incremental || caddyhttp.IsIncremental(res.Header) {
432 return -1 // negative means immediately
433 }
434
435 resCTHeader := res.Header.Get("Content-Type")
436 resCT, _, err := mime.ParseMediaType(resCTHeader)
437
438 // For Server-Sent Events responses, flush immediately.
439 // The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream
440 if err == nil && resCT == "text/event-stream" {
441 return -1
442 }
443
444 // We might have the case of streaming for which Content-Length might be unset.
445 if res.ContentLength == -1 {
446 return -1
447 }
448
449 return time.Duration(h.FlushInterval)
450 }
451
452 func (h Handler) copyResponse(dst http.ResponseWriter, src io.Reader, flushInterval time.Duration, logger *zap.Logger) error {
453 var w io.Writer = dst
454
455 if flushInterval != 0 {
456 var mlwLogger *zap.Logger
457 if h.VerboseLogs {
458 mlwLogger = logger.Named("max_latency_writer")
459 } else {
460 mlwLogger = zap.NewNop()
461 }
462 mlw := &maxLatencyWriter{
463 dst: dst,
464 //nolint:bodyclose
465 flush: http.NewResponseController(dst).Flush,
466 latency: flushInterval,
467 logger: mlwLogger,
468 }
469 defer mlw.stop()
470
471 // set up initial timer so headers get flushed even if body writes are delayed
472 mlw.flushPending = true
473 mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
474
475 w = mlw
476 }
477
478 buf := streamingBufPool.Get().(*[]byte)
479 defer streamingBufPool.Put(buf)
480
481 var copyLogger *zap.Logger
482 if h.VerboseLogs {
483 copyLogger = logger
484 } else {
485 copyLogger = zap.NewNop()
486 }
487
488 _, err := h.copyBuffer(w, src, *buf, copyLogger)
489 return err
490 }
491
492 // errWritingDownstream wraps errors from writing the response to the client,
493 // to tell them apart from errors reading the response from the backend.
494 var errWritingDownstream = errors.New("writing")
495
496 // copyBuffer returns any write errors or non-EOF read errors, and the amount
497 // of bytes written.
498 func (h Handler) copyBuffer(dst io.Writer, src io.Reader, buf []byte, logger *zap.Logger) (int64, error) {
499 if len(buf) == 0 {
500 buf = make([]byte, defaultBufferSize)
501 }
502 var written int64
503 for {
504 logger.Debug("waiting to read from upstream")
505 nr, rerr := src.Read(buf)
506 logger := logger.With(zap.Int("read", nr))
507 if c := logger.Check(zapcore.DebugLevel, "read from upstream"); c != nil {
508 c.Write(zap.Error(rerr))
509 }
510 if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
511 // TODO: this could be useful to know (indeed, it revealed an error in our
512 // fastcgi PoC earlier; but it's this single error report here that necessitates
513 // a function separate from io.CopyBuffer, since io.CopyBuffer does not distinguish
514 // between read or write errors; in a reverse proxy situation, write errors are not
515 // something we need to report to the client, but read errors are a problem on our
516 // end for sure. so we need to decide what we want.)
517 // p.logf("copyBuffer: ReverseProxy read error during body copy: %v", rerr)
518 if c := logger.Check(zapcore.ErrorLevel, "reading from backend"); c != nil {
519 c.Write(zap.Error(rerr))
520 }
521 }
522 if nr > 0 {
523 logger.Debug("writing to downstream")
524 nw, werr := dst.Write(buf[:nr])
525 if nw > 0 {
526 written += int64(nw)
527 }
528 if c := logger.Check(zapcore.DebugLevel, "wrote to downstream"); c != nil {
529 c.Write(
530 zap.Int("written", nw),
531 zap.Int64("written_total", written),
532 zap.Error(werr),
533 )
534 }
535 if werr != nil {
536 return written, fmt.Errorf("%w: %w", errWritingDownstream, werr)
537 }
538 if nr != nw {
539 return written, io.ErrShortWrite
540 }
541 }
542 if rerr != nil {
543 if rerr == io.EOF {
544 return written, nil
545 }
546 return written, fmt.Errorf("reading: %w", rerr)
547 }
548 }
549 }
550
551 // openConnection maps an open connection to an optional function for graceful
552 // close and records which upstream address the connection is proxying to.
553 // Also tracks whether the connection is detached, which means it should only be
554 // closed when the upstream is removed from the config, not on every reload.
555 type openConnection struct {
556 conn io.ReadWriteCloser
557 gracefulClose func(deadline time.Time) error
558 detached bool
559 upstream string
560 }
561
562 // tunnelTracker tracks hijacked/upgraded connections for selective cleanup.
563 // This exists to detach the lifecycle of streaming connections from the proxy
564 // Handler and config, since we typically want them to survive past config reloads.
565 // It also allows for selective connection cleanup based on their attachment status.
566 type tunnelTracker struct {
567 connections map[io.ReadWriteCloser]openConnection
568 closeTimer *time.Timer
569 closeDelay time.Duration
570 stopped bool
571 mu sync.Mutex
572 logger *zap.Logger
573 }
574
575 func newTunnelTracker(logger *zap.Logger, closeDelay time.Duration) *tunnelTracker {
576 return &tunnelTracker{
577 connections: make(map[io.ReadWriteCloser]openConnection),
578 closeDelay: closeDelay,
579 logger: logger,
580 }
581 }
582
583 // registerConnection stores conn in the tracking map. The caller must invoke
584 // the returned del func when the connection is done.
585 func (ts *tunnelTracker) registerConnection(conn io.ReadWriteCloser, gracefulClose func(deadline time.Time) error, detached bool, upstream string) (del func()) {
586 ts.mu.Lock()
587 ts.connections[conn] = openConnection{conn, gracefulClose, detached, upstream}
588 ts.mu.Unlock()
589 return func() {
590 ts.mu.Lock()
591 delete(ts.connections, conn)
592 empty := len(ts.connections) == 0 && ts.stopped
593 if empty {
594 if ts.closeTimer != nil {
595 if ts.closeTimer.Stop() {
596 ts.logger.Debug("stopped streaming connections close timer - all connections are already closed")
597 }
598 ts.closeTimer = nil
599 }
600 }
601 ts.mu.Unlock()
602 if empty {
603 unregisterDetachedTunnelTrackers(ts)
604 }
605 }
606 }
607
608 // closeAttachedConnections closes all tracked attached connections.
609 func (ts *tunnelTracker) closeAttachedConnections() error {
610 ts.mu.Lock()
611 defer ts.mu.Unlock()
612 // detached connections are only closed when the upstream is gone from the config
613 return ts.closeConnectionsLocked(time.Now().Add(closeControlWriteTimeout), func(oc openConnection) bool {
614 return !oc.detached
615 })
616 }
617
618 // closeConnectionsLocked sends a close message to, then closes, every
619 // tracked connection that match selects. The caller must hold mu. Every
620 // graceful close shares one absolute deadline: the loop is serial and holds
621 // the lock, so per-connection timeouts would add up across stalled peers.
622 // All close messages are sent before any connection is closed, because
623 // closing one side of a tunnel makes its copy loops close the other side,
624 // which would lose a close message still waiting to be sent there.
625 // The first error is returned.
626 func (ts *tunnelTracker) closeConnectionsLocked(deadline time.Time, match func(openConnection) bool) error {
627 var batch []openConnection
628 for _, oc := range ts.connections {
629 if match(oc) {
630 batch = append(batch, oc)
631 }
632 }
633
634 var err error
635 for _, oc := range batch {
636 if oc.gracefulClose != nil {
637 if gracefulErr := oc.gracefulClose(deadline); gracefulErr != nil && err == nil {
638 err = gracefulErr
639 }
640 }
641 }
642 for _, oc := range batch {
643 // The other side of a tunnel may already have closed this one, which
644 // is the expected outcome rather than an error worth logging.
645 if closeErr := oc.conn.Close(); closeErr != nil && !errors.Is(closeErr, net.ErrClosed) && err == nil {
646 err = closeErr
647 }
648 }
649 return err
650 }
651
652 // cleanupAttachedConnections closes upgraded attached connections.
653 // Depending on closeDelay it does that either immediately or after a timer.
654 func (ts *tunnelTracker) cleanupAttachedConnections() error {
655 ts.mu.Lock()
656 // Mark the tracker stopped before upstream-removal notifications, even
657 // when attached connections will be closed after a delay.
658 ts.stopped = true
659 if len(ts.connections) == 0 {
660 ts.mu.Unlock()
661 unregisterDetachedTunnelTrackers(ts)
662 return nil
663 }
664 if ts.closeDelay == 0 {
665 ts.mu.Unlock()
666 return ts.closeAttachedConnections()
667 }
668
669 defer ts.mu.Unlock()
670 delay := ts.closeDelay
671 ts.closeTimer = time.AfterFunc(delay, func() {
672 if c := ts.logger.Check(zapcore.DebugLevel, "closing streaming connections after delay"); c != nil {
673 c.Write(zap.Duration("delay", delay))
674 }
675 err := ts.closeAttachedConnections()
676 if err != nil {
677 if c := ts.logger.Check(zapcore.ErrorLevel, "failed to close connections after delay"); c != nil {
678 c.Write(
679 zap.Error(err),
680 zap.Duration("delay", delay),
681 )
682 }
683 }
684 })
685 return nil
686 }
687
688 // writeCloseControl sends a best-effort Close control message to the given
689 // WebSocket connection. Thanks to @pascaldekloe who provided inspiration
690 // from his simple implementation of this I was able to learn from at:
691 // github.com/pascaldekloe/websocket. Further work for handling masking
692 // taken from github.com/gorilla/websocket.
693 //
694 // The write must finish by deadline: a peer that stopped reading leaves the
695 // tunnel's copy goroutine stuck writing, and the close message would wait
696 // behind it forever while the caller holds the tunnel tracker lock. The
697 // connection is closed right after, so the deadline needs no reset.
698 // Connections that cannot set a write deadline are skipped instead.
699 func writeCloseControl(conn io.Writer, isClient bool, deadline time.Time) error {
700 dc, ok := conn.(interface{ SetWriteDeadline(time.Time) error })
701 if !ok {
702 return nil
703 }
704 if err := dc.SetWriteDeadline(deadline); err != nil {
705 if errors.Is(err, http.ErrNotSupported) {
706 return nil
707 }
708 return err
709 }
710
711 // Sources:
712 // https://github.com/pascaldekloe/websocket/blob/32050af67a5d/websocket.go#L119
713 // https://github.com/gorilla/websocket/blob/v1.5.0/conn.go#L413
714
715 // For now, we're not using a reason. We might later, though.
716 // The code handling the reason is left in
717 var reason string // max 123 bytes (control frame payload limit is 125; status code takes 2)
718
719 const closeMessage = 8
720 const finalBit = 1 << 7 // Frame header byte 0 bits from Section 5.2 of RFC 6455
721 const maskBit = 1 << 7 // Frame header byte 1 bits from Section 5.2 of RFC 6455
722 const goingAwayUpper uint8 = 1001 >> 8
723 const goingAwayLower uint8 = 1001 & 0xff
724
725 b0 := byte(closeMessage) | finalBit
726 b1 := byte(len(reason) + 2)
727 if isClient {
728 b1 |= maskBit
729 }
730
731 buf := make([]byte, 0, 127)
732 buf = append(buf, b0, b1)
733 msgLength := 4 + len(reason)
734
735 // Both branches below append the "going away" code and reason
736 appendMessage := func(buf []byte) []byte {
737 buf = append(buf, goingAwayUpper, goingAwayLower)
738 buf = append(buf, []byte(reason)...)
739 return buf
740 }
741
742 // When we're the client, we need to mask the message as per
743 // https://www.rfc-editor.org/rfc/rfc6455#section-5.3
744 if isClient {
745 key := newMaskKey()
746 buf = append(buf, key[:]...)
747 msgLength += len(key)
748 buf = appendMessage(buf)
749 maskBytes(key, 0, buf[2+len(key):])
750 } else {
751 buf = appendMessage(buf)
752 }
753
754 // simply best-effort, but return error for logging purposes
755 // TODO: we might need to ensure we are the exclusive writer by this point (io.Copy is stopped)?
756 _, err := conn.Write(buf[:msgLength])
757 return err
758 }
759
760 // closeControlWriteTimeout is how long one batch of graceful closes waits
761 // for peers to accept their close messages before giving up on them. It is
762 // a variable so tests can shorten it.
763 var closeControlWriteTimeout = 5 * time.Second
764
765 // Copied from https://github.com/gorilla/websocket/blob/v1.5.0/mask.go
766 func maskBytes(key [4]byte, pos int, b []byte) int {
767 // Mask one byte at a time for small buffers.
768 if len(b) < 2*wordSize {
769 for i := range b {
770 b[i] ^= key[pos&3]
771 pos++
772 }
773 return pos & 3
774 }
775
776 // Mask one byte at a time to word boundary.
777 if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
778 n = wordSize - n
779 for i := range b[:n] {
780 b[i] ^= key[pos&3]
781 pos++
782 }
783 b = b[n:]
784 }
785
786 // Create aligned word size key.
787 var k [wordSize]byte
788 for i := range k {
789 k[i] = key[(pos+i)&3] // nolint:gosec // false positive, impossible to be out of bounds; see: https://github.com/securego/gosec/issues/1525
790 }
791 kw := *(*uintptr)(unsafe.Pointer(&k))
792
793 // Mask one word at a time.
794 n := (len(b) / wordSize) * wordSize
795 for i := 0; i < n; i += wordSize {
796 *(*uintptr)(unsafe.Add(unsafe.Pointer(&b[0]), i)) ^= kw
797 }
798
799 // Mask one byte at a time for remaining bytes.
800 b = b[n:]
801 for i := range b {
802 b[i] ^= key[pos&3]
803 pos++
804 }
805
806 return pos & 3
807 }
808
809 // Copied from https://github.com/gorilla/websocket/blob/v1.5.0/conn.go#L184
810 func newMaskKey() [4]byte {
811 n := weakrand.Uint32()
812 return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
813 }
814
815 // isWebsocket returns true if r looks to be an upgrade request for WebSockets.
816 // It is a fairly naive check.
817 func isWebsocket(r *http.Request) bool {
818 return httpguts.HeaderValuesContainsToken(r.Header["Connection"], "upgrade") &&
819 httpguts.HeaderValuesContainsToken(r.Header["Upgrade"], "websocket")
820 }
821
822 // closeConnectionsForUpstream closes detached connections to the given
823 // upstream on stopped trackers. Attached connections keep their close delay.
824 // The deadline is shared so one upstream removal is bounded across every
825 // tracker it notifies.
826 func (ts *tunnelTracker) closeConnectionsForUpstream(addr string, deadline time.Time) error {
827 ts.mu.Lock()
828 defer ts.mu.Unlock()
829 if !ts.stopped {
830 return nil
831 }
832 return ts.closeConnectionsLocked(deadline, func(oc openConnection) bool {
833 return oc.detached && oc.upstream == addr
834 })
835 }
836
837 type maxLatencyWriter struct {
838 dst io.Writer
839 flush func() error
840 latency time.Duration // non-zero; negative means to flush immediately
841
842 mu sync.Mutex // protects t, flushPending, and dst.Flush
843 t *time.Timer
844 flushPending bool
845 logger *zap.Logger
846 }
847
848 func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
849 m.mu.Lock()
850 defer m.mu.Unlock()
851 n, err = m.dst.Write(p)
852 if c := m.logger.Check(zapcore.DebugLevel, "wrote bytes"); c != nil {
853 c.Write(zap.Int("n", n), zap.Error(err))
854 }
855 if m.latency < 0 {
856 m.logger.Debug("flushing immediately")
857 //nolint:errcheck
858 m.flush()
859 return n, err
860 }
861 if m.flushPending {
862 m.logger.Debug("delayed flush already pending")
863 return n, err
864 }
865 if m.t == nil {
866 m.t = time.AfterFunc(m.latency, m.delayedFlush)
867 } else {
868 m.t.Reset(m.latency)
869 }
870 if c := m.logger.Check(zapcore.DebugLevel, "timer set for delayed flush"); c != nil {
871 c.Write(zap.Duration("duration", m.latency))
872 }
873 m.flushPending = true
874 return n, err
875 }
876
877 func (m *maxLatencyWriter) delayedFlush() {
878 m.mu.Lock()
879 defer m.mu.Unlock()
880 if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
881 m.logger.Debug("delayed flush is not pending")
882 return
883 }
884 m.logger.Debug("delayed flush")
885 //nolint:errcheck
886 m.flush()
887 m.flushPending = false
888 }
889
890 func (m *maxLatencyWriter) stop() {
891 m.mu.Lock()
892 defer m.mu.Unlock()
893 m.flushPending = false
894 if m.t != nil {
895 m.t.Stop()
896 }
897 }
898
899 // switchProtocolCopier exists so goroutines proxying data back and
900 // forth have nice names in stacks.
901 type switchProtocolCopier struct {
902 user, backend io.ReadWriteCloser
903 wg *sync.WaitGroup
904 bufferSize int
905 // sent and received accumulate byte counts for each direction.
906 // They are written before wg.Done() and read after wg.Wait(), so no
907 // additional synchronization is needed beyond the WaitGroup barrier.
908 sent *int64 // bytes copied to backend; must be non-nil
909 received *int64 // bytes copied from backend; must be non-nil
910 }
911
912 // errCopyDone is reported by a direction that ended at a clean EOF on a
913 // connection whose write half cannot be closed, so the end of that direction
914 // could not be propagated to the peer.
915 var errCopyDone = errors.New("hijacked connection copy complete")
916
917 func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
918 defer c.wg.Done()
919 n, err := io.CopyBuffer(c.user, c.backend, c.buffer())
920 *c.received = n
921 if err != nil {
922 errc <- err
923 return
924 }
925
926 // backend conn has reached EOF so propagate close write to user conn
927 if wc, ok := c.user.(interface{ CloseWrite() error }); ok {
928 errc <- wc.CloseWrite()
929 return
930 }
931
932 errc <- errCopyDone
933 }
934
935 func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
936 defer c.wg.Done()
937 n, err := io.CopyBuffer(c.backend, c.user, c.buffer())
938 *c.sent = n
939 if err != nil {
940 errc <- err
941 return
942 }
943
944 // user conn has reached EOF so propagate close write to backend conn
945 if wc, ok := c.backend.(interface{ CloseWrite() error }); ok {
946 errc <- wc.CloseWrite()
947 return
948 }
949
950 errc <- errCopyDone
951 }
952
953 func (c switchProtocolCopier) buffer() []byte {
954 size := c.bufferSize
955 if size <= 0 {
956 size = defaultBufferSize
957 }
958 return make([]byte, size)
959 }
960
961 var streamingBufPool = sync.Pool{
962 New: func() any {
963 // The Pool's New function should generally only return pointer
964 // types, since a pointer can be put into the return interface
965 // value without an allocation
966 // - (from the package docs)
967 b := make([]byte, defaultBufferSize)
968 return &b
969 },
970 }
971
972 const (
973 defaultBufferSize = 32 * 1024
974 wordSize = int(unsafe.Sizeof(uintptr(0)))
975 )