View Javadoc
1   /*
2    * Copyright 2016 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
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             // Connect and directly close again.
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         // TFO is an almost-pure optimisation and should not change any observable behaviour in our tests.
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 }