|  | @ -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 | 
			
		
	
	
		
		
			
				
					|  | 
 |