summaryrefslogtreecommitdiff
path: root/listeners.go
diff options
context:
space:
mode:
authorArtem Mikheev <30644072+renbou@users.noreply.github.com>2022-03-23 02:47:57 +0300
committerGitHub <noreply@github.com>2022-03-22 19:47:57 -0400
commitc9b5e7f77b8aac7334d81c552c583af81ba1c400 (patch)
treef1ef90ea232cb73e68f692ca504726602ab523d4 /listeners.go
parent79cbe7bfd06565d0e7ab0717119f78960ed54c08 (diff)
Fix http3 servers dying after reload (#4654)
Diffstat (limited to 'listeners.go')
-rw-r--r--listeners.go81
1 files changed, 76 insertions, 5 deletions
diff --git a/listeners.go b/listeners.go
index c23ff78..181db75 100644
--- a/listeners.go
+++ b/listeners.go
@@ -15,6 +15,9 @@
package caddy
import (
+ "context"
+ "crypto/tls"
+ "errors"
"fmt"
"net"
"os"
@@ -24,6 +27,9 @@ import (
"sync/atomic"
"syscall"
"time"
+
+ "github.com/lucas-clemente/quic-go"
+ "github.com/lucas-clemente/quic-go/http3"
)
// Listen is like net.Listen, except Caddy's listeners can overlap
@@ -79,6 +85,27 @@ func ListenPacket(network, addr string) (net.PacketConn, error) {
return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil
}
+// ListenQUIC returns a quic.EarlyListener suitable for use in a Caddy module.
+// Note that the context passed to Accept is currently ignored, so using
+// a context other than context.Background is meaningless.
+func ListenQUIC(addr string, tlsConf *tls.Config) (quic.EarlyListener, error) {
+ lnKey := "quic/" + addr
+
+ sharedEl, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
+ el, err := quic.ListenAddrEarly(addr, http3.ConfigureTLSConfig(tlsConf), &quic.Config{})
+ if err != nil {
+ return nil, err
+ }
+ return &sharedQuicListener{EarlyListener: el, key: lnKey}, nil
+ })
+
+ ctx, cancel := context.WithCancel(context.Background())
+ return &fakeCloseQuicListener{
+ sharedQuicListener: sharedEl.(*sharedQuicListener),
+ context: ctx, contextCancel: cancel,
+ }, err
+}
+
// fakeCloseListener is a private wrapper over a listener that
// is shared. The state of fakeCloseListener is not shared.
// This allows one user of a socket to "close" the listener
@@ -95,7 +122,7 @@ type fakeCloseListener struct {
func (fcl *fakeCloseListener) Accept() (net.Conn, error) {
// if the listener is already "closed", return error
if atomic.LoadInt32(&fcl.closed) == 1 {
- return nil, fcl.fakeClosedErr()
+ return nil, fakeClosedErr(fcl)
}
// call underlying accept
@@ -119,7 +146,7 @@ func (fcl *fakeCloseListener) Accept() (net.Conn, error) {
_ = fcl.sharedListener.clearDeadline()
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
- return nil, fcl.fakeClosedErr()
+ return nil, fakeClosedErr(fcl)
}
}
@@ -145,14 +172,47 @@ func (fcl *fakeCloseListener) Close() error {
return nil
}
+type fakeCloseQuicListener struct {
+ closed int32 // accessed atomically; belongs to this struct only
+ *sharedQuicListener // embedded, so we also become a quic.EarlyListener
+ context context.Context
+ contextCancel context.CancelFunc
+}
+
+// Currently Accept ignores the passed context, however a situation where
+// someone would need a hotswappable QUIC-only (not http3, since it uses context.Background here)
+// server on which Accept would be called with non-empty contexts
+// (mind that the default net listeners' Accept doesn't take a context argument)
+// sounds way too rare for us to sacrifice efficiency here.
+func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (quic.EarlySession, error) {
+ conn, err := fcql.sharedQuicListener.Accept(fcql.context)
+ if err == nil {
+ return conn, nil
+ }
+
+ // if the listener is "closed", return a fake closed error instead
+ if atomic.LoadInt32(&fcql.closed) == 1 && errors.Is(err, context.Canceled) {
+ return nil, fakeClosedErr(fcql)
+ }
+ return nil, err
+}
+
+func (fcql *fakeCloseQuicListener) Close() error {
+ if atomic.CompareAndSwapInt32(&fcql.closed, 0, 1) {
+ fcql.contextCancel()
+ _, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
+ }
+ return nil
+}
+
// fakeClosedErr returns an error value that is not temporary
// nor a timeout, suitable for making the caller think the
// listener is actually closed
-func (fcl *fakeCloseListener) fakeClosedErr() error {
+func fakeClosedErr(l interface{ Addr() net.Addr }) error {
return &net.OpError{
Op: "accept",
- Net: fcl.Addr().Network(),
- Addr: fcl.Addr(),
+ Net: l.Addr().Network(),
+ Addr: l.Addr(),
Err: errFakeClosed,
}
}
@@ -244,6 +304,17 @@ func (sl *sharedListener) Destruct() error {
return sl.Listener.Close()
}
+// sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
+type sharedQuicListener struct {
+ quic.EarlyListener
+ key string
+}
+
+// Destruct closes the underlying QUIC listener.
+func (sql *sharedQuicListener) Destruct() error {
+ return sql.EarlyListener.Close()
+}
+
// sharedPacketConn is like sharedListener, but for net.PacketConns.
type sharedPacketConn struct {
net.PacketConn