Skip to content

OF-2773 QUIC support#3251

Draft
dwd wants to merge 25 commits into
igniterealtime:mainfrom
dwd:quic
Draft

OF-2773 QUIC support#3251
dwd wants to merge 25 commits into
igniterealtime:mainfrom
dwd:quic

Conversation

@dwd
Copy link
Copy Markdown
Member

@dwd dwd commented Apr 4, 2026

This supports QUIC, rather minimalist, for C2S only.

Rather alarmingly, it actually runs.

dwd added 6 commits April 4, 2026 09:25
Add foundational support for XMPP-over-QUIC client listeners in the existing connection management architecture, while keeping runtime transport implementation intentionally incremental.\n\nCore model changes:\n- Added a new connection type, QUIC_C2S, with SOCKET_C2S fallback semantics for client-oriented behavior and configuration inheritance where applicable.\n- Added a default QUIC client port constant (5224) to ConnectionManager.\n- Added QUIC-specific client settings in ConnectionSettings.Client (enabled flag, port, thread/read-buffer properties, auth policy property, idle timeout property and max-streams system property).\n\nListener lifecycle integration:\n- ConnectionManagerImpl now constructs and manages a dedicated QUIC client listener.\n- QUIC listeners are included in getListeners(), getListener(), and getListeners(type) routing logic.\n- ConnectionListener now stores ConnectionAcceptor (interface type) instead of NettyConnectionAcceptor (implementation type), and instantiates acceptors per connection type.\n- Added QuicConnectionAcceptor as a lifecycle placeholder that integrates with listener start/stop/reconfigure behavior and logs explicit runtime-not-yet-implemented warnings when enabled.\n\nRate limiting alignment:\n- NewConnectionLimiterRegistry now classifies QUIC_C2S as part of the shared C2S limiter bucket, consistent with existing TCP/BOSH C2S grouping.\n\nTests:\n- Extended ConnectionTypeTest to verify QUIC_C2S is client-oriented.\n- Extended NewConnectionLimiterRegistryTest to verify QUIC shares the C2S limiter and receives dynamic C2S limiter updates.\n\nValidation run:\n- ./mvnw -pl xmppserver -Dtest=ConnectionTypeTest,NewConnectionLimiterRegistryTest test\n- Result: BUILD SUCCESS (12 tests, 0 failures).
Expose the new QUIC_C2S listener as a first-class admin-managed connection type, with UI and localization updates that align with existing house style for connection configuration pages.\n\nNew admin page:\n- Added connection-settings-quic-c2s.jsp for QUIC listener management.\n- Supports enabling/disabling the listener, setting QUIC port, configuring QUIC idle timeout, and configuring max streams per session.\n- Includes CSRF protection, validation, logging, success/error messaging, and a link to the existing advanced settings page for QUIC_C2S in direct TLS mode.\n\nNavigation and visibility:\n- Added a dedicated sidebar entry for QUIC client connections in admin-sidebar.xml.\n- Added QUIC certificate-store navigation entries (identity/trust) under TLS certificate management.\n- Updated index.jsp server ports overview to display QUIC listener type/description and link to the new QUIC settings page.\n\nExisting admin page compatibility:\n- Extended connection-settings-advanced.jsp to understand QUIC_C2S in connection-type translation, page title/pageID selection, and success messaging.\n- Extended certificate management pages to include QUIC_C2S labels (security-certificate-store-management.jsp and security-certificate-details.jsp).\n\nLocalization:\n- Added English i18n keys for:\n  - sidebar labels/descriptions for QUIC settings\n  - QUIC ports table labels/descriptions\n  - QUIC connection-settings page text and validation errors\n  - QUIC connection-type label for advanced settings\n  - QUIC certificate-store/truststore labels\n  - QUIC-related system property descriptions\n\nValidation run:\n- ./mvnw -pl xmppserver -Dtest=ConnectionTypeTest,NewConnectionLimiterRegistryTest test\n- Result: BUILD SUCCESS (12 tests, 0 failures).
Replace the QUIC acceptor placeholder with a Netty QUIC server bootstrap that binds a UDP listener, checks native QUIC availability, and initializes a single bidirectional stream pipeline for XMPP stanza traffic.

