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