Repository: caddyserver/caddy
File: listeners.go (Go)

Code before:
func ListenTimeout(network, addr string, keepalivePeriod time.Duration) (net.Listener, error) {
	netAddr, err := ParseNetworkAddress(JoinNetworkAddress(network, addr, ""))
	if err != nil {
		return nil, err
	}

	ln, err := netAddr.Listen(context.TODO(), 0, net.ListenConfig{KeepAlive: keepalivePeriod})
	if err != nil {
		return nil, err
	}

	return ln.(net.Listener), nil
}

// DEPRECATED: Use NetworkAddress.Listen instead. This function will likely be changed or removed in the future.
func ListenPacket(network, addr string) (net.PacketConn, error) {
	netAddr, err := ParseNetworkAddress(JoinNetworkAddress(network, addr, ""))
	if err != nil {
		return nil, err
	}

	ln, err := netAddr.Listen(context.TODO(), 0, net.ListenConfig{})
	if err != nil {
		return nil, err
	}

	return ln.(net.PacketConn), nil
}

// ListenQUIC returns a quic.EarlyListener suitable for use in a Caddy module.
// The network will be transformed into a QUIC-compatible type (if unix, then
// unixgram will be used; otherwise, udp will be used).
//
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config, activeRequests *int64) (http3.QUICEarlyListener, error) {
	lnKey := listenerKey("quic"+na.Network, na.JoinHostPort(portOffset))

	sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
		lnAny, err := na.Listen(ctx, portOffset, config)
		if err != nil {
			return nil, err
		}

		ln := lnAny.(net.PacketConn)

		h3ln := ln
		for {
			// retrieve the underlying socket, so quic-go can optimize.
			if unwrapper, ok := h3ln.(interface{ Unwrap() net.PacketConn }); ok {
				h3ln = unwrapper.Unwrap()
			} else {

Review comment (suggestion):
This should probably be made tunable eventually, but at least we have a high limit right now that should work for most people. Maybe this could be in a follow-up PR. (It will need to be marked as experimental though as this API is still undergoing finalization.)

Code after:

	sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
		lnAny, err := na.Listen(ctx, portOffset, config)
		if err != nil {
			return nil, err
		}

		ln := lnAny.(net.PacketConn)

		h3ln := ln
		for {
			// retrieve the underlying socket, so quic-go can optimize.
			if unwrapper, ok := h3ln.(interface{ Unwrap() net.PacketConn }); ok {
				h3ln = unwrapper.Unwrap()
			} else {
				break
			}
		}

		sqs := newSharedQUICState(tlsConf)
		// http3.ConfigureTLSConfig only uses this field and tls App sets this field as well
		//nolint:gosec
		quicTlsConfig := &tls.Config{GetConfigForClient: sqs.getConfigForClient}
		// Require clients to verify their source address when we're handling more than 1000 handshakes per second.
		// TODO: make tunable?
		limiter := rate.NewLimiter(1000, 1000)
		tr := &quic.Transport{
			Conn:                h3ln,
			VerifySourceAddress: func(addr net.Addr) bool { return !limiter.Allow() },
		}
		earlyLn, err := tr.ListenEarly(http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{Allow0RTT: true})
		if err != nil {
			return nil, err
		}
		// TODO: figure out when to close the listener and the transport
		// using the original net.PacketConn to close them properly
		return &sharedQuicListener{EarlyListener: earlyLn, packetConn: ln, sqs: sqs, key: lnKey}, nil
	})
	if err != nil {
		return nil, err
	}

	sql := sharedEarlyListener.(*sharedQuicListener)
	// add current tls.Config to sqs, so GetConfigForClient will always return the latest tls.Config in case of context cancellation
	ctx, cancel := sql.sqs.addState(tlsConf)

	return &fakeCloseQuicListener{
		sharedQuicListener: sql,
		context:            ctx,
		contextCancel:      cancel,
	}, nil