The stream pipeline now mirrors the existing socket C2S stack: traffic shaping, idle handling, keepalive behavior, XMPP decoding, UTF-8 encoding, write-timeout protection, deferred auto-read startup, and business-logic execution on a blocking executor pool.

Introduce a dedicated QuicClientConnectionHandler and wire it through NettyConnectionHandlerFactory so QUIC uses the standard client stanza processing path while marking connections encrypted from creation time.

Update NettyConnection address resolution to support non-Inet stream channels by falling back to the parent channel remote address. This is required for QUIC stream channels, where peer socket addressing is exposed on the parent channel.

Add Netty QUIC dependencies to xmppserver/pom.xml (classes artifact plus linux-x86_64 native runtime) so QUIC server bootstrap classes and native runtime are available at build/runtime.
Introduce NettyConnectionTest cases that validate peer-address resolution behavior for both standard socket channels and QUIC-style stream channels.

The new tests verify three scenarios: direct InetSocketAddress resolution, fallback to the parent channel when the stream channel has no InetSocketAddress, and UnknownHostException when neither channel exposes a usable InetSocketAddress.

These tests cover the address-resolution logic that QUIC depends on and guard against regressions that would break host/address lookups on stream-backed connections.
Resolve QUIC handshake failures caused by ALPN mismatch by replacing the hardcoded server ALPN value with configurable settings that default to the XMPP C2S token .

Add  () as a dynamic comma-separated list property, allowing operators to include additional ALPN identifiers for interoperability testing or non-standard clients.

Update QUIC SSL context construction to read the configured ALPN list and fail fast on an empty list, producing a clear startup error instead of opaque  handshake failures.

Document the new system property in i18n system-property descriptions for admin visibility.
Fix NettyConnection peer-address resolution for QUIC by reading  when traversing the channel hierarchy.

Previously, QUIC stream and connection channels exposed connection/stream IDs as , which caused host resolution to fail and client session admission checks to reject the connection before session creation.

The updated resolution logic now walks the channel ancestry and prioritizes the QUIC socket-level peer address when available, then falls back to InetSocketAddress remoteAddress values on each channel.

Extend NettyConnectionTest with a QUIC-specific case that simulates stream/connection address types and validates successful resolution via QUIC remote socket address.
@dwd dwd changed the title OF-2774 QUIC support OF-2773 QUIC support Apr 4, 2026
Increase the default QUIC stream budget to twenty-five streams and add a dedicated outbound stream cap (default twenty) for server-initiated send streams.

Introduce QUIC session stream routing that is shared per QUIC connection. The router tracks inbound streams, opens outbound streams when needed, and chooses a deterministic outbound stream per stanza based on the bare from JID.

Enforce stable routing rules for outbound stanzas: traffic from the server bare JID and from the authenticated account bare JID stays on the first stream, while other bare-JID keys are pinned to consistently selected non-primary streams.

Add a QUIC-specific client stanza handler that allows additional inbound streams to attach to the existing LocalClientSession instead of creating a new session. This enables clients to send stanzas over multiple streams in one QUIC connection.

Update NettyConnection close and delivery behavior for QUIC: closing non-primary streams no longer forces full session closure, and outbound packet delivery is delegated to the QUIC stream router when available.

Document the new outbound stream property in system-property descriptions.
@guusdk
Copy link
Copy Markdown
Member

guusdk commented Apr 6, 2026

Commit messages use a funky literal /n

@guusdk
Copy link
Copy Markdown
Member

guusdk commented Apr 6, 2026

Can / should the admin page be merged with the pre-existing 'Client Connections' page?

That page now has two boxes, one for "Plain-text (with STARTTLS) connections" and one for "Encrypted (Direct TLS) connections".

The new 'Client Connections (QUIC)' page that's introduced in this PR only adds one box ("QUIC Client Listener") which visually looks very similar to the two boxes on the original page. Have you considered moving "QUIC Client Listener" box to the "Client Connections" page, renaming it "QUIC"? When server-to-server support is added, we can do the same there.

