quic_listener (quic v1.8.2)

View Source

QUIC server listener for accepting connections.

This module handles: - UDP socket management - Initial packet routing to connections - Connection ID management - Stateless retry (optional)

Connection Handler Callback

The connection_handler option decides which process owns each new connection, and so which process receives its {quic, Conn, _} events. Use it when you accept connections yourself rather than through a higher-level server such as quic_h3.

   Opts = #{
       cert => Cert,
       key => Key,
       connection_handler => fun(Conn) ->
           %% Return the process that will consume this connection's
           %% events. It becomes the owner before the connection sees
           %% its first packet.
           {ok, HandlerPid}
       end
   }

Return {ok, HandlerPid} to take the connection, or {error, Reason} to decline it, in which case the listener logs a warning and keeps ownership. An arity-2 fun is called as Fun(Conn, DCID).

Return the final owner

The listener calls quic:set_owner_sync/2 on the pid you return and only then hands the connection its first packet. That is the one moment at which ownership moves with nothing in flight.

Do not return a short-lived broker that later transfers ownership to a worker. Between the broker's last read and the worker's set_owner_sync the connection is still delivering to the broker, and those events die with it. Draining the broker's mailbox does not close that window: the connection can emit into it after the drain returns.

When the owner is an OTP process, take ownership in init/1, or in the handler itself before returning:

   connection_handler => fun(Conn) ->
       {ok, Pid} = my_conn:start_link(Conn),
       ok = quic:set_owner_sync(Conn, Pid),
       {ok, Pid}
   end

gen_server and gen_statem both answer start_link before running any post-init callback, so a set_owner_sync in handle_continue or in a state_enter callback runs once the starter is already back in control, which reopens the same window.

Do not assume `connected' arrives first

A datagram is processed before the connection changes state, so stream events from that datagram reach the owner ahead of {quic, Conn, {connected, Info}}. A client that coalesces its Finished with a 1-RTT packet does exactly this. An owner that waits for connected before entering its main loop mis-orders or swallows whatever came first.

Close what you own

Listener-created connections do not monitor their owner. If your handler dies the connection stays up until its idle timeout, so close it explicitly on the way out.

Summary

Functions

Get list of active connections.

Get the port the listener is bound to.

Get the address the listener is bound to. Resolves the actual bound address from the socket, so it reflects the real value even when the listener was opened with inet6, {ip, Addr} or {ifaddr, Addr}.

false Handle incoming UDP packets (gen_udp backend)

false

Add a connection ID to the routing table. Called by a connection when it issues a new CID (NEW_CONNECTION_ID) so packets the peer sends to the rotated/migrated CID reach the connection (the routing ETS is owned by the listener).

Remove a connection ID from the routing table (CID retired).

Start a QUIC listener (without linking to caller).

Start a QUIC listener on the given port. Options: - cert: Server certificate (DER binary) - cert_chain: Certificate chain [binary()] - key: Private key - alpn: List of supported ALPN protocols - active_n: Number of packets before socket goes passive (default 100) - reuseport: Enable SO_REUSEPORT for multiple listeners (default false) - connections_table: Shared ETS table for connection tracking (pool mode) - preferred_ipv4: {IP, Port} for preferred IPv4 address (RFC 9000 Section 9.6) - preferred_ipv6: {IP, Port} for preferred IPv6 address (RFC 9000 Section 9.6)

Stop the listener.

Types

state/0

-type state() ::
          #listener_state{socket :: gen_udp:socket() | socket:socket(),
                          socket_state :: quic_socket:socket_state() | undefined,
                          socket_backend :: gen_udp | socket,
                          gro_receiver :: pid() | undefined,
                          port :: inet:port_number(),
                          cert :: binary() | undefined,
                          cert_chain :: [binary()],
                          private_key :: term() | undefined,
                          psks :: #{binary() => binary()} | undefined,
                          psk_callback :: fun((binary()) -> {ok, binary()} | not_found) | undefined,
                          sni_callback ::
                              fun((binary() | undefined) -> {ok, map()} | {error, term()}) | undefined,
                          alpn_list :: [binary()],
                          connections :: ets:tid(),
                          tickets_table :: ets:tid(),
                          owns_tables :: boolean(),
                          reset_secret :: binary(),
                          address_validation :: never | always,
                          token_max_age_ms :: non_neg_integer(),
                          connection_handler ::
                              fun((pid()) -> {ok, pid()} | {error, term()}) |
                              fun((pid(), binary()) -> {ok, pid()} | {error, term()}) |
                              undefined,
                          cid_config ::
                              #cid_config{lb_config ::
                                              #lb_config{config_rotation :: 0..6,
                                                         algorithm ::
                                                             plaintext | stream_cipher | block_cipher,
                                                         server_id :: binary(),
                                                         server_id_len :: 1..15,
                                                         nonce_len :: 4..18,
                                                         key :: binary() | undefined} |
                                              undefined,
                                          cid_len :: 1..20,
                                          reset_secret :: binary() | undefined} |
                              undefined,
                          dcid_len :: pos_integer(),
                          opts :: map()}.

Functions

code_change(OldVsn, State, Extra)

false

get_connections(Listener)

-spec get_connections(pid()) -> [pid()].

Get list of active connections.

get_port(Listener)

-spec get_port(pid()) -> inet:port_number().

Get the port the listener is bound to.

get_sockname(Listener)

-spec get_sockname(pid()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, term()}.

Get the address the listener is bound to. Resolves the actual bound address from the socket, so it reflects the real value even when the listener was opened with inet6, {ip, Addr} or {ifaddr, Addr}.

handle_call(Request, From, Listener_state)

-spec handle_call(term(), gen_server:from(), state()) -> {reply, term(), state()}.

false

handle_cast(Msg, Listener_state)

-spec handle_cast(term(), state()) -> {noreply, state()}.

false

handle_continue(_, _)

false

handle_info(Info, Listener_state)

false Handle incoming UDP packets (gen_udp backend)

init(_)

-spec init({inet:port_number(), map()}) -> term().

false

register_cid(Listener, CID, ConnPid)

-spec register_cid(pid(), binary(), pid()) -> ok.

Add a connection ID to the routing table. Called by a connection when it issues a new CID (NEW_CONNECTION_ID) so packets the peer sends to the rotated/migrated CID reach the connection (the routing ETS is owned by the listener).

retire_cid(Listener, CID)

-spec retire_cid(pid(), binary()) -> ok.

Remove a connection ID from the routing table (CID retired).

start(Port, Opts)

-spec start(inet:port_number(), map()) -> {ok, pid()} | {error, term()}.

Start a QUIC listener (without linking to caller).

start_link(Port, Opts)

-spec start_link(inet:port_number(), map()) -> {ok, pid()} | {error, term()}.

Start a QUIC listener on the given port. Options: - cert: Server certificate (DER binary) - cert_chain: Certificate chain [binary()] - key: Private key - alpn: List of supported ALPN protocols - active_n: Number of packets before socket goes passive (default 100) - reuseport: Enable SO_REUSEPORT for multiple listeners (default false) - connections_table: Shared ETS table for connection tracking (pool mode) - preferred_ipv4: {IP, Port} for preferred IPv4 address (RFC 9000 Section 9.6) - preferred_ipv6: {IP, Port} for preferred IPv6 address (RFC 9000 Section 9.6)

stop(Listener)

-spec stop(pid()) -> ok.

Stop the listener.

terminate(Reason, Listener_state)

false