|
@ -15,6 +15,9 @@ |
|
|
package caddy |
|
|
package caddy |
|
|
|
|
|
|
|
|
import ( |
|
|
import ( |
|
|
|
|
|
"context" |
|
|
|
|
|
"crypto/tls" |
|
|
|
|
|
"errors" |
|
|
"fmt" |
|
|
"fmt" |
|
|
"net" |
|
|
"net" |
|
|
"os" |
|
|
"os" |
|
@ -24,6 +27,9 @@ import ( |
|
|
"sync/atomic" |
|
|
"sync/atomic" |
|
|
"syscall" |
|
|
"syscall" |
|
|
"time" |
|
|
"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
|
|
|
// 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 |
|
|
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
|
|
|
// fakeCloseListener is a private wrapper over a listener that
|
|
|
// is shared. The state of fakeCloseListener is not shared.
|
|
|
// is shared. The state of fakeCloseListener is not shared.
|
|
|
// This allows one user of a socket to "close" the listener
|
|
|
// 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) { |
|
|
func (fcl *fakeCloseListener) Accept() (net.Conn, error) { |
|
|
// if the listener is already "closed", return error
|
|
|
// if the listener is already "closed", return error
|
|
|
if atomic.LoadInt32(&fcl.closed) == 1 { |
|
|
if atomic.LoadInt32(&fcl.closed) == 1 { |
|
|
return nil, fcl.fakeClosedErr() |
|
|
return nil, fakeClosedErr(fcl) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// call underlying accept
|
|
|
// call underlying accept
|
|
@ -119,7 +146,7 @@ func (fcl *fakeCloseListener) Accept() (net.Conn, error) { |
|
|
_ = fcl.sharedListener.clearDeadline() |
|
|
_ = fcl.sharedListener.clearDeadline() |
|
|
|
|
|
|
|
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() { |
|
|
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 |
|
|
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
|
|
|
// fakeClosedErr returns an error value that is not temporary
|
|
|
// nor a timeout, suitable for making the caller think the
|
|
|
// nor a timeout, suitable for making the caller think the
|
|
|
// listener is actually closed
|
|
|
// listener is actually closed
|
|
|
func (fcl *fakeCloseListener) fakeClosedErr() error { |
|
|
func fakeClosedErr(l interface{ Addr() net.Addr }) error { |
|
|
return &net.OpError{ |
|
|
return &net.OpError{ |
|
|
Op: "accept", |
|
|
Op: "accept", |
|
|
Net: fcl.Addr().Network(), |
|
|
Net: l.Addr().Network(), |
|
|
Addr: fcl.Addr(), |
|
|
Addr: l.Addr(), |
|
|
Err: errFakeClosed, |
|
|
Err: errFakeClosed, |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -244,6 +304,17 @@ func (sl *sharedListener) Destruct() error { |
|
|
return sl.Listener.Close() |
|
|
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.
|
|
|
// sharedPacketConn is like sharedListener, but for net.PacketConns.
|
|
|
type sharedPacketConn struct { |
|
|
type sharedPacketConn struct { |
|
|
net.PacketConn |
|
|
net.PacketConn |
|
|