image

private static final Logger Log = LoggerFactory.getLogger(QuicConnectionAcceptor.class);

public static final SystemProperty<Duration> WRITE_TIMEOUT_SECONDS = SystemProperty.Builder.ofType(Duration.class)
.setKey("xmpp.quic.client.write-timeout-seconds")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a system_property.xmpp.quic.client.write-timeout-seconds i18n entry to describe the property. Such descriptions are shown in the admin console's System Property page.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class NettyConnectionTest
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like some minimal javadoc that describes what each test is supposed to test / why.


final QuicServerCodecBuilder serverCodecBuilder = new QuicServerCodecBuilder()
.sslContext(sslContext)
.tokenHandler(InsecureQuicTokenHandler.INSTANCE)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc of InsecureQuicTokenHandler reads:

This shouldn't be used in production.

Maybe we shouldn't use this in production.

.initialMaxStreamDataBidirectionalLocal(10 * 1024 * 1024)
.initialMaxStreamDataBidirectionalRemote(10 * 1024 * 1024)
.initialMaxStreamsBidirectional(ConnectionSettings.Client.QUIC_MAX_STREAMS.getValue())
.handler(new NewConnectionRateLimitHandler(ConnectionType.QUIC_C2S))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With QUIC, the amount of transport-layers doesn't correspond to the amount of sessions, if I understand things correctly (which is a big 'if'). Is the rate limiter that's applied here on the transport, correct? Is this roughly equivalent to 'generate a new session' here, or do we now rate limit a transport on which many, many different sessions are likely to be created (should this rate limiter be moved to inside the initChannel thing?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fascinating question wherein I lack the knowledge to answer. My guess is that it's "connection", ie QUIC session, not "stream".

}

@Override
public synchronized void reconfigure(final ConnectionConfiguration configuration)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Netty / TCP variant of reconfigure tries to modify state of all existing channels. This implementation does not. Is that OK?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not. I'll see about fixing.

Comment thread xmppserver/pom.xml
<groupId>io.netty</groupId>
<artifactId>netty-codec-native-quic</artifactId>
<version>${netty.version}</version>
<classifier>linux-x86_64</classifier>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only works on (certain flavors of) Linux? Can this be made working on Windows? Alternatively, do things crash and burn when loaded on Windows? Should we have some kind of detection mechanism that gracefully informs an administrator if and why QUIC support is (un)available?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, apparently... The underlying library (Quiche) should work fine on Windows etc though. I'll look into getting it built on as many platforms as possible.

.sslContext(sslContext)
.tokenHandler(InsecureQuicTokenHandler.INSTANCE)
.maxIdleTimeout(ConnectionSettings.Client.QUIC_IDLE_TIMEOUT_PROPERTY.getValue().toMillis(), TimeUnit.MILLISECONDS)
.initialMaxData(10 * 1024 * 1024)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these three hardcoded 10 * 1024 * 1024 values be made configurable?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what MaxData is, actually. The other two are maxed out, then overridden by the bidirectional property.

@dwd
Copy link
Copy Markdown
Member Author

dwd commented Apr 7, 2026

Can / should the admin page be merged with the pre-existing 'Client Connections' page?

Absolutely.

@dwd
Copy link
Copy Markdown
Member Author

dwd commented Apr 7, 2026

Commit messages use a funky literal /n

Ew. Yes, I don't know why it's done that.

dwd and others added 11 commits April 23, 2026 12:03
…, async stream open, factory guard

Bug 1: openOutboundStream() previously only added StringEncoder and WriteTimeoutHandler to
server-initiated QUIC streams, leaving them with no inbound pipeline. Any data sent by the
client on those streams was silently discarded and NettyConnection.reinit() would NPE.
Fix: openOutboundStreamAsync() now builds the same full pipeline as the client-initiated
stream initializer in QuicConnectionAcceptor (traffic shaping, idle/keepalive, NettyXMPPDecoder,
StringEncoder, write-timeout, autoRead gate, QuicClientConnectionHandler).
QuicSessionStreamRouter now receives ConnectionConfiguration and EventExecutorGroup from
QuicConnectionAcceptor so it can construct the handler correctly.

