Repository navigation
Expand file tree
/
Copy pathserver.go
More file actions
2417 lines (2238 loc) · 75.3 KB
/
Copy pathserver.go
File metadata and controls
2417 lines (2238 loc) · 75.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2025 Sylvain Müller. All rights reserved.
// Mount of this source code is governed by a Apache-2.0 license that can be found
// at https://github.com/stdapi/edge/blob/main/LICENSE.txt.
package edge
import (
"cmp"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"iter"
"log"
"log/slog"
"maps"
"net"
"net/http"
"net/url"
"slices"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"github.com/quic-go/quic-go/qlogwriter"
"github.com/stdapi/edge/internal/stringsutil"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2"
)
type sessionKey struct{}
type serverConfig struct {
readHeaderTimeout time.Duration
readTimeout time.Duration
writeTimeout time.Duration
idleTimeout time.Duration
sniffTimeout time.Duration
maxHeaderBytes int
maxFrameBytes int
maxMessageBytes int
maxConcurrentStreams int
}
func defaultServerConfig() serverConfig {
return serverConfig{
readHeaderTimeout: 10 * time.Second,
idleTimeout: 90 * time.Second,
sniffTimeout: time.Second,
maxHeaderBytes: 1 << 20,
maxFrameBytes: 1 << 20,
maxMessageBytes: defaultMaxMessageBytes,
maxConcurrentStreams: 250,
}
}
// server owns everything that is not routing: the listeners, the connections accepted on
// them, and the transports used to reach upstreams.
type server struct {
// cutCtx is the parent of every request context. A cut cancels it with errCut, a drain
// does not.
cutCtx context.Context
// ctx is canceled when the proxy starts closing.
ctx context.Context
p *Proxy
fwd *forwarder
// h1, h2 and h3 serve the explicit proxy port over HTTP/1.1, HTTP/2 and QUIC. term and
// term2 serve the connections the proxy terminated itself, with origin semantics,
// over HTTP/1.1 and HTTP/2. newTerm and newFront2 build a server of the same kind for a
// connection that reaches its server once the drain shut it down.
h1 *http.Server
h2 *http.Server
h3 *http3.Server
term *http.Server
term2 *http.Server
newTerm func(*http.Protocols) *http.Server
newFront2 func() *http.Server
// late tracks the servers of serveLate, which the cut closes with its own.
late map[*http.Server]struct{}
listeners map[*net.Listener]struct{}
// active tracks connections the proxy owns outside of http.Server, which is every
// connection it terminated. It answers what a shutdown must close, so a connection enters
// it as soon as it is accepted, before anything has decided it.
active map[io.Closer]struct{}
// sessions is what [Proxy.Sessions] reports. A session enters it only once its target is
// resolved, so a caller never sees one it cannot pass to [Proxy.Admits].
sessions map[*Session]struct{}
// fronts tracks the connections accepted on an explicit TCP front, until a handler hijacks
// one.
fronts map[net.Conn]struct{}
// frontSess is the session of each front connection, net.Conn or *quic.Conn, whose closer
// the cut runs. dispatching keeps a front connection not yet handed to its server until
// dispatchFront lets it go and closes its channel: until then its end belongs to
// dispatchFront alone.
frontSess map[any]*Session
dispatching map[net.Conn]chan struct{}
// waiting keeps the front connections until they are handed to their server, and fresh the
// terminated HTTP/1.1 connections that have read no byte, which the drain ends.
waiting map[net.Conn]struct{}
fresh sync.Map
// quics tracks the QUIC demultiplexers. Their connections own their sessions under a
// separate lock, merged when the sessions are read rather than mirrored here, so no
// invariant spans the two locks.
quics map[*quicDemux]struct{}
// tunnels is the demultiplexer the CONNECT-UDP tunnels feed, brought up on the first
// tunnel. It has no socket, so one serves the whole proxy.
tunnels *quicDemux
cutCancel context.CancelCauseFunc
// cutEnded is closed once a cut has ended, whoever started it.
cutEnded chan struct{}
// frontStage is closed once the front stage of the cut began.
frontStage chan struct{}
cancel context.CancelFunc
// done is closed once Shutdown or Close has returned, when a front may close the socket
// its connections run on.
done chan struct{}
// requests lists the requests in flight.
requests *requestList
// serving counts the admitted sessions with a handler still running, except those a
// Session.Close ended before a cut, and the handlers of the CONNECT and CONNECT-UDP streams
// on HTTP/2 and HTTP/3 until they return.
serving gauge
// tracked counts the connections and the listeners Shutdown waits for, frontsLive the
// connections of the TCP fronts, and h3Live those of the HTTP/3 fronts from their first
// Initial.
tracked gauge
frontsLive gauge
h3Live gauge
h3Gate h3Gate
doneOne sync.Once
cutOnce sync.Once
// cutStarter records who started the cut, cutByDeadline or cutByClose.
cutStarter atomic.Uint32
// frontStageBy is the deadline of the front stage of the cut in nanoseconds since the epoch,
// zero before it, which the end dispatchFront gives a connection does not pass, I4.
frontStageBy atomic.Int64
mu sync.Mutex
closing atomic.Bool
// refusing is set at the start of a cut, cut once it has ended the requests, both guarded by
// mu.
refusing bool
cut bool
}
func newServer(p *Proxy) (*server, error) {
fwd, err := newForwarder(p)
if err != nil {
return nil, err
}
s := &server{
p: p,
fwd: fwd,
listeners: make(map[*net.Listener]struct{}),
active: make(map[io.Closer]struct{}),
sessions: make(map[*Session]struct{}),
fronts: make(map[net.Conn]struct{}),
frontSess: make(map[any]*Session),
dispatching: make(map[net.Conn]chan struct{}),
late: make(map[*http.Server]struct{}),
frontStage: make(chan struct{}),
waiting: make(map[net.Conn]struct{}),
quics: make(map[*quicDemux]struct{}),
done: make(chan struct{}),
cutEnded: make(chan struct{}),
requests: newRequestList(),
}
s.ctx, s.cancel = context.WithCancel(context.Background())
s.cutCtx, s.cutCancel = context.WithCancelCause(context.Background())
cutBase := func(net.Listener) context.Context { return s.cutCtx }
// HTTP/1.1 and HTTP/2 are served by servers of their own, so the drain disables the
// keep-alives of the first and shuts the second down for its GOAWAY. A plaintext HTTP/2
// client has no ALPN, so only unencrypted HTTP/2 serves it.
http1 := new(http.Protocols)
http1.SetHTTP1(true)
http2 := new(http.Protocols)
http2.SetHTTP2(true)
http2.SetUnencryptedHTTP2(true)
http2Cfg := &http.HTTP2Config{
MaxConcurrentStreams: p.cfg.server.maxConcurrentStreams,
MaxReadFrameSize: h2MaxReadFrameSize,
MaxDecoderHeaderTableSize: h2HeaderTableSize,
}
newFront := func(protocols *http.Protocols) *http.Server {
return &http.Server{
Handler: http.HandlerFunc(p.serveProxyRequest),
Protocols: protocols,
HTTP2: http2Cfg,
ReadHeaderTimeout: p.cfg.server.readHeaderTimeout,
ReadTimeout: p.cfg.server.readTimeout,
WriteTimeout: p.cfg.server.writeTimeout,
IdleTimeout: p.cfg.server.idleTimeout,
MaxHeaderBytes: p.cfg.server.maxHeaderBytes,
// One value more for the field a schemeConn adds.
MaxHeaderValueCount: http.DefaultMaxHeaderValueCount + 1,
ErrorLog: log.New(slogWriter{p.cfg.logger}, "", 0),
BaseContext: cutBase,
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
// The session outlives a cut of the requests. A connection without a wrapper gets no
// cancel, since a cancel nothing ever calls would leak.
sctx := context.WithoutCancel(ctx)
var cancelRequests context.CancelFunc
if cc := cancelConnOf(c); cc != nil {
var cancel context.CancelFunc
sctx, cancel = context.WithCancel(sctx)
// The requests of the connection end with its session, before the connection.
ctx, cancelRequests = context.WithCancel(ctx)
cc.arm(func() {
cancel()
cancelRequests()
})
}
sess := newSession(p, sctx, c, Target{}, KindHTTP, HTTP11)
sess.httpConn = c
sess.closer = normalEnd{c}
sess.cancelRequests = cancelRequests
if cancelRequests != nil {
// A session opened on a stream ends with the connection, I3.
sess.streamsCtx = ctx
}
if sc := cancelConnOf(c); sc != nil {
sc.sess.Store(sess)
}
s.mu.Lock()
if _, ok := s.fronts[c]; ok {
// The front stage may have ended the connection since it was handed over, I5.
s.frontSess[c] = sess
}
s.mu.Unlock()
return context.WithValue(ctx, sessionKey{}, sess)
},
ConnState: s.trackFront,
}
}
s.h1, s.h2 = newFront(http1), newFront(http2)
s.h3 = &http3.Server{
Handler: http.HandlerFunc(p.serveProxyRequest),
EnableDatagrams: true,
MaxHeaderBytes: p.cfg.server.maxHeaderBytes,
IdleTimeout: p.cfg.server.idleTimeout,
Logger: p.cfg.logger,
ConnContext: func(ctx context.Context, c *quic.Conn) context.Context {
s.h3Gate.counted(c)
// The handshake is over by the time a connection is handed here, so the session
// has its TLS state from the start.
sess := newSession(p, ctx, nil, Target{}, KindHTTP, HTTP3)
sess.remoteAddr, sess.localAddr = c.RemoteAddr(), c.LocalAddr()
state := c.ConnectionState().TLS
sess.tlsState.Store(&state)
sess.closer = quicConnEnd{c}
s.endIdleH3(ctx, c, sess)
// The requests of the connection end with its session, I1, and with the connection.
rctx, cancel := context.WithCancel(s.cutChild(ctx))
sess.cancelRequests = cancel
sess.streamsCtx = rctx
s.mu.Lock()
s.frontSess[c] = sess
s.mu.Unlock()
context.AfterFunc(c.Context(), func() {
s.mu.Lock()
delete(s.frontSess, c)
s.mu.Unlock()
})
return context.WithValue(rctx, sessionKey{}, sess)
},
}
s.newTerm = func(protocols *http.Protocols) *http.Server {
return &http.Server{
Handler: http.HandlerFunc(p.serveTerminatedRequest),
Protocols: protocols,
HTTP2: http2Cfg,
ReadHeaderTimeout: p.cfg.server.readHeaderTimeout,
ReadTimeout: p.cfg.server.readTimeout,
WriteTimeout: p.cfg.server.writeTimeout,
IdleTimeout: p.cfg.server.idleTimeout,
MaxHeaderBytes: p.cfg.server.maxHeaderBytes,
ErrorLog: log.New(slogWriter{p.cfg.logger}, "", 0),
// An OPTIONS * through a tunnel is about the origin, so it is routed like any request.
DisableGeneralOptionsHandler: true,
BaseContext: cutBase,
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
if sc := servedConnOf(c); sc != nil {
// The requests of the connection end with its session, I1, and with the
// connection.
rctx, cancel := context.WithCancel(ctx)
sc.sess.armRequests(cancel, nil)
return context.WithValue(rctx, sessionKey{}, sc.sess)
}
return ctx
},
ConnState: s.trackTerm,
}
}
s.term, s.term2 = s.newTerm(http1), s.newTerm(http2)
s.newFront2 = func() *http.Server { return newFront(http2) }
return s, nil
}
// ListenAndServe listens on addr and serves as an explicit forward proxy. Clients point at
// it with their proxy setting: plain HTTP requests carry an absolute target, HTTPS requests
// start with CONNECT, and UDP goes through a CONNECT-UDP tunnel, RFC 9298, an Upgrade over
// HTTP/1.1 or an extended CONNECT over HTTP/2. The HTTP/2 form needs GODEBUG=http2xconnect=1
// in the environment of the process.
//
// ListenAndServe always returns a non-nil error. After [Proxy.Shutdown] or [Proxy.Close] it
// returns [ErrProxyClosed].
func (p *Proxy) ListenAndServe(addr string) error {
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
// This call built the listener, and a refused Serve never takes it over.
defer func() { _ = ln.Close() }()
return p.Serve(ln)
}
// Serve accepts connections on ln and serves them as an explicit forward proxy.
// It always returns a non-nil error and closes ln. After [Proxy.Shutdown] or [Proxy.Close] has
// begun, the returned error is [ErrProxyClosed], once ln is closed.
//
// Prefer [Proxy.ServeTLS] over a TLS listener built by the caller. Such a listener still
// works when it returns [*tls.Conn], but the [Session.Context] of a connection of another type
// with a ConnectionState method never ends.
func (p *Proxy) Serve(ln net.Listener) error {
return p.serveFront(cancelListener{ln})
}
func (p *Proxy) serveFront(ln net.Listener) error {
// The drain or the cut closes the listener, and so does a failed accept, so the second
// close has to report nothing.
ln = &onceCloseListener{Listener: ln}
if !p.srv.tryTrackListener(&ln) {
// As net/http's Serve does.
_ = ln.Close()
return ErrProxyClosed
}
defer p.srv.untrackListener(&ln)
// The queues outlive the listener: a connection accepted before it closed is still being
// dispatched. Each server closes its queue, the front stage closing both.
q1, q2 := newConnQueue(ln.Addr()), newConnQueue(ln.Addr())
go func() { _ = p.srv.h1.Serve(q1) }()
go func() { _ = p.srv.h2.Serve(q2) }()
// The limit on a field the schemeConn decodes is above the server's, which refuses the
// request first.
sl := schemeListener{Listener: ln, p: p, maxHeaderBytes: p.cfg.server.maxHeaderBytes + 64<<10}
var delay time.Duration
for {
c, err := sl.Accept()
if err != nil {
if p.srv.closing.Load() {
return ErrProxyClosed
}
if temporaryAccept(err) {
delay = min(max(2*delay, 5*time.Millisecond), time.Second)
time.Sleep(delay)
continue
}
// The connections accepted before are still dispatched, and refused once the
// queues closed.
_ = ln.Close()
_ = q1.Close()
_ = q2.Close()
return err
}
delay = 0
if !p.srv.addFront(c) {
abort(c)
continue
}
go p.dispatchFront(c, q1, q2)
}
}
// dispatchFront hands c to the server of its protocol, the ALPN of a TLS connection or the
// preface of a plaintext one, read under the deadline net/http gives a handshake. An HTTP/2
// connection that reaches its server once the drain shut it down gets a server of its own, and
// one decided once a cut started gets its end here.
func (p *Proxy) dispatchFront(c net.Conn, q1, q2 *connQueue) {
h2, err := p.frontHTTP2(c)
sc := schemeConnOf(c)
if (!isTimeout(err) || sc == nil) && p.srv.handOver(c) {
// Handed to its server, which ends it from here.
if err != nil || !h2 {
// A failed handshake goes to the HTTP/1.1 server, which answers it as net/http does.
if !q1.push(c) {
p.srv.removeFront(c)
_ = c.Close()
}
return
}
if sc != nil {
// HTTP/2 has no header timeout of its own to bound.
sc.boundHeader(time.Time{})
}
if !q2.push(c) {
p.serveLate(c)
}
return
}
// No request arrived within the read header timeout, a failed cause, or within the T the drain
// gives a connection to begin one, or the cut started, a normal end each. The connection stays
// registered until its end returned, I5.
if sc != nil {
sc.beginEnd()
}
switch {
case errors.Is(err, errReadHeaderTimeout):
abortBy(c, p.srv.frontEndBy())
case p.srv.cutting():
// The front connections end in the front stage, once the units ended.
<-p.srv.frontStage
closeNormallyBy(c, p.srv.frontEndBy())
case p.srv.closing.Load():
p.srv.endDrained(c)
default:
abortBy(c, p.srv.frontEndBy())
}
p.srv.removeFront(c)
p.srv.letGo(c)
}
func (p *Proxy) frontHTTP2(c net.Conn) (h2 bool, err error) {
var header time.Time
if d := p.cfg.server.readHeaderTimeout; d > 0 {
header = time.Now().Add(d)
}
defer func() {
if isTimeout(err) && !header.IsZero() && !time.Now().Before(header) {
// The read header timeout expired, and not the T of the drain, which endWaiting
// may have set since.
err = fmt.Errorf("%w: %w", errReadHeaderTimeout, err)
}
}()
hs, isTLS := c.(interface {
HandshakeContext(context.Context) error
})
sc := schemeConnOf(c)
// Under the lock of endWaiting, so a drain that starts meanwhile cannot have its deadline
// replaced.
p.srv.mu.Lock()
if !header.IsZero() {
// The read header timeout bounds the handshake and the first request header, and the
// one of net/http counts from its own read, after this one.
_ = c.SetDeadline(header)
if sc != nil {
sc.boundHeader(header)
}
}
if !isTLS {
// A plaintext connection reaches its server at its accept.
p.reachedLocked(c, sc, header, false)
} else if header.IsZero() && p.srv.closing.Load() {
// The drain bounds a handshake that no read header timeout bounds, as drainBy does.
_ = c.SetDeadline(time.Now().Add(alertWriteTimeout))
}
p.srv.mu.Unlock()
// Off the waits first, so the drain sets no deadline past the reset.
defer func() {
p.srv.served(c)
_ = c.SetDeadline(time.Time{})
}()
if isTLS {
if err := hs.HandshakeContext(context.Background()); err != nil {
return false, err
}
// Read before the lock, so a handshake still running blocks no drain.
cs, ok := c.(connectionStater)
h2 := ok && cs.ConnectionState().NegotiatedProtocol == "h2"
p.srv.mu.Lock()
p.reachedLocked(c, sc, header, h2)
p.srv.mu.Unlock()
}
if sc != nil {
return sc.http2()
}
return false, nil
}
// reachedLocked records that c reached its server, past which the drain bounds the wait for its
// first request, and gives it T from here when the drain already started, docs/lifecycle.md The
// drain. It runs under the server lock, which endWaiting takes.
func (p *Proxy) reachedLocked(c net.Conn, sc *schemeConn, header time.Time, h2 bool) {
if sc != nil {
sc.markReached(p.srv.closing.Load(), h2)
}
if p.srv.closing.Load() {
deadline := time.Now().Add(alertWriteTimeout)
if !header.IsZero() && header.Before(deadline) {
deadline = header
}
_ = c.SetDeadline(deadline)
}
}
// endDrained gives c, a front connection the drain ends before it began a request, its normal
// end. A TLS connection that negotiated h2 gets an empty SETTINGS and a GOAWAY first, what the
// client sends before it read them is discarded, and its normal end follows within T,
// docs/lifecycle.md The drain.
func (s *server) endDrained(c net.Conn) {
sc := schemeConnOf(c)
cs, ok := c.(connectionStater)
if sc != nil && ok {
if state := cs.ConnectionState(); state.HandshakeComplete && state.NegotiatedProtocol == "h2" {
deadline := s.frontEndBy()
_ = sc.SetWriteDeadline(deadline)
fr := http2.NewFramer(sc.Conn, nil)
if fr.WriteSettings() == nil && fr.WriteGoAway(0, http2.ErrCodeNo, nil) == nil {
_ = sc.Conn.SetReadDeadline(deadline)
_, _ = io.Copy(io.Discard, sc.Conn)
}
}
}
closeNormallyBy(c, s.frontEndBy())
}
// frontEndBy is the deadline of an end dispatchFront begins now, T from now, or the deadline of
// the front stage of the cut when that one comes first, I4.
func (s *server) frontEndBy() time.Time {
deadline := time.Now().Add(alertWriteTimeout)
if n := s.frontStageBy.Load(); n != 0 && n < deadline.UnixNano() {
deadline = time.Unix(0, n)
}
return deadline
}
// serveLate serves c on a server of its own, shut down once it has the connection, which writes
// its GOAWAY. Once a cut started it leaves c to the front stage.
func (p *Proxy) serveLate(c net.Conn) {
srv := p.srv.newFront2()
p.srv.mu.Lock()
if p.srv.refusing {
p.srv.mu.Unlock()
return
}
p.srv.late[srv] = struct{}{}
p.srv.mu.Unlock()
defer func() {
p.srv.mu.Lock()
delete(p.srv.late, srv)
p.srv.mu.Unlock()
}()
shutdownOnceServing(srv)
q := newConnQueue(c.LocalAddr())
go func() { _ = q.push(c) }()
_ = srv.Serve(q)
}
// shutdownOnceServing shuts srv, the server of one connection, down at its first request, T after
// the HTTP/2 server registered the connection, which it has once the connection is idle or active,
// or at its close. Its GOAWAY then lets in the requests read by then.
func shutdownOnceServing(srv *http.Server) {
var once sync.Once
shutdown := func() { once.Do(func() { go func() { _ = srv.Shutdown(context.Background()) }() }) }
handler := srv.Handler
srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
shutdown()
handler.ServeHTTP(w, r)
})
next := srv.ConnState
var armed sync.Once
srv.ConnState = func(c net.Conn, state http.ConnState) {
if next != nil {
next(c, state)
}
switch state {
case http.StateIdle, http.StateActive:
armed.Do(func() { time.AfterFunc(alertWriteTimeout, shutdown) })
case http.StateClosed:
shutdown()
}
}
}
// onceCloseListener closes its listener once, and only the first call reports the result.
type onceCloseListener struct {
net.Listener
once sync.Once
}
func (l *onceCloseListener) Close() error {
var err error
l.once.Do(func() { err = l.Listener.Close() })
return err
}
// ListenAndServeTLS listens on addr and serves as an explicit forward proxy reached over
// TLS. The certificate authenticates the proxy itself, not the authorities it intercepts,
// so it must be one the client already trusts for that name.
func (p *Proxy) ListenAndServeTLS(addr string, cert tls.Certificate) error {
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
defer func() { _ = ln.Close() }()
return p.ServeTLS(ln, cert)
}
// ServeTLS accepts TLS connections on ln and serves them as an explicit forward proxy.
// It always returns a non-nil error and closes ln. After [Proxy.Shutdown] or [Proxy.Close] has
// begun, the returned error is [ErrProxyClosed], once ln is closed.
func (p *Proxy) ServeTLS(ln net.Listener, cert tls.Certificate) error {
cfg := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
NextProtos: []string{"h2", "http/1.1"},
}
// The cancel wrapper sits under the TLS listener, where it cannot hide the handshake
// from net/http.
return p.serveFront(tlsListener{Listener: cancelListener{ln}, cfg: cfg})
}
// ListenAndServeQUIC listens for UDP on addr and serves as an explicit forward proxy reached
// over HTTP/3. Like [Proxy.ListenAndServeTLS], the certificate authenticates the proxy itself.
//
// A client configured with it sends plain requests with an absolute target, CONNECT for a TCP
// origin, and CONNECT-UDP (RFC 9298) for a UDP origin, HTTP/3 origins included.
func (p *Proxy) ListenAndServeQUIC(addr string, cert tls.Certificate) error {
pc, err := net.ListenPacket("udp", addr)
if err != nil {
return err
}
defer func() { _ = pc.Close() }()
return p.ServeQUIC(pc, cert)
}
// ServeQUIC serves as an explicit forward proxy on the QUIC connections arriving on pc, see
// [Proxy.ListenAndServeQUIC]. It always returns a non-nil error and closes pc. After
// [Proxy.Shutdown] or [Proxy.Close] has begun, the returned error is [ErrProxyClosed], once pc
// is closed.
func (p *Proxy) ServeQUIC(pc net.PacketConn, cert tls.Certificate) error {
// During the drain the listener stays open and refuses each new connection with
// CONNECTION_REFUSED, until the start of the cut closes it.
tr := &quic.Transport{Conn: pc, ConnContext: func(ctx context.Context, _ *quic.ClientInfo) (context.Context, error) {
// Under the lock beginClose sets closing under, so a connection either is refused or
// is counted before Shutdown reads the count.
p.srv.mu.Lock()
defer p.srv.mu.Unlock()
if p.srv.closing.Load() {
return nil, ErrProxyClosed
}
t := &h3Token{g: &p.srv.h3Live}
t.g.add()
return context.WithValue(ctx, h3TokenKey{}, t), nil
}}
acceptCtx, stopAccept := context.WithCancel(context.Background())
front := &quicFront{late: p.srv.lateH3, gate: &p.srv.h3Gate, logger: p.cfg.logger, own: p.srv.fwd.up.own, conns: make(map[*quic.Conn]struct{}),
sessOf: func(c *quic.Conn) *Session { return p.srv.frontSessionOf(c) },
tr: tr, pc: pc, closed: make(chan struct{}), stopAccept: stopAccept}
if !p.srv.tryTrackFront(front) {
_ = pc.Close()
return ErrProxyClosed
}
ln, err := tr.Listen(http3.ConfigureTLSConfig(&tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
}), &quic.Config{
MaxIdleTimeout: p.cfg.server.idleTimeout,
EnableDatagrams: true,
MaxIncomingStreams: int64(p.cfg.server.maxConcurrentStreams),
Tracer: h3Trace,
})
if err != nil {
p.srv.untrackFront(front)
_ = front.Close()
return err
}
err = front.accept(acceptCtx, ln, p.srv.h3)
closeListener(ln)
if !p.srv.closing.Load() {
p.srv.untrackFront(front)
_ = front.Close()
return err
}
// The front is closed in the front stage of the cut, or at the end of the drain.
<-front.closed
p.srv.untrackFront(front)
return ErrProxyClosed
}
// h3Token is the place of one HTTP/3 front connection in h3Live, from its first Initial to its
// end.
type h3Token struct {
g *gauge
once sync.Once
accepted atomic.Bool
}
type h3TokenKey struct{}
func (t *h3Token) release() { t.once.Do(t.g.done) }
// h3Handshake bounds the time the proxy keeps a connection quic-go never delivers, past the
// 10 s its default configuration gives a handshake.
const h3Handshake = 15 * time.Second
// h3Trace returns no trace. Its context is the connection's, which ends with it, whatever the
// end of its handshake.
func h3Trace(ctx context.Context, _ bool, _ quic.ConnectionID) qlogwriter.Trace {
if t, ok := ctx.Value(h3TokenKey{}).(*h3Token); ok {
context.AfterFunc(ctx, t.release)
time.AfterFunc(h3Handshake, func() {
if !t.accepted.Load() {
t.release()
}
})
}
return nil
}
// h3Gate orders the start of the service of a connection on an HTTP/3 server before the
// shutdown of that server. quic-go counts a connection only after it checked that its server is
// open, and a shutdown between the two closes the connection with H3_NO_ERROR and no answer.
type h3Gate struct {
// counting maps the connections between the start of their service and their count.
counting sync.Map
mu sync.RWMutex
shut bool
}
// serve serves c on srv, and returns http.ErrServerClosed once the shutdown of srv began. The
// shutdown waits while c is between the check of its server and its count.
func (g *h3Gate) serve(srv *http3.Server, c *quic.Conn) error {
g.mu.RLock()
if g.shut {
g.mu.RUnlock()
return http.ErrServerClosed
}
held := new(atomic.Bool)
held.Store(true)
g.counting.Store(c, held)
err := srv.ServeQUICConn(c)
g.counting.Delete(c)
if held.Swap(false) {
g.mu.RUnlock()
}
return err
}
// counted runs in the ConnContext of the server, once quic-go counted c.
func (g *h3Gate) counted(c *quic.Conn) {
if v, ok := g.counting.Load(c); ok && v.(*atomic.Bool).Swap(false) {
g.mu.RUnlock()
}
}
// shutdown shuts srv down once no connection is between the check of its server and its count.
func (g *h3Gate) shutdown(ctx context.Context, srv *http3.Server) error {
g.mu.Lock()
g.shut = true
g.mu.Unlock()
return srv.Shutdown(ctx)
}
// quicFront records the connections accepted on an HTTP/3 front, so a cut closes them with
// a CONNECTION_CLOSE before the transport under them.
type quicFront struct {
// late returns a server of its own for a connection that reaches the shared one after the
// start of the drain, nil once a cut started.
late func() *http3.Server
gate *h3Gate
logger *slog.Logger
own *ownSockets
conns map[*quic.Conn]struct{}
// sessOf returns the session of a connection, nil before it reached its server.
sessOf func(*quic.Conn) *Session
tr *quic.Transport
pc net.PacketConn
// closed is closed once Close has ended the connections, the transport and the socket.
closed chan struct{}
// stopAccept ends the accept loop at the start of the cut.
stopAccept context.CancelFunc
mu sync.Mutex
closeOnce sync.Once
}
// accept serves each connection accepted on ln on a goroutine of its own until ctx ends,
// and returns the error Accept ended on. ServeListener would return only through
// http3.Server.Close or Shutdown, and a cut calls neither.
func (f *quicFront) accept(ctx context.Context, ln *quic.Listener, h3 *http3.Server) error {
for {
c, err := ln.Accept(ctx)
if err != nil {
return err
}
// Once the handshake is over, a connection from a socket the proxy recorded upstream is
// the proxy itself.
if f.own.datagram(addrPort(c.RemoteAddr())) {
f.logger.Debug("edge: HTTP/3 connection refused", slog.String("client", c.RemoteAddr().String()),
slog.String("reason", "the proxy opened it upstream, a request loop"))
_ = c.CloseWithError(quic.ApplicationErrorCode(http3.ErrCodeExcessiveLoad), "")
continue
}
if t, ok := c.Context().Value(h3TokenKey{}).(*h3Token); ok {
t.accepted.Store(true)
}
f.add(c)
go f.serve(h3, c)
}
}
func (f *quicFront) serve(h3 *http3.Server, c *quic.Conn) {
err := f.gate.serve(h3, c)
if errors.Is(err, http.ErrServerClosed) && c.Context().Err() == nil && f.late != nil {
// The drain shut the server down before it had the connection.
if late := f.late(); late != nil {
err = late.ServeQUICConn(c)
}
}
// Refused or failed before it was established, the connection is still open.
_ = closeNoError(c)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
f.logger.Debug("edge: HTTP/3 connection closed", slog.String("error", err.Error()))
}
}
func (f *quicFront) add(c *quic.Conn) {
f.mu.Lock()
f.conns[c] = struct{}{}
f.mu.Unlock()
context.AfterFunc(c.Context(), func() {
f.mu.Lock()
delete(f.conns, c)
f.mu.Unlock()
})
}
// Close closes every connection still open with a CONNECTION_CLOSE, sent before the call
// returns, then the transport and the socket under them. Closing the transport first would end
// the connections without a CONNECTION_CLOSE.
func (f *quicFront) Close() error {
f.closeOnce.Do(func() {
f.mu.Lock()
conns := make([]*quic.Conn, 0, len(f.conns))
for c := range f.conns {
conns = append(conns, c)
}
f.mu.Unlock()
var wg sync.WaitGroup
for _, c := range conns {
if sess := f.sessOf(c); sess != nil {
// The closer gives a connection with a request still in flight its failed end.
wg.Go(func() { _ = sess.endBy(causeCut, time.Now().Add(alertWriteTimeout)) })
continue
}
wg.Go(func() { _ = closeNoError(c) })
}
wg.Wait()
_ = f.tr.Close()
_ = f.pc.Close()
close(f.closed)
})
return nil
}
// Shutdown stops accepting new connections, then waits for the requests in flight to finish.
// The servers drain together: idle HTTP/1.1 connections are closed, HTTP/2 and HTTP/3
// connections are told with a GOAWAY, and every request already started runs to completion.
// A handler serving a whole connection, a stream or a datagram flow, ends when its peers do,
// so it is waited on until ctx is done and cut then, along with anything else still open.
// Shutdown returns once that cut has ended. Without a deadline on ctx, Shutdown waits
// indefinitely. After Shutdown or Close has returned, a further Shutdown returns nil at once.
func (p *Proxy) Shutdown(ctx context.Context) error {
// A cut that has ended left nothing to drain.
select {
case <-p.srv.done:
return nil
default:
}
first := p.srv.beginClose()
defer p.srv.endClose()
err := p.srv.closeListeners()
if first {
// The drain takes its deadline once.
p.srv.endWaiting()
}
// Past the deadline, whatever draining could not end is cut: streams, upgraded pipes, and
// connections still handshaking. Cutting them is what unblocks the waits below.
stop := context.AfterFunc(ctx, func() { p.srv.cutBy(cutByDeadline) })
p.srv.mu.Lock()
demuxes := slices.Collect(maps.Keys(p.srv.quics))
p.srv.mu.Unlock()
// The planes drain together. An intercepted connection carried on a CONNECT stream of
// the front server only ends once the terminating server drains it, so draining one
// server after the other would hold the first open until the deadline.
// HTTP/1.1 drains with its keep-alives disabled: net/http drops a request it reads after
// Shutdown. HTTP/2 drains under Shutdown, which writes the GOAWAY.
p.srv.h1.SetKeepAlivesEnabled(false)
p.srv.term.SetKeepAlivesEnabled(false)
errs := make([]error, 4+len(demuxes))
var drains sync.WaitGroup
drains.Go(func() { errs[0] = p.srv.h2.Shutdown(ctx) })
// quic-go closes every connection once the context of its Shutdown ends, before the cut
// has ended their requests, so the cut ends them instead.
drains.Go(func() { errs[1] = p.srv.h3Gate.shutdown(context.WithoutCancel(ctx), p.srv.h3) })
drains.Go(func() { errs[2] = p.srv.term2.Shutdown(ctx) })
drains.Go(func() { errs[3] = p.srv.frontsLive.waitCtx(ctx) })
for i, d := range demuxes {
drains.Go(func() { errs[4+i] = d.shutdown(ctx) })
}
// Each drain bounds itself by ctx, but no close can force a handler to return. The waits
// are raced against the deadline, and past it Shutdown returns once the cut has ended,
// leaving a handler that ignores its context to unwind behind it.
drained := make(chan struct{})
go func() { drains.Wait(); close(drained) }()
select {
case <-drained:
case <-ctx.Done():
// The drains may still be writing errs, so it is not read; the deadline is the
// whole report.
p.srv.cutBy(cutByDeadline)
p.srv.fwd.close()
return errors.Join(err, p.srv.cutError(ctx))
case <-p.srv.cutEnded:
// A Close or the deadline cut the drain, and a handler that ignores its context is not
// waited for.
p.srv.fwd.close()
return errors.Join(err, p.srv.cutError(ctx))
}
// Every drain that ran into the deadline reports it; the deadline is one fact and it is
// reported once, on the paths that know it cut something.
for i, e := range errs {
if errors.Is(e, context.Canceled) || errors.Is(e, context.DeadlineExceeded) {
errs[i] = nil
}
}
err = errors.Join(err, errors.Join(errs...))
wait:
for {
// A connection accepted at the start may join a gauge another one left, so every
// gauge is read again after each wait.
zero := p.srv.tracked.zeroed()
if zero == nil {
zero = p.srv.frontsLive.zeroed()
}
if zero == nil {
zero = p.srv.h3Live.zeroed()
}
if zero == nil {
break
}
select {
case <-zero:
case <-p.srv.cutEnded:
err = errors.Join(err, p.srv.cutError(ctx))
break wait
case <-ctx.Done():
err = errors.Join(err, p.srv.cutError(ctx))
break wait
}
}
// A cut that the deadline or Close started ends before Shutdown returns. A drain that
// ended ends the pools and the sockets itself. A deadline seen first by the wait above may
// stop the AfterFunc before it ran, and still cuts.
if !stop() || p.srv.cutCtx.Err() != nil || ctx.Err() != nil {
p.srv.cutBy(cutByDeadline)
if !errors.Is(err, ErrProxyClosed) && ctx.Err() == nil {
err = errors.Join(err, ErrProxyClosed)
}
} else {
// The pools end, then the UDP sockets close, docs/lifecycle.md Drain.
p.srv.fwd.close()
p.srv.closeQUICFronts()
p.srv.closeHTTPServers()
}
p.srv.fwd.close()
return err
}
// cutError is the error a Shutdown reports for a cut: its context's error when that context
// started it, ErrProxyClosed when Close did.
func (s *server) cutError(ctx context.Context) error {
if err := ctx.Err(); err != nil && s.cutStarter.Load() != cutByClose {
return err
}
return ErrProxyClosed