View Javadoc
1   /*
2    * Copyright 2013 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.channel.Channel;
20  import io.netty.channel.ChannelFuture;
21  import io.netty.channel.ChannelHandler;
22  import io.netty.channel.ChannelHandlerContext;
23  import io.netty.channel.ChannelInboundHandlerAdapter;
24  import io.netty.channel.ChannelOption;
25  import io.netty.channel.socket.oio.OioSocketChannel;
26  import io.netty.util.internal.SocketUtils;
27  import io.netty.util.NetUtil;
28  import io.netty.util.concurrent.GlobalEventExecutor;
29  import io.netty.util.concurrent.Promise;
30  import io.netty.util.internal.logging.InternalLoggerFactory;
31  import org.junit.jupiter.api.Test;
32  import org.junit.jupiter.api.TestInfo;
33  import org.junit.jupiter.api.Timeout;
34  
35  import java.io.IOException;
36  import java.net.ConnectException;
37  import java.net.Socket;
38  import java.util.concurrent.TimeUnit;
39  
40  import static io.netty.testsuite.transport.socket.SocketTestPermutation.BAD_HOST;
41  import static io.netty.testsuite.transport.socket.SocketTestPermutation.BAD_PORT;
42  import static io.netty.testsuite.transport.socket.SocketTestPermutation.UNASSIGNED_PORT;
43  import static org.hamcrest.CoreMatchers.*;
44  import static org.hamcrest.MatcherAssert.assertThat;
45  import static org.junit.jupiter.api.Assertions.assertFalse;
46  import static org.junit.jupiter.api.Assertions.fail;
47  import static org.junit.jupiter.api.Assumptions.assumeTrue;
48  
49  public class SocketConnectionAttemptTest extends AbstractClientSocketTest {
50  
51      @Test
52      @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
53      public void testConnectTimeout(TestInfo testInfo) throws Throwable {
54          run(testInfo, new Runner<Bootstrap>() {
55              @Override
56              public void run(Bootstrap bootstrap) throws Throwable {
57                  testConnectTimeout(bootstrap);
58              }
59          });
60      }
61  
62      public void testConnectTimeout(Bootstrap cb) throws Throwable {
63          cb.handler(new TestHandler()).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000);
64          ChannelFuture future = cb.connect(BAD_HOST, BAD_PORT);
65          try {
66              assertThat(future.await(3000), is(true));
67          } finally {
68              future.channel().close();
69          }
70      }
71  
72      @Test
73      @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
74      public void testConnectRefused(TestInfo testInfo) throws Throwable {
75          run(testInfo, new Runner<Bootstrap>() {
76              @Override
77              public void run(Bootstrap bootstrap) throws Throwable {
78                  testConnectRefused(bootstrap);
79              }
80          });
81      }
82  
83      public void testConnectRefused(Bootstrap cb) throws Throwable {
84          testConnectRefused0(cb, false);
85      }
86  
87      @Test
88      @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
89      public void testConnectRefusedHalfClosure(TestInfo testInfo) throws Throwable {
90          run(testInfo, new Runner<Bootstrap>() {
91              @Override
92              public void run(Bootstrap bootstrap) throws Throwable {
93                  testConnectRefusedHalfClosure(bootstrap);
94              }
95          });
96      }
97  
98      public void testConnectRefusedHalfClosure(Bootstrap cb) throws Throwable {
99          testConnectRefused0(cb, true);
100     }
101 
102     private static void testConnectRefused0(Bootstrap cb, boolean halfClosure) throws Throwable {
103         final Promise<Error> errorPromise = GlobalEventExecutor.INSTANCE.newPromise();
104         ChannelHandler handler = new ChannelInboundHandlerAdapter() {
105             @Override
106             public void channelActive(ChannelHandlerContext ctx) throws Exception {
107                 errorPromise.setFailure(new AssertionError("should have never been called"));
108             }
109         };
110 
111         cb.handler(handler);
112         cb.option(ChannelOption.ALLOW_HALF_CLOSURE, halfClosure);
113         ChannelFuture future = cb.connect(NetUtil.LOCALHOST, UNASSIGNED_PORT).awaitUninterruptibly();
114         assertThat(future.cause(), is(instanceOf(ConnectException.class)));
115         assertThat(errorPromise.cause(), is(nullValue()));
116     }
117 
118     @Test
119     public void testConnectCancellation(TestInfo testInfo) throws Throwable {
120         // Check if the test can be executed or should be skipped because of no network/internet connection
121         // See https://github.com/netty/netty/issues/1474
122         boolean badHostTimedOut = true;
123         Socket socket = new Socket();
124         try {
125             SocketUtils.connect(socket, SocketUtils.socketAddress(BAD_HOST, BAD_PORT), 10);
126         } catch (ConnectException e) {
127             badHostTimedOut = false;
128             // is thrown for no route to host when using Socket connect
129         } catch (Exception e) {
130             // ignore
131         } finally {
132             try {
133                 socket.close();
134             } catch (IOException e) {
135                 // ignore
136             }
137         }
138 
139         assumeTrue(badHostTimedOut, "The connection attempt to " + BAD_HOST + " does not time out.");
140 
141         run(testInfo, new Runner<Bootstrap>() {
142             @Override
143             public void run(Bootstrap bootstrap) throws Throwable {
144                 testConnectCancellation(bootstrap);
145             }
146         });
147     }
148 
149     public void testConnectCancellation(Bootstrap cb) throws Throwable {
150         cb.handler(new TestHandler()).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 4000);
151         ChannelFuture future = cb.connect(BAD_HOST, BAD_PORT);
152         try {
153             if (future.await(1000)) {
154                 if (future.isSuccess()) {
155                     fail("A connection attempt to " + BAD_HOST + " must not succeed.");
156                 } else {
157                     throw future.cause();
158                 }
159             }
160 
161             if (future.cancel(true)) {
162                 assertThat(future.channel().closeFuture().await(500), is(true));
163                 assertThat(future.isCancelled(), is(true));
164             } else {
165                 // Check if cancellation is supported or not.
166                 assertFalse(isConnectCancellationSupported(future.channel()), future.channel().getClass() +
167                         " should support connect cancellation");
168             }
169         } finally {
170             future.channel().close();
171         }
172     }
173 
174     @SuppressWarnings("deprecation")
175     protected boolean isConnectCancellationSupported(Channel channel) {
176         return !(channel instanceof OioSocketChannel);
177     }
178 
179     private static class TestHandler extends ChannelInboundHandlerAdapter {
180         @Override
181         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
182             InternalLoggerFactory.getInstance(
183                     SocketConnectionAttemptTest.class).warn("Unexpected exception:", cause);
184         }
185     }
186 }