Bug 2: openOutboundStream() called awaitUninterruptibly(250ms) on the stream-open future,
which can deadlock when called from a Netty I/O thread (deliver() -> writeStanza() path).
Fix: replaced with a fully async approach. openOutboundStreamAsync() attaches a listener to
the future; stanzas that arrive while the open is in flight are queued in pendingStanzas and
flushed onto the new stream (or the primary stream as fallback) once the future completes.
A streamOpenInFlight flag prevents concurrent open attempts.

Bug 3: NettyConnectionHandlerFactory silently produced a QuicClientConnectionHandler without
a QuicSessionStreamRouter for QUIC_C2S, which would fall back to plain ClientStanzaHandler
and break multi-stream session sharing. Fix: throw UnsupportedOperationException with a clear
message directing callers to QuicConnectionAcceptor.

Tests: NettyConnectionHandlerFactoryTest covers the UnsupportedOperationException for QUIC_C2S
and verifies correct handler types for SOCKET_C2S and SOCKET_S2S.

Co-authored-by: Junie <junie@jetbrains.com>
…t, ALPN/token-handler docs

- QuicSessionStreamRouter: replace CopyOnWriteArrayList with plain ArrayList for
  inboundConnections and outboundChannels. All access is already guarded by the
  instance monitor (synchronized methods), so COWL was redundant and misleading.

- ConnectionSettings.Client.QUIC_MAX_OUTBOUND_STREAMS: change default from 20 to 0.
  The previous default of 20 could cause the server to attempt opening more
  server-initiated streams than the client permits (QUIC_MAX_STREAMS defaults to 25
  inbound, leaving no headroom). Defaulting to 0 disables server-initiated streams
  until an operator explicitly enables them. Added a Javadoc comment explaining the
  relationship between the two properties.

- ConnectionSettings.Client.QUIC_ALPN: add Javadoc noting that 'xmpp-client' is a
  placeholder pending finalisation of the IETF XMPP-over-QUIC draft.

- QuicConnectionAcceptor: add a TODO comment on InsecureQuicTokenHandler warning that
  it must be replaced with a real address-validation token handler before production
  deployment to prevent UDP amplification attacks.

Co-authored-by: Junie <junie@jetbrains.com>
On aux QUIC streams the stream-open exchange is pure boilerplate —
the session is already authenticated and bound on the primary stream.
This change makes the server proactively initialise each aux stream
(both client-initiated and server-initiated) without waiting for the
client to send <stream:stream>:

- Add QuicClientStanzaHandler.initAsAuxStream(LocalClientSession):
  sets session + sessionCreated=true, wires the NettyConnection via
  reinit(), and sends the server stream-open response immediately.
- Override QuicClientConnectionHandler.handlerAdded(): after the
  parent creates the NettyConnection and stanza handler, if a session
  already exists on the stream router (i.e. this is an aux stream),
  call initAsAuxStream() so the stream is ready for stanzas as soon
  as the channel becomes active.
- Netty buffers the stream-open write until channelActive fires, so
  issuing it from handlerAdded() is safe.
- Clients that still send <stream:stream> on an aux stream are handled
  gracefully: createSession() detects sessionCreated=true and simply
  sends another stream-open response without re-wiring the session.
- Server-initiated (outbound) aux streams benefit automatically: the
  existing openOutboundStreamAsync() pipeline already uses
  QuicClientConnectionHandler with the stream router, so handlerAdded
  triggers initAsAuxStream() there too.

Co-authored-by: Junie <junie@jetbrains.com>
Addresses client-agent diagnostic request for stream-credit investigation.

Answers to client questions:
1. QUIC stack: Netty/quiche (netty-codec-classes-quic 4.2.10.Final)
   ALPN: configurable via xmpp.quic.client.alpn, default 'xmpp-client'

