1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.testsuite.transport.socket;
17
18 import io.netty.bootstrap.Bootstrap;
19 import io.netty.bootstrap.ServerBootstrap;
20 import io.netty.buffer.ByteBuf;
21 import io.netty.buffer.CompositeByteBuf;
22 import io.netty.channel.Channel;
23 import io.netty.channel.ChannelFuture;
24 import io.netty.channel.ChannelFutureListener;
25 import io.netty.channel.ChannelHandlerContext;
26 import io.netty.channel.ChannelInboundHandlerAdapter;
27 import io.netty.channel.ChannelInitializer;
28 import io.netty.channel.ChannelOption;
29 import io.netty.channel.socket.SocketChannel;
30 import io.netty.util.concurrent.ImmediateEventExecutor;
31 import io.netty.util.concurrent.Promise;
32 import org.junit.jupiter.api.Test;
33 import org.junit.jupiter.api.TestInfo;
34 import org.junit.jupiter.api.Timeout;
35
36 import java.io.ByteArrayOutputStream;
37 import java.net.InetSocketAddress;
38 import java.net.SocketAddress;
39 import java.util.concurrent.BlockingQueue;
40 import java.util.concurrent.LinkedBlockingQueue;
41 import java.util.concurrent.Semaphore;
42 import java.util.concurrent.TimeUnit;
43 import java.util.function.Function;
44
45 import static io.netty.buffer.ByteBufUtil.writeAscii;
46 import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
47 import static io.netty.util.CharsetUtil.US_ASCII;
48 import static org.junit.jupiter.api.Assertions.assertEquals;
49 import static org.junit.jupiter.api.Assertions.assertFalse;
50 import static org.junit.jupiter.api.Assertions.assertNotNull;
51 import static org.junit.jupiter.api.Assertions.assertNull;
52 import static org.junit.jupiter.api.Assertions.assertTrue;
53
54 public class SocketConnectTest extends AbstractSocketTest {
55
56 @Test
57 @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
58 public void testCloseTwice(TestInfo testInfo) throws Throwable {
59 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
60 @Override
61 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
62 testCloseTwice(serverBootstrap, bootstrap);
63 }
64 });
65 }
66
67 public void testCloseTwice(ServerBootstrap sb, Bootstrap cb) throws Throwable {
68 Channel serverChannel = null;
69 Channel clientChannel = null;
70 try {
71 serverChannel = sb.childHandler(new ChannelInboundHandlerAdapter()).bind().syncUninterruptibly().channel();
72 final BlockingQueue<ChannelFuture> futures = new LinkedBlockingQueue<>();
73 clientChannel = cb.handler(new ChannelInboundHandlerAdapter() {
74 @Override
75 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
76 futures.add(ctx.close());
77 }
78 })
79 .connect(serverChannel.localAddress()).syncUninterruptibly().channel();
80 clientChannel.pipeline().fireUserEventTriggered("test");
81 clientChannel.close().syncUninterruptibly();
82 futures.take().sync();
83 clientChannel = null;
84
85 serverChannel.close().syncUninterruptibly();
86 serverChannel.close().syncUninterruptibly();
87 serverChannel = null;
88 } finally {
89 if (clientChannel != null) {
90 clientChannel.close().syncUninterruptibly();
91 }
92 if (serverChannel != null) {
93 serverChannel.close().syncUninterruptibly();
94 }
95 }
96 }
97
98 @Test
99 @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
100 public void testLocalAddressAfterConnect(TestInfo testInfo) throws Throwable {
101 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
102 @Override
103 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
104 testLocalAddressAfterConnect(serverBootstrap, bootstrap);
105 }
106 });
107 }
108
109 public void testLocalAddressAfterConnect(ServerBootstrap sb, Bootstrap cb) throws Throwable {
110 Channel serverChannel = null;
111 Channel clientChannel = null;
112 try {
113 final Promise<InetSocketAddress> localAddressPromise = ImmediateEventExecutor.INSTANCE.newPromise();
114 serverChannel = sb.childHandler(new ChannelInboundHandlerAdapter() {
115 @Override
116 public void channelActive(ChannelHandlerContext ctx) throws Exception {
117 localAddressPromise.setSuccess((InetSocketAddress) ctx.channel().localAddress());
118 }
119 }).bind().syncUninterruptibly().channel();
120
121 clientChannel = cb.handler(new ChannelInboundHandlerAdapter()).register().syncUninterruptibly().channel();
122
123 assertNull(clientChannel.localAddress());
124 assertNull(clientChannel.remoteAddress());
125
126 clientChannel.connect(serverChannel.localAddress()).syncUninterruptibly().channel();
127 assertLocalAddress((InetSocketAddress) clientChannel.localAddress());
128 assertNotNull(clientChannel.remoteAddress());
129
130 assertLocalAddress(localAddressPromise.get());
131 } finally {
132 if (clientChannel != null) {
133 clientChannel.close().syncUninterruptibly();
134 }
135 if (serverChannel != null) {
136 serverChannel.close().syncUninterruptibly();
137 }
138 }
139 }
140
141 @Test
142 @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
143 public void testChannelEventsFiredWhenClosedDirectly(TestInfo testInfo) throws Throwable {
144 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
145 @Override
146 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
147 testChannelEventsFiredWhenClosedDirectly(serverBootstrap, bootstrap);
148 }
149 });
150 }
151
152 public void testChannelEventsFiredWhenClosedDirectly(ServerBootstrap sb, Bootstrap cb) throws Throwable {
153 final BlockingQueue<Integer> events = new LinkedBlockingQueue<Integer>();
154
155 Channel sc = null;
156 Channel cc = null;
157 try {
158 sb.childHandler(new ChannelInboundHandlerAdapter());
159 sc = sb.bind().syncUninterruptibly().channel();
160
161 cb.handler(new ChannelInboundHandlerAdapter() {
162 @Override
163 public void channelActive(ChannelHandlerContext ctx) throws Exception {
164 events.add(0);
165 }
166
167 @Override
168 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
169 events.add(1);
170 }
171 });
172
173 cc = cb.connect(sc.localAddress()).addListener(ChannelFutureListener.CLOSE).
174 syncUninterruptibly().channel();
175 assertEquals(0, events.take().intValue());
176 assertEquals(1, events.take().intValue());
177 } finally {
178 if (cc != null) {
179 cc.close();
180 }
181 if (sc != null) {
182 sc.close();
183 }
184 }
185 }
186
187 @Test
188 @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
189 public void testWriteWithFastOpenBeforeConnect(TestInfo testInfo) throws Throwable {
190 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
191 @Override
192 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
193 testWriteWithFastOpenBeforeConnect(serverBootstrap, bootstrap);
194 }
195 });
196 }
197
198 @Test
199 @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
200 public void testWriteWithFastOpenBeforeConnectDirectBufferReaderIndex(TestInfo testInfo) throws Throwable {
201 run(testInfo, (serverBootstrap, bootstrap) ->
202 testWriteWithFastOpenBeforeConnect(serverBootstrap, bootstrap,
203 channel -> directBufferWithReaderIndex(
204 channel, "BAD-PREFIX-", "[fastopen-reader-index]"),
205 "[fastopen-reader-index]"));
206 }
207
208 @Test
209 @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
210 public void testWriteWithFastOpenBeforeConnectCompositeBuffer(TestInfo testInfo) throws Throwable {
211 run(testInfo, (serverBootstrap, bootstrap) ->
212 testWriteWithFastOpenBeforeConnect(serverBootstrap, bootstrap,
213 channel -> compositeBuffer(channel, "[fastopen-", "composite-", "data]"),
214 "[fastopen-composite-data]"));
215 }
216
217 @Test
218 @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
219 public void testWriteWithFastOpenBeforeConnectCompositeBufferReaderIndex(TestInfo testInfo) throws Throwable {
220 run(testInfo, (serverBootstrap, bootstrap) ->
221 testWriteWithFastOpenBeforeConnect(serverBootstrap, bootstrap,
222 channel -> compositeBufferWithReaderIndex(
223 channel, "BAD-PREFIX-", "[fastopen-", "composite-reader-", "index]"),
224 "[fastopen-composite-reader-index]"));
225 }
226
227 public void testWriteWithFastOpenBeforeConnect(ServerBootstrap sb, Bootstrap cb) throws Throwable {
228 testWriteWithFastOpenBeforeConnect(sb, cb, channel -> writeAscii(DEFAULT, "[fastopen]"), "[fastopen]");
229 }
230
231 protected void testWriteWithFastOpenBeforeConnect(ServerBootstrap sb, Bootstrap cb,
232 Function<Channel, ByteBuf> initialDataFactory,
233 String expectedInitialData) throws Throwable {
234 enableTcpFastOpen(sb, cb);
235 sb.childOption(ChannelOption.AUTO_READ, true);
236 cb.option(ChannelOption.AUTO_READ, true);
237
238 sb.childHandler(new ChannelInitializer<SocketChannel>() {
239 @Override
240 protected void initChannel(SocketChannel ch) throws Exception {
241 ch.pipeline().addLast(new EchoServerHandler());
242 }
243 });
244
245 Channel sc = sb.bind().sync().channel();
246 connectAndVerifyDataTransfer(cb, sc, initialDataFactory, expectedInitialData);
247 connectAndVerifyDataTransfer(cb, sc, initialDataFactory, expectedInitialData);
248 }
249
250 private static void connectAndVerifyDataTransfer(Bootstrap cb, Channel sc,
251 Function<Channel, ByteBuf> initialDataFactory,
252 String expectedInitialData)
253 throws InterruptedException {
254 BufferingClientHandler handler = new BufferingClientHandler();
255 cb.handler(handler);
256 ChannelFuture register = cb.register();
257 Channel channel = register.sync().channel();
258 ChannelFuture write = channel.write(initialDataFactory.apply(channel));
259 SocketAddress remoteAddress = sc.localAddress();
260 ChannelFuture connectFuture = channel.connect(remoteAddress);
261 Channel cc = connectFuture.sync().channel();
262 cc.writeAndFlush(writeAscii(DEFAULT, "[normal data]")).sync();
263 write.sync();
264 String expectedString = expectedInitialData + "[normal data]";
265 String result = handler.collectBuffer(expectedString.getBytes(US_ASCII).length);
266 cc.disconnect().sync();
267 assertEquals(expectedString, result);
268 }
269
270 private static ByteBuf directBufferWithReaderIndex(Channel channel, String prefix, String data) {
271 ByteBuf buffer = directBuffer(channel, prefix + data);
272 buffer.readerIndex(prefix.getBytes(US_ASCII).length);
273 return buffer;
274 }
275
276 private static ByteBuf compositeBufferWithReaderIndex(Channel channel, String prefix, String... parts) {
277 CompositeByteBuf buffer = channel.alloc().compositeDirectBuffer(parts.length + 1);
278 buffer.addComponent(true, directBuffer(channel, prefix));
279 for (String part : parts) {
280 buffer.addComponent(true, directBuffer(channel, part));
281 }
282 buffer.readerIndex(prefix.getBytes(US_ASCII).length);
283 return buffer;
284 }
285
286 private static ByteBuf compositeBuffer(Channel channel, String... parts) {
287 CompositeByteBuf buffer = channel.alloc().compositeDirectBuffer(parts.length);
288 for (String part : parts) {
289 buffer.addComponent(true, directBuffer(channel, part));
290 }
291 return buffer;
292 }
293
294 private static ByteBuf directBuffer(Channel channel, String data) {
295 byte[] bytes = data.getBytes(US_ASCII);
296 ByteBuf buffer = channel.alloc().directBuffer(bytes.length);
297 buffer.writeBytes(bytes);
298 return buffer;
299 }
300
301 protected void enableTcpFastOpen(ServerBootstrap sb, Bootstrap cb) {
302
303 sb.option(ChannelOption.TCP_FASTOPEN, 5);
304 cb.option(ChannelOption.TCP_FASTOPEN_CONNECT, true);
305 }
306
307 private static void assertLocalAddress(InetSocketAddress address) {
308 assertTrue(address.getPort() > 0);
309 assertFalse(address.getAddress().isAnyLocalAddress());
310 }
311
312 private static class BufferingClientHandler extends ChannelInboundHandlerAdapter {
313 private final Semaphore semaphore = new Semaphore(0);
314 private final ByteArrayOutputStream streamBuffer = new ByteArrayOutputStream();
315
316 @Override
317 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
318 if (msg instanceof ByteBuf) {
319 ByteBuf buf = (ByteBuf) msg;
320 int readableBytes = buf.readableBytes();
321 buf.readBytes(streamBuffer, readableBytes);
322 semaphore.release(readableBytes);
323 buf.release();
324 } else {
325 throw new IllegalArgumentException("Unexpected message type: " + msg);
326 }
327 }
328
329 String collectBuffer(int expectedBytes) throws InterruptedException {
330 semaphore.acquire(expectedBytes);
331 byte[] bytes = streamBuffer.toByteArray();
332 streamBuffer.reset();
333 return new String(bytes, US_ASCII);
334 }
335 }
336
337 private static final class EchoServerHandler extends ChannelInboundHandlerAdapter {
338 @Override
339 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
340 if (msg instanceof ByteBuf) {
341 ByteBuf buffer = ctx.alloc().buffer();
342 ByteBuf buf = (ByteBuf) msg;
343 buffer.writeBytes(buf);
344 buf.release();
345 ctx.channel().writeAndFlush(buffer);
346 } else {
347 throw new IllegalArgumentException("Unexpected message type: " + msg);
348 }
349 }
350 }
351 }