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