2. Transport parameters advertised to client (logged at INFO on startup):
   - initial_max_streams_bidi  : 25 (xmpp.quic.client.max-streams, default 25)
   - initial_max_streams_uni   : 0  (uni streams not used)
   - initial_max_data          : 10 MB
   - initial_max_stream_data_bidi_local  : 10 MB
   - initial_max_stream_data_bidi_remote : 10 MB
   - initial_max_stream_data_uni         : 0
   - max_idle_timeout          : 360000 ms (xmpp.quic.client.idle, default 6 min)

3. MAX_STREAMS frames: handled automatically by quiche as streams close.
   No application-layer intervention required.

4/5. Server accepts all client-initiated bidi streams via streamHandler;
   server-initiated streams controlled by xmpp.quic.client.max-outbound-streams
   (default 0 = disabled).

6. Idle timeout: server advertises 6 min; connection teardown at 14-20 s
   suggests the *client* has a shorter idle timeout or keepalive issue.

Changes:
- Log all transport parameters at INFO level when listener starts so they
  can be verified from server logs without a packet capture.
- Add xmpp.quic.client.qlog-dir property: when set to a writable directory,
  enables per-connection qlog files (one .qlog file per QUIC connection).
  Use qvis (https://qvis.quictools.info/) to inspect transport_parameters_set
  and max_streams frames. Disabled by default (empty string).
- Add explicit .initialMaxStreamsUnidirectional(0) to document intent.
- Add QLogConfiguration/QuicChannelOption imports.

Co-authored-by: Junie <junie@jetbrains.com>
Adds INFO-level logging in two places to make inbound QUIC stream handling
observable from the server log alone (no qlog capture needed):

 - QuicConnectionAcceptor stream handler: logs streamId, type, parent QUIC
   channel and remote address for every new inbound stream, so we can verify
   that a client open_bi() request actually reached the server.
 - QuicClientConnectionHandler.handlerAdded: logs whether the stream was
   classified as PRIMARY (no session yet on the QUIC connection, awaiting
   <stream:stream>) or AUX (session already bound, server-side auto-init).

This directly addresses debugging of the reported symptom where client
open_bi() for aux stream slot 0 never resolves: if the server log shows
'QUIC inbound stream initialised streamId=...', the request reached us
and was accepted by the codec; if it does not, the issue is transport-layer
(credits, flow control, idle timeout) rather than application-layer.

Co-authored-by: Junie <junie@jetbrains.com>
…ents

Add a per-connection handler on the QuicChannel pipeline that logs:
- At connection establishment: peerAllowedBidiStreams and peerAllowedUniStreams
  (how many server-initiated streams the client is granting us)
- On QuicStreamLimitChangedEvent: updated peer stream credit counts

This is diagnostic infrastructure to help determine whether client-initiated
aux stream opens are failing at the transport layer (STREAM frames not arriving)
or the application layer (frames arrive but server doesn't accept them).

If 'QUIC inbound stream initialised' never appears in the log after bind, but
'QUIC connection established' does, the client's open_bi() frames are not
reaching the server's UDP socket — the issue is transport-layer (NAT, firewall,
or client-side stream-credit misreading), not server application code.

Co-authored-by: Junie <junie@jetbrains.com>
Aux streams now carry bare stanzas immediately with no <stream:stream>
handshake. The server no longer sends a stream-open response when
initialising an aux stream (initAsAuxStream), and if the client
unexpectedly sends a stream-open on an aux stream the server wires up
the session but does not reply with a stream-open of its own.

Removed now-unused StringUtils import.

Co-authored-by: Junie <junie@jetbrains.com>
Per XEP-0467 §3.3, XEP-0198 MUST NOT be advertised or negotiated over
QUIC. Suppress the <sm xmlns='urn:xmpp:sm:2'/> and <sm xmlns='urn:xmpp:sm:3'/>
stream features for QUIC connections, and reject any client <enable/>
attempt arriving on a QUIC connection.

Co-authored-by: Junie <junie@jetbrains.com>
Per XEP-0467 §3.2, top-level non-stanza elements (stream errors, CSI
toggles, framing) MUST only be sent on the initial QUIC stream (id 0).
NettyConnection.deliverRawText now routes such writes via the
QuicSessionStreamRouter's primary stream when a router is present, even
if the NettyConnection itself sits on an aux stream.

Co-authored-by: Junie <junie@jetbrains.com>
…ime#8)

XEP-0467 SHOULDs a QUIC max_idle_timeout of at most 600 seconds.
Lower the default from 6 minutes to exactly 600 seconds to match.

Co-authored-by: Junie <junie@jetbrains.com>
…anel

The QUIC client listener settings page now exposes the
xmpp.quic.client.max-outbound-streams, xmpp.quic.client.alpn and
xmpp.quic.client.qlog-dir properties (alongside the existing port,
idle timeout and max-streams fields), with matching i18n strings and
form validation.

Co-authored-by: Junie <junie@jetbrains.com>
…s on aux streams

Bug 1 (XEP-0467 §3.2 violation): NettyConnection.close() was writing the
stream:error + </stream:stream> directly via channelHandlerContext.writeAndFlush,
bypassing the deliverRawText primary-stream routing. Fixed by routing the close
payload through the primary QuicStreamChannel when a QuicSessionStreamRouter is
present and the current channel is not the primary stream.

Bug 2 (spurious keepalive timeouts): QuicConnectionAcceptor adds IdleStateHandler
and NettyIdleStateKeepAliveHandler to every QUIC stream pipeline. On aux streams
these fire independently of the QUIC connection's own liveness, causing
connection-timeout stream:errors to be sent even when the QUIC connection is
active. Fixed by removing 'idleStateHandler' and 'keepAliveHandler' from the
pipeline in QuicClientConnectionHandler.handlerAdded() when the stream is
classified as AUX (session already bound). The QUIC connection-level idle timeout
(max_idle_timeout transport parameter) handles liveness for the whole connection.

Co-authored-by: Junie <junie@jetbrains.com>
dwd and others added 6 commits April 30, 2026 12:09
…keepalives

The connection-timeout stream:error was being generated by the application-level
IdleStateHandler + NettyIdleStateKeepAliveHandler pipeline, which used the TCP
client idle timeout (xmpp.client.idle, default 6 min) independently of QUIC
transport liveness. This caused spurious disconnections when the QUIC connection
was healthy but no XMPP stanzas had been exchanged.

Fix: QuicClientConnectionHandler.getMaxIdleTime() now returns Duration.ofMillis(-1),
signalling that no application-level idle check is desired. QuicConnectionAcceptor
skips adding idleStateHandler and keepAliveHandler to the pipeline when this value
is negative. The QUIC max_idle_timeout transport parameter (default 600s, per
XEP-0467 §2) handles connection liveness at the transport layer without generating
any additional XMPP traffic.

Also removed the now-redundant aux-stream idle-handler removal code from
QuicClientConnectionHandler.handlerAdded(), since those handlers are never added
to any QUIC stream pipeline.

Co-authored-by: Junie <junie@jetbrains.com>
The WriteTimeoutHandler was inherited from the TCP pipeline. On a QUIC
stream a write may legitimately sit in quiche's send queue for tens of
seconds while the QUIC layer paces, retransmits and honours flow /
congestion control on a slow or lossy link. Treating that as a stalled
session caused healthy clients to be disconnected with a stream:error
on high-latency / low-bandwidth paths (observed at 50 kbit / 120 ms
sending vCard payloads, with srtt ballooning to ~34 s before the timer
fired).

Liveness on QUIC is correctly handled by the transport's
max_idle_timeout (XEP-0467 §2 igniterealtime#8, 600 s default) and quiche's
loss-detection / PTO machinery; an application-level write timeout adds
no value on top of that.

Removed from both QuicConnectionAcceptor's per-stream pipeline (client-
initiated streams) and QuicSessionStreamRouter.openOutboundStreamAsync
(server-initiated streams) for symmetry. Unused imports cleaned up.
WRITE_TIMEOUT_SECONDS SystemProperty kept in place for backward
compatibility with operator configuration.

Co-authored-by: Junie <junie@jetbrains.com>
…> 16)

