netty5/transport-blockhound-tests/src/test/java/io/netty/util/internal/NettyBlockHoundIntegrationT...

512 lines
20 KiB
Java

/*
* Copyright 2019 The Netty Project
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
* https://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.util.internal;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.nio.NioHandler;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.resolver.dns.DnsNameResolverBuilder;
import io.netty.resolver.dns.DnsServerAddressStreamProviders;
import io.netty.util.HashedWheelTimer;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.ImmediateExecutor;
import io.netty.util.concurrent.SingleThreadEventExecutor;
import io.netty.util.internal.Hidden.NettyBlockHoundIntegration;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import reactor.blockhound.BlockHound;
import reactor.blockhound.BlockingOperationError;
import reactor.blockhound.integration.BlockHoundIntegration;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.ServiceLoader;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import static io.netty.buffer.Unpooled.wrappedBuffer;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class NettyBlockHoundIntegrationTest {
@BeforeAll
public static void setUpClass() {
BlockHound.install();
}
@Test
public void testServiceLoader() {
for (BlockHoundIntegration integration : ServiceLoader.load(BlockHoundIntegration.class)) {
if (integration instanceof NettyBlockHoundIntegration) {
return;
}
}
fail("NettyBlockHoundIntegration cannot be loaded with ServiceLoader");
}
@Test
public void testBlockingCallsInNettyThreads() throws Exception {
final FutureTask<Void> future = new FutureTask<>(() -> {
Thread.sleep(0);
return null;
});
GlobalEventExecutor.INSTANCE.execute(future);
try {
future.get(5, TimeUnit.SECONDS);
fail("Expected an exception due to a blocking call but none was thrown");
} catch (ExecutionException e) {
assertThat(e.getCause(), Matchers.instanceOf(BlockingOperationError.class));
}
}
@Test
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
public void testGlobalEventExecutorTakeTask() throws InterruptedException {
testEventExecutorTakeTask(GlobalEventExecutor.INSTANCE);
}
@Test
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
public void testSingleThreadEventExecutorTakeTask() throws InterruptedException {
SingleThreadEventExecutor executor =
new SingleThreadEventExecutor(new DefaultThreadFactory("test")) {
@Override
protected void run() {
while (!confirmShutdown()) {
Runnable task = takeTask();
if (task != null) {
task.run();
}
}
}
};
testEventExecutorTakeTask(executor);
}
private static void testEventExecutorTakeTask(EventExecutor eventExecutor) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
Future<?> f = eventExecutor.schedule(latch::countDown, 10, TimeUnit.MILLISECONDS);
f.sync();
latch.await();
}
@Test
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
public void testSingleThreadEventExecutorAddTask() throws Exception {
TestLinkedBlockingQueue<Runnable> taskQueue = new TestLinkedBlockingQueue<>();
SingleThreadEventExecutor executor =
new SingleThreadEventExecutor(new DefaultThreadFactory("test")) {
@Override
protected Queue<Runnable> newTaskQueue(int maxPendingTasks) {
return taskQueue;
}
@Override
protected void run() {
while (!confirmShutdown()) {
Runnable task = takeTask();
if (task != null) {
task.run();
}
}
}
};
taskQueue.emulateContention();
CountDownLatch latch = new CountDownLatch(1);
executor.submit(() -> {
executor.execute(() -> { }); // calls addTask
latch.countDown();
});
taskQueue.waitUntilContented();
taskQueue.removeContention();
latch.await();
}
@Test
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
public void testHashedWheelTimerStartStop() throws Exception {
HashedWheelTimer timer = new HashedWheelTimer();
GlobalEventExecutor.INSTANCE.submit(timer::start).get(5, TimeUnit.SECONDS);
GlobalEventExecutor.INSTANCE.submit(timer::stop).get(5, TimeUnit.SECONDS);
}
// Tests copied from io.netty.handler.ssl.SslHandlerTest
@Test
public void testHandshakeWithExecutorThatExecuteDirectory() throws Exception {
testHandshakeWithExecutor(Runnable::run, "TLSv1.2");
}
@Test
public void testHandshakeWithExecutorThatExecuteDirectoryTLSv13() throws Exception {
assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK));
testHandshakeWithExecutor(Runnable::run, "TLSv1.3");
}
@Test
public void testHandshakeWithImmediateExecutor() throws Exception {
testHandshakeWithExecutor(ImmediateExecutor.INSTANCE, "TLSv1.2");
}
@Test
public void testHandshakeWithImmediateExecutorTLSv13() throws Exception {
assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK));
testHandshakeWithExecutor(ImmediateExecutor.INSTANCE, "TLSv1.3");
}
@Test
public void testHandshakeWithImmediateEventExecutor() throws Exception {
testHandshakeWithExecutor(ImmediateEventExecutor.INSTANCE, "TLSv1.2");
}
@Test
public void testHandshakeWithImmediateEventExecutorTLSv13() throws Exception {
assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK));
testHandshakeWithExecutor(ImmediateEventExecutor.INSTANCE, "TLSv1.3");
}
@Test
public void testHandshakeWithExecutor() throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
try {
testHandshakeWithExecutor(executorService, "TLSv1.2");
} finally {
executorService.shutdown();
}
}
@Test
public void testHandshakeWithExecutorTLSv13() throws Exception {
assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK));
ExecutorService executorService = Executors.newCachedThreadPool();
try {
testHandshakeWithExecutor(executorService, "TLSv1.3");
} finally {
executorService.shutdown();
}
}
@Test
public void testTrustManagerVerifyJDK() throws Exception {
testTrustManagerVerify(SslProvider.JDK, "TLSv1.2");
}
@Test
public void testTrustManagerVerifyTLSv13JDK() throws Exception {
assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK));
testTrustManagerVerify(SslProvider.JDK, "TLSv1.3");
}
@Test
public void testTrustManagerVerifyOpenSSL() throws Exception {
testTrustManagerVerify(SslProvider.OPENSSL, "TLSv1.2");
}
@Test
public void testTrustManagerVerifyTLSv13OpenSSL() throws Exception {
assumeTrue(SslProvider.isTlsv13Supported(SslProvider.OPENSSL));
testTrustManagerVerify(SslProvider.OPENSSL, "TLSv1.3");
}
@Test
public void testSslHandlerWrapAllowsBlockingCalls() throws Exception {
final SslContext sslClientCtx =
SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(SslProvider.JDK)
.build();
final SslHandler sslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT);
final EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory());
final CountDownLatch activeLatch = new CountDownLatch(1);
final AtomicReference<Throwable> error = new AtomicReference<>();
Channel sc = null;
Channel cc = null;
try {
sc = new ServerBootstrap()
.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelHandler() { })
.bind(new InetSocketAddress(0))
.get();
cc = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(sslHandler);
ch.pipeline().addLast(new ChannelHandler() {
@Override
public void channelActive(ChannelHandlerContext ctx) {
activeLatch.countDown();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof SslHandshakeCompletionEvent &&
((SslHandshakeCompletionEvent) evt).cause() != null) {
Throwable cause = ((SslHandshakeCompletionEvent) evt).cause();
cause.printStackTrace();
error.set(cause);
}
ctx.fireUserEventTriggered(evt);
}
});
}
})
.connect(sc.localAddress())
.addListener(future -> future.get().writeAndFlush(wrappedBuffer(new byte [] { 1, 2, 3, 4 })))
.get();
assertTrue(activeLatch.await(5, TimeUnit.SECONDS));
assertNull(error.get());
} finally {
if (cc != null) {
cc.close().syncUninterruptibly();
}
if (sc != null) {
sc.close().syncUninterruptibly();
}
group.shutdownGracefully();
ReferenceCountUtil.release(sslClientCtx);
}
}
@Test
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
public void testUnixResolverDnsServerAddressStreamProvider_Parse() throws InterruptedException {
doTestParseResolverFilesAllowsBlockingCalls(DnsServerAddressStreamProviders::unixDefault);
}
@Test
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
public void testHostsFileParser_Parse() throws InterruptedException {
doTestParseResolverFilesAllowsBlockingCalls(DnsNameResolverBuilder::new);
}
@Test
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
public void testUnixResolverDnsServerAddressStreamProvider_ParseEtcResolverSearchDomainsAndOptions()
throws InterruptedException {
EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory());
try {
DnsNameResolverBuilder builder = new DnsNameResolverBuilder(group.next())
.channelFactory(NioDatagramChannel::new);
doTestParseResolverFilesAllowsBlockingCalls(builder::build);
} finally {
group.shutdownGracefully();
}
}
private static void doTestParseResolverFilesAllowsBlockingCalls(Callable<Object> callable)
throws InterruptedException {
SingleThreadEventExecutor executor =
new SingleThreadEventExecutor(new DefaultThreadFactory("test")) {
@Override
protected void run() {
while (!confirmShutdown()) {
Runnable task = takeTask();
if (task != null) {
task.run();
}
}
}
};
try {
CountDownLatch latch = new CountDownLatch(1);
List<Object> result = new ArrayList<>();
List<Throwable> error = new ArrayList<>();
executor.execute(() -> {
try {
result.add(callable.call());
} catch (Throwable t) {
error.add(t);
}
latch.countDown();
});
latch.await();
assertEquals(0, error.size());
assertEquals(1, result.size());
} finally {
executor.shutdownGracefully();
}
}
private static void testTrustManagerVerify(SslProvider provider, String tlsVersion) throws Exception {
final SslContext sslClientCtx =
SslContextBuilder.forClient()
.sslProvider(provider)
.protocols(tlsVersion)
.trustManager(ResourcesUtil.getFile(
NettyBlockHoundIntegrationTest.class, "mutual_auth_ca.pem"))
.build();
final SslContext sslServerCtx =
SslContextBuilder.forServer(ResourcesUtil.getFile(
NettyBlockHoundIntegrationTest.class, "localhost_server.pem"),
ResourcesUtil.getFile(
NettyBlockHoundIntegrationTest.class, "localhost_server.key"),
null)
.sslProvider(provider)
.protocols(tlsVersion)
.build();
final SslHandler clientSslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT);
final SslHandler serverSslHandler = sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT);
testHandshake(sslClientCtx, clientSslHandler, serverSslHandler);
}
private static void testHandshakeWithExecutor(Executor executor, String tlsVersion) throws Exception {
final SslContext sslClientCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(SslProvider.JDK).protocols(tlsVersion).build();
final SelfSignedCertificate cert = new SelfSignedCertificate();
final SslContext sslServerCtx = SslContextBuilder.forServer(cert.key(), cert.cert())
.sslProvider(SslProvider.JDK).protocols(tlsVersion).build();
final SslHandler clientSslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT, executor);
final SslHandler serverSslHandler = sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT, executor);
testHandshake(sslClientCtx, clientSslHandler, serverSslHandler);
}
private static void testHandshake(SslContext sslClientCtx, SslHandler clientSslHandler,
SslHandler serverSslHandler) throws Exception {
EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory());
Channel sc = null;
Channel cc = null;
try {
sc = new ServerBootstrap()
.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(serverSslHandler)
.bind(new InetSocketAddress(0)).get();
Future<Channel> future = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline()
.addLast(clientSslHandler)
.addLast(new ChannelHandler() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof SslHandshakeCompletionEvent &&
((SslHandshakeCompletionEvent) evt).cause() != null) {
((SslHandshakeCompletionEvent) evt).cause().printStackTrace();
}
ctx.fireUserEventTriggered(evt);
}
});
}
}).connect(sc.localAddress());
cc = future.get();
clientSslHandler.handshakeFuture().await().sync();
serverSslHandler.handshakeFuture().await().sync();
} finally {
if (cc != null) {
cc.close().syncUninterruptibly();
}
if (sc != null) {
sc.close().syncUninterruptibly();
}
group.shutdownGracefully();
ReferenceCountUtil.release(sslClientCtx);
}
}
private static class TestLinkedBlockingQueue<T> extends LinkedBlockingQueue<T> {
private final ReentrantLock lock = new ReentrantLock();
@Override
public boolean offer(T t) {
lock.lock();
try {
return super.offer(t);
} finally {
lock.unlock();
}
}
void emulateContention() {
lock.lock();
}
void waitUntilContented() throws InterruptedException {
// wait until the lock gets contended
while (lock.getQueueLength() == 0) {
Thread.sleep(10L);
}
}
void removeContention() {
lock.unlock();
}
}
}