Improve client SNI in testSniWithApnHandler

Motivations
The test SniHandlerTest#testSniWithApnHandler() does not actually
involve SNI: given the client setup, the ClientHello in the form of hex
strings is not actually written to the wire, so the server never receives that.
We may need to write in somewhere else (e.g., channelActive()) instead of in
initChannel() in order for the hex strings to reach the server. So here
what's actually going on is an ordinary TLS C/S communication without SNI.

Modifications
The client part is modified to enable SNI by using an SslHandler with an
SSLEngine created by io.netty.handler.ssl.SslContext#newEngine(), where
the server hostname is specified. Also, more clauses are added to verify that
the SNI is indeed successful.

Results
Now the test verifies that both SNI and APN actually happen and succeed.
This commit is contained in:
Renjie Sun 2016-04-26 10:22:44 +08:00 committed by Norman Maurer
parent 6d7bed3431
commit ad27387646

View File

@ -18,11 +18,11 @@ package io.netty.handler.ssl;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
@ -41,7 +41,6 @@ import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.xml.bind.DatatypeConverter; import javax.xml.bind.DatatypeConverter;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
@ -164,25 +163,15 @@ public class SniHandlerTest {
@Test @Test
public void testSniWithApnHandler() throws Exception { public void testSniWithApnHandler() throws Exception {
SslContext nettyContext = makeSslContext(); SslContext nettyContext = makeSslContext();
SslContext leanContext = makeSslContext(); SslContext sniContext = makeSslContext();
final SslContext clientContext = makeSslClientContext(); final SslContext clientContext = makeSslClientContext();
SslContext wrappedLeanContext = null;
final CountDownLatch serverApnDoneLatch = new CountDownLatch(1); final CountDownLatch serverApnDoneLatch = new CountDownLatch(1);
final CountDownLatch clientApnDoneLatch = new CountDownLatch(1); final CountDownLatch clientApnDoneLatch = new CountDownLatch(1);
final DomainNameMapping<SslContext> mapping = new DomainMappingBuilder(nettyContext) final DomainNameMapping<SslContext> mapping = new DomainMappingBuilder(nettyContext)
.add("*.netty.io", nettyContext) .add("*.netty.io", nettyContext)
.add("*.LEANCLOUD.CN", leanContext).build(); .add("sni.fake.site", sniContext).build();
// hex dump of a client hello packet, which contains hostname "CHAT4。LEANCLOUD。CN" final SniHandler handler = new SniHandler(mapping);
final String tlsHandshakeMessageHex1 = "16030100";
// part 2
final String tlsHandshakeMessageHex = "bd010000b90303a74225676d1814ba57faff3b366" +
"3656ed05ee9dbb2a4dbb1bb1c32d2ea5fc39e0000000100008c0000001700150000164348" +
"415434E380824C45414E434C4F5544E38082434E000b000403000102000a00340032000e0" +
"00d0019000b000c00180009000a0016001700080006000700140015000400050012001300" +
"0100020003000f0010001100230000000d0020001e0601060206030501050205030401040" +
"20403030103020303020102020203000f00010133740000";
EventLoopGroup group = new NioEventLoopGroup(2); EventLoopGroup group = new NioEventLoopGroup(2);
Channel serverChannel = null; Channel serverChannel = null;
Channel clientChannel = null; Channel clientChannel = null;
@ -195,7 +184,7 @@ public class SniHandlerTest {
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
// Server side SNI. // Server side SNI.
p.addLast(new SniHandler(mapping)); p.addLast(handler);
// Catch the notification event that APN has completed successfully. // Catch the notification event that APN has completed successfully.
p.addLast(new ApplicationProtocolNegotiationHandler("foo") { p.addLast(new ApplicationProtocolNegotiationHandler("foo") {
@Override @Override
@ -212,11 +201,8 @@ public class SniHandlerTest {
cb.handler(new ChannelInitializer<Channel>() { cb.handler(new ChannelInitializer<Channel>() {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
// Simulate client side SNI (which is currently not supported). ch.pipeline().addLast(new SslHandler(clientContext.newEngine(
ch.write(Unpooled.wrappedBuffer(DatatypeConverter.parseHexBinary(tlsHandshakeMessageHex1))); ch.alloc(), "sni.fake.site", -1)));
ch.writeAndFlush(Unpooled.wrappedBuffer(DatatypeConverter.parseHexBinary(tlsHandshakeMessageHex)));
// Add a SslHandler so we can do the client side of the handshake.
ch.pipeline().addLast(clientContext.newHandler(ch.alloc()));
// Catch the notification event that APN has completed successfully. // Catch the notification event that APN has completed successfully.
ch.pipeline().addLast(new ApplicationProtocolNegotiationHandler("foo") { ch.pipeline().addLast(new ApplicationProtocolNegotiationHandler("foo") {
@Override @Override
@ -235,6 +221,8 @@ public class SniHandlerTest {
assertTrue(serverApnDoneLatch.await(5, TimeUnit.SECONDS)); assertTrue(serverApnDoneLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientApnDoneLatch.await(5, TimeUnit.SECONDS)); assertTrue(clientApnDoneLatch.await(5, TimeUnit.SECONDS));
assertThat(handler.hostname(), is("sni.fake.site"));
assertThat(handler.sslContext(), is(sniContext));
} finally { } finally {
if (serverChannel != null) { if (serverChannel != null) {
serverChannel.close().sync(); serverChannel.close().sync();