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