// Package quicreuse provides `quicreuse.ConnManager`, which provides functionality // for reusing QUIC transports for various purposes, like listening & dialing, having // multiple QUIC listeners on the same address with different ALPNs, and sharing the // same address with non QUIC transports like WebRTC.
package quicreuse import ( ma manet quiclogging quicmetrics ) type QUICListener interface { Accept(ctx context.Context) (quic.Connection, error) Close() error Addr() net.Addr } var _ QUICListener = &quic.Listener{} type QUICTransport interface { Listen(tlsConf *tls.Config, conf *quic.Config) (QUICListener, error) Dial(ctx context.Context, addr net.Addr, tlsConf *tls.Config, conf *quic.Config) (quic.Connection, error) WriteTo(b []byte, addr net.Addr) (int, error) ReadNonQUICPacket(ctx context.Context, b []byte) (int, net.Addr, error) io.Closer } // ConnManager enables QUIC and WebTransport transports to listen on the same port, reusing // listen addresses for dialing, and provides a PacketConn for sharing the listen address // with other protocols like WebRTC. // Reusing the listen address for dialing helps with address discovery and hole punching. For details // of the reuse logic see `ListenQUICAndAssociate` and `DialQUIC`. // If reuseport is disabled using the `DisableReuseport` option, listen addresses are not used for // dialing. type ConnManager struct { reuseUDP4 *reuse reuseUDP6 *reuse enableReuseport bool listenUDP listenUDP sourceIPSelectorFn func() (SourceIPSelector, error) enableMetrics bool registerer prometheus.Registerer serverConfig *quic.Config clientConfig *quic.Config quicListenersMu sync.Mutex quicListeners map[string]quicListenerEntry srk quic.StatelessResetKey tokenKey quic.TokenGeneratorKey connContext connContextFunc verifySourceAddress func(addr net.Addr) bool } type quicListenerEntry struct { refCount int ln *quicListener } func defaultListenUDP( string, *net.UDPAddr) (net.PacketConn, error) { return net.ListenUDP(, ) } func defaultSourceIPSelectorFn() (SourceIPSelector, error) { , := netroute.New() return &netrouteSourceIPSelector{routes: }, } const ( unverifiedAddressNewConnectionRPS = 1000 unverifiedAddressNewConnectionBurst = 1000 ) // NewConnManager returns a new ConnManager func ( quic.StatelessResetKey, quic.TokenGeneratorKey, ...Option) (*ConnManager, error) { := &ConnManager{ enableReuseport: true, quicListeners: make(map[string]quicListenerEntry), srk: , tokenKey: , registerer: prometheus.DefaultRegisterer, listenUDP: defaultListenUDP, sourceIPSelectorFn: defaultSourceIPSelectorFn, } for , := range { if := (); != nil { return nil, } } := quicConfig.Clone() .Tracer = .getTracer() := .Clone() .clientConfig = .serverConfig = // Verify source addresses when under high load. // This is ensures that the number of spoofed/unverified addresses that are passed to downstream rate limiters // are limited, which enables IP address based rate limiting. := rate.NewLimiter(unverifiedAddressNewConnectionRPS, unverifiedAddressNewConnectionBurst) := .verifySourceAddress .verifySourceAddress = func( net.Addr) bool { if .Allow() { if != nil { return () } return false } return true } if .enableReuseport { .reuseUDP4 = newReuse(&, &, .listenUDP, .sourceIPSelectorFn, .connContext, .verifySourceAddress) .reuseUDP6 = newReuse(&, &, .listenUDP, .sourceIPSelectorFn, .connContext, .verifySourceAddress) } return , nil } func ( *ConnManager) () func(context.Context, quiclogging.Perspective, quic.ConnectionID) *quiclogging.ConnectionTracer { return func( context.Context, quiclogging.Perspective, quic.ConnectionID) *quiclogging.ConnectionTracer { var *quiclogging.ConnectionTracer if .enableMetrics { switch { case quiclogging.PerspectiveClient: = quicmetrics.NewClientConnectionTracerWithRegisterer(.registerer) case quiclogging.PerspectiveServer: = quicmetrics.NewServerConnectionTracerWithRegisterer(.registerer) default: log.Error("invalid logging perspective: %s", ) } } var *quiclogging.ConnectionTracer if qlogTracerDir != "" { = qloggerForDir(qlogTracerDir, , ) if != nil { = quiclogging.NewMultiplexedConnectionTracer(, ) } } return } } func ( *ConnManager) ( string) (*reuse, error) { switch { case "udp4": return .reuseUDP4, nil case "udp6": return .reuseUDP6, nil default: return nil, errors.New("invalid network: must be either udp4 or udp6") } } // LendTransport is an advanced method used to lend an existing QUICTransport // to the ConnManager. The ConnManager will close the returned channel when it // is done with the transport, so that the owner may safely close the transport. func ( *ConnManager) ( string, QUICTransport, net.PacketConn) (<-chan struct{}, error) { .quicListenersMu.Lock() defer .quicListenersMu.Unlock() , := .LocalAddr().(*net.UDPAddr) if ! { return nil, errors.New("expected a conn.LocalAddr() to return a *net.UDPAddr") } := &refcountedTransport{ QUICTransport: , packetConn: , borrowDoneSignal: make(chan struct{}), } var *reuse , := .getReuse() if != nil { return nil, } return .borrowDoneSignal, .AddTransport(, ) } // ListenQUIC listens for quic connections with the provided `tlsConf.NextProtos` ALPNs on `addr`. The same addr can be shared between // different ALPNs. func ( *ConnManager) ( ma.Multiaddr, *tls.Config, func( quic.Connection, uint64) bool) (Listener, error) { return .ListenQUICAndAssociate(nil, , , ) } // ListenQUICAndAssociate listens for quic connections with the provided `tlsConf.NextProtos` ALPNs on `addr`. The same addr can be shared between // different ALPNs. // The QUIC Transport used for listening is tagged with the `association`. Any subsequent `TransportWithAssociationForDial`, // or `DialQUIC` calls with the same `association` will reuse the QUIC Transport used by this method. // A common use of associations is to ensure /quic dials use the quic listening address and /webtransport dials use the // WebTransport listening address. func ( *ConnManager) ( any, ma.Multiaddr, *tls.Config, func( quic.Connection, uint64) bool) (Listener, error) { , , := manet.DialArgs() if != nil { return nil, } , := net.ResolveUDPAddr(, ) if != nil { return nil, } .quicListenersMu.Lock() defer .quicListenersMu.Unlock() := .String() , := .quicListeners[] if ! { , := .transportForListen(, , ) if != nil { return nil, } , := newQuicListener(, .serverConfig) if != nil { return nil, } = .LocalAddr().String() = quicListenerEntry{ln: } } else if .enableReuseport && != nil { , := .getReuse() if != nil { return nil, fmt.Errorf("reuse error: %w", ) } = .AssertTransportExists(.ln.transport) if != nil { return nil, fmt.Errorf("reuse assert transport failed: %w", ) } if , := .ln.transport.(*refcountedTransport); { .associate() } } , := .ln.Add(, , func() { .onListenerClosed() }) if != nil { if .refCount <= 0 { .ln.Close() } return nil, } .refCount++ .quicListeners[] = return , nil } func ( *ConnManager) ( string) { .quicListenersMu.Lock() defer .quicListenersMu.Unlock() := .quicListeners[] .refCount = .refCount - 1 if .refCount <= 0 { delete(.quicListeners, ) .ln.Close() } else { .quicListeners[] = } } // SharedNonQUICPacketConn returns a `net.PacketConn` for `laddr` for non QUIC uses. func ( *ConnManager) ( string, *net.UDPAddr) (net.PacketConn, error) { .quicListenersMu.Lock() defer .quicListenersMu.Unlock() := .String() , := .quicListeners[] if ! { return nil, errors.New("expected to be able to share with a QUIC listener, but no QUIC listener found. The QUIC listener should start first") } := .ln.transport if , := .(*refcountedTransport); { .IncreaseCount() , := context.WithCancel(context.Background()) return &nonQUICPacketConn{ ctx: , ctxCancel: , owningTransport: , tr: .QUICTransport, }, nil } return nil, errors.New("expected to be able to share with a QUIC listener, but the QUIC listener is not using a refcountedTransport. `DisableReuseport` should not be set") } func ( *ConnManager) ( any, string, *net.UDPAddr) (RefCountedQUICTransport, error) { if .enableReuseport { , := .getReuse() if != nil { return nil, } , := .TransportForListen(, ) if != nil { return nil, } .associate() return , nil } , := .listenUDP(, ) if != nil { return nil, } return .newSingleOwnerTransport(), nil } type associationKey struct{} // WithAssociation returns a new context with the given association. Used in // DialQUIC to prefer a transport that has the given association. func ( context.Context, any) context.Context { return context.WithValue(, associationKey{}, ) } // DialQUIC dials `raddr`. Use `WithAssociation` to select a specific transport that was previously used for listening. // see the documentation for `ListenQUICAndAssociate` for details on associate. // The priority order for reusing the transport is as follows: // - Listening transport with the same association // - Any other listening transport // - Any transport previously used for dialing // If none of these are available, it'll create a new transport. func ( *ConnManager) ( context.Context, ma.Multiaddr, *tls.Config, func( quic.Connection, uint64) bool) (quic.Connection, error) { , , := FromQuicMultiaddr() if != nil { return nil, } , , := manet.DialArgs() if != nil { return nil, } := .clientConfig.Clone() .AllowConnectionWindowIncrease = if == quic.Version1 { // The endpoint has explicit support for QUIC v1, so we'll only use that version. .Versions = []quic.Version{quic.Version1} } else { return nil, errors.New("unknown QUIC version") } var RefCountedQUICTransport := .Value(associationKey{}) , = .TransportWithAssociationForDial(, , ) if != nil { return nil, } , := .Dial(, , , ) if != nil { .DecreaseCount() return nil, } return , nil } // TransportForDial returns a transport for dialing `raddr`. // If reuseport is enabled, it attempts to reuse the QUIC Transport used for // previous listens or dials. func ( *ConnManager) ( string, *net.UDPAddr) (RefCountedQUICTransport, error) { return .TransportWithAssociationForDial(nil, , ) } // TransportWithAssociationForDial returns a transport for dialing `raddr`. // If reuseport is enabled, it attempts to reuse the QUIC Transport previously used for listening with `ListenQuicAndAssociate` // with the same `association`. If it fails to do so, it uses any other previously used transport. func ( *ConnManager) ( any, string, *net.UDPAddr) (RefCountedQUICTransport, error) { if .enableReuseport { , := .getReuse() if != nil { return nil, } return .TransportWithAssociationForDial(, , ) } var *net.UDPAddr switch { case "udp4": = &net.UDPAddr{IP: net.IPv4zero, Port: 0} case "udp6": = &net.UDPAddr{IP: net.IPv6zero, Port: 0} } , := .listenUDP(, ) if != nil { return nil, } return .newSingleOwnerTransport(), nil } func ( *ConnManager) ( net.PacketConn) *singleOwnerTransport { return &singleOwnerTransport{ Transport: &wrappedQUICTransport{ Transport: newQUICTransport( , &.tokenKey, &.srk, .connContext, .verifySourceAddress, ), }, packetConn: } } // Protocols returns the supported QUIC protocols. The only supported protocol at the moment is /quic-v1. func ( *ConnManager) () []int { return []int{ma.P_QUIC_V1} } func ( *ConnManager) () error { if !.enableReuseport { return nil } if := .reuseUDP6.Close(); != nil { return } return .reuseUDP4.Close() } func ( *ConnManager) () *quic.Config { return .clientConfig } // wrappedQUICTransport wraps a `quic.Transport` to confirm to `QUICTransport` type wrappedQUICTransport struct { *quic.Transport } var _ QUICTransport = (*wrappedQUICTransport)(nil) func ( *wrappedQUICTransport) ( *tls.Config, *quic.Config) (QUICListener, error) { return .Transport.Listen(, ) } func newQUICTransport( net.PacketConn, *quic.TokenGeneratorKey, *quic.StatelessResetKey, connContextFunc, func( net.Addr) bool, ) *quic.Transport { return &quic.Transport{ Conn: , TokenGeneratorKey: , StatelessResetKey: , ConnContext: , VerifySourceAddress: , } }