Per XEP-0467 §3.2, traffic not to/from the user's own bare JID or the
server's own JID SHOULD be carried on aux streams to avoid head-of-line
blocking on the control stream id 0. The routing logic in
QuicSessionStreamRouter.shouldUsePrimaryStream already implements this
correctly: it returns true only for empty 'from', the account's own
bare JID, or the server's bare JID, and falls through to aux-stream
selection for everything else.

However, QUIC_MAX_OUTBOUND_STREAMS defaulted to 0 (disabled) as a safe
initial value when the multi-stream code first landed, with the result
that openOutboundStreamAsync always returned false and every stanza
fell back to the primary stream. On a slow/lossy link this caused a
single large stanza (e.g. a vCard with an embedded photo from a remote
peer) to head-of-line-block the control stream, queuing presence,
keepalives and other small writes behind it for tens of seconds and
contributing to disconnects.

Raising the default to 16 keeps the total within the
QUIC_MAX_STREAMS budget (25 by default), leaving headroom for the
primary stream and any client-initiated aux streams, while still
honouring the peerAllowedStreams check at runtime. Operators can set
the value to 0 to disable aux streams entirely if needed.

Co-authored-by: Junie <junie@jetbrains.com>
…ing new server streams

When the server needs a non-primary QUIC stream for a stanza from a new
sender bare JID, prefer any client-initiated (inbound) stream that has
no current from-bare-JID assignment over opening a new server-initiated
(outbound) stream. Falls back to outbound-stream open only when every
existing non-primary stream is already allocated, and falls back to
round-robin reuse only when both fail.

