Skip to content
This repository was archived by the owner on May 8, 2026. It is now read-only.

Commit 6d32ba2

Browse files
authored
Expose QuicSslContextBuilder::sni (#851)
**Motivation:** In order to be able to receive the `SniCompletionEvent` sni mapping needs to be used, however, the function `QuicSslContextBuilder::buildForServerWithSni` doesn't allow setting `ClientAuth` and alternativly the builder doesn't allow setting the SNI mapping. This means there is currently no way to use both SNI mapping and support/enforce client certs with QUIC. **Modification:** Expose the `QuicSslContextBuilder::sni` so that its possible to create a `QuicSslContext` with SNI mapping alongside other settings. Also adds `QuicChannelConnectTest::testSniWithClientAuth` to validate **Result:** The following then works to allow both receiving the `SniCompletionEvent` and require a client cert ``` // Build context with clientAuth and trustManager configured QuicSslContext sniContext = QuicSslContextBuilder.forServer(key, null, cert) .applicationProtocols("alpn/1") .clientAuth(ClientAuth.REQUIRE) // ... .build(); // Create SNI-enabled context using the builder's sni() method QuicSslContext serverContext = QuicSslContextBuilder.forServer(key, null, cert) .clientAuth(ClientAuth.REQUIRE) .sni(hostname -> sniContext) .build(); ``` Port of netty/netty#16178
1 parent 5a7b6ef commit 6d32ba2

2 files changed

Lines changed: 92 additions & 1 deletion

File tree

codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicSslContextBuilder.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,27 @@ private QuicSslContextBuilder(boolean forServer) {
179179
this.forServer = forServer;
180180
}
181181

182-
private QuicSslContextBuilder sni(Mapping<? super String, ? extends QuicSslContext> mapping) {
182+
/**
183+
* Enables
184+
* <a href="https://quicwg.org/ops-drafts/draft-ietf-quic-manageability.html#name-server-name-indication-sni">
185+
* SNI</a> support on the server side.
186+
* <p>
187+
* The provided {@link Mapping} receives the hostname from the client and returns the
188+
* {@link QuicSslContext} to use for that connection. The returned context's settings
189+
* (such as {@link #clientAuth(ClientAuth)}, {@link #trustManager(TrustManagerFactory)}, etc.)
190+
* will be applied to the connection.
191+
* <p>
192+
* Use {@link io.netty.util.DomainWildcardMappingBuilder} to create the {@link Mapping} when
193+
* matching against domain patterns is needed.
194+
*
195+
* @param mapping the {@link Mapping} that maps hostnames to {@link QuicSslContext} instances
196+
* @return this builder
197+
* @throws NullPointerException if {@code mapping} is {@code null}
198+
*/
199+
public QuicSslContextBuilder sni(Mapping<? super String, ? extends QuicSslContext> mapping) {
200+
if (!forServer) {
201+
throw new UnsupportedOperationException("Only supported for server");
202+
}
183203
this.mapping = checkNotNull(mapping, "mapping");
184204
return this;
185205
}

codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicChannelConnectTest.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,77 @@ TestQuicTokenHandler.INSTANCE, new ChannelInboundHandlerAdapter(),
15421542
}
15431543
}
15441544

1545+
@ParameterizedTest
1546+
@MethodSource("newSslTaskExecutors")
1547+
public void testSniWithClientAuth(Executor executor) throws Throwable {
1548+
String hostname = "quic.netty.io";
1549+
1550+
QuicSslContext sniServerSslContext = QuicSslContextBuilder.forServer(
1551+
QuicTestUtils.SELF_SIGNED_CERTIFICATE.privateKey(), null,
1552+
QuicTestUtils.SELF_SIGNED_CERTIFICATE.certificate())
1553+
.trustManager(InsecureTrustManagerFactory.INSTANCE)
1554+
.clientAuth(ClientAuth.REQUIRE)
1555+
.applicationProtocols(QuicTestUtils.PROTOS).build();
1556+
1557+
QuicSslContext serverSslContext = QuicSslContextBuilder.forServer(
1558+
QuicTestUtils.SELF_SIGNED_CERTIFICATE.privateKey(), null,
1559+
QuicTestUtils.SELF_SIGNED_CERTIFICATE.certificate())
1560+
.sni(new DomainWildcardMappingBuilder<>(sniServerSslContext)
1561+
.add(hostname, sniServerSslContext).build())
1562+
.applicationProtocols(QuicTestUtils.PROTOS).build();
1563+
1564+
CountDownLatch sniEventLatch = new CountDownLatch(1);
1565+
CountDownLatch sslEventLatch = new CountDownLatch(1);
1566+
Channel server = QuicTestUtils.newServer(QuicTestUtils.newQuicServerBuilder(executor, serverSslContext),
1567+
TestQuicTokenHandler.INSTANCE, new ChannelInboundHandlerAdapter() {
1568+
@Override
1569+
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
1570+
if (evt instanceof SniCompletionEvent) {
1571+
if (hostname.equals(((SniCompletionEvent) evt).hostname())) {
1572+
sniEventLatch.countDown();
1573+
}
1574+
} else if (evt instanceof SslHandshakeCompletionEvent) {
1575+
if (((SslHandshakeCompletionEvent) evt).isSuccess()) {
1576+
sslEventLatch.countDown();
1577+
}
1578+
}
1579+
super.userEventTriggered(ctx, evt);
1580+
}
1581+
},
1582+
new ChannelInboundHandlerAdapter());
1583+
1584+
InetSocketAddress address = (InetSocketAddress) server.localAddress();
1585+
1586+
QuicSslContext clientSslContext = QuicSslContextBuilder.forClient()
1587+
.trustManager(InsecureTrustManagerFactory.INSTANCE)
1588+
.keyManager(QuicTestUtils.SELF_SIGNED_CERTIFICATE.privateKey(), null,
1589+
QuicTestUtils.SELF_SIGNED_CERTIFICATE.certificate())
1590+
.applicationProtocols(QuicTestUtils.PROTOS).build();
1591+
1592+
Channel channel = QuicTestUtils.newClient(QuicTestUtils.newQuicClientBuilder(executor)
1593+
.sslEngineProvider(c -> clientSslContext.newEngine(c.alloc(), hostname, 8080)));
1594+
try {
1595+
ChannelActiveVerifyHandler clientQuicChannelHandler = new ChannelActiveVerifyHandler();
1596+
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
1597+
.handler(clientQuicChannelHandler)
1598+
.streamHandler(new ChannelInboundHandlerAdapter())
1599+
.remoteAddress(address)
1600+
.connect()
1601+
.get();
1602+
1603+
quicChannel.close().sync();
1604+
ChannelFuture closeFuture = quicChannel.closeFuture().await();
1605+
assertTrue(closeFuture.isSuccess());
1606+
clientQuicChannelHandler.assertState();
1607+
sniEventLatch.await();
1608+
sslEventLatch.await();
1609+
} finally {
1610+
server.close().sync();
1611+
channel.close().sync();
1612+
1613+
shutdown(executor);
1614+
}
1615+
}
15451616

15461617
@ParameterizedTest
15471618
@MethodSource("newSslTaskExecutors")

0 commit comments

Comments
 (0)