Client-opened streams cost nothing extra to use and consume client
stream credit that has already been granted; opening a new server stream
costs a server stream credit and a round-trip. Reusing client streams
first reflects this asymmetry and avoids opening unnecessary streams.

Co-authored-by: Junie <junie@jetbrains.com>
… panel

Previously the QUIC client listener had its own sidebar entry and JSP
(connection-settings-quic-c2s.jsp). It is now embedded as a fourth
contentBox within connection-settings-socket-c2s.jsp, alongside
plaintext, Direct TLS, idle policy and rate-limit panels. The form
handles plaintext/DirectTLS/QUIC/idle/rate-limit settings atomically.

The standalone connection-settings-quic-c2s.jsp is kept as a redirect
to connection-settings-socket-c2s.jsp for any bookmarks or external
links pointing at it. The sidebar item is removed; the certificate-store
sub-sidebar for QUIC_C2S remains so identity/trust stores are still
reachable separately.

Co-authored-by: Junie <junie@jetbrains.com>
The session-details admin page previously labelled QUIC sessions as
'TCP' (the catch-all for any NettyConnection) and surfaced no QUIC
specific information. This change:

* Detects QUIC_C2S sessions via ConnectionConfiguration.getType() and
  labels them 'QUIC' in the Connection Type row.
* Adds a 'QUIC Connection' details panel for QUIC sessions only,
  showing:
    - Stream counts (client-opened / server-opened, primary stream id,
      number of remote bare-JIDs mapped to non-primary streams) — pulls
      directly from QuicSessionStreamRouter.
    - Peer-allowed bidi stream credits (how many streams the server
      may still open toward the client).
    - RTT (smoothed), congestion window, PMTU and quiche delivery-rate
      estimate from QuicConnectionPathStats (path 0).
    - Packets sent/received/lost/retransmitted and bytes sent/received/
      lost from QuicConnectionStats.
* Adds public accessors to QuicSessionStreamRouter for the counts,
  primary stream id and underlying QuicChannel; widens
  NettyConnection.getChannel() from package-private to public so the
  JSP can locate the router via QuicSessionStreamRouter.find().
* Stats are fetched synchronously with a short (250 ms) timeout — the
  admin page is rendered on demand so the small wait is acceptable and
  it keeps the implementation simple without a periodic-poll cache.

Co-authored-by: Junie <junie@jetbrains.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants