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.channel.Channel;
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.channel.ChannelInboundHandlerAdapter;
23  import io.netty.channel.ChannelInitializer;
24  import io.netty.channel.ChannelOption;
25  import org.junit.jupiter.api.Test;
26  import org.junit.jupiter.api.TestInfo;
27  import org.junit.jupiter.api.Timeout;
28  
29  import java.io.IOException;
30  import java.util.Locale;
31  import java.util.concurrent.CountDownLatch;
32  import java.util.concurrent.TimeUnit;
33  import java.util.concurrent.atomic.AtomicReference;
34  
35  import static org.junit.jupiter.api.Assertions.assertNull;
36  import static org.junit.jupiter.api.Assertions.assertTrue;
37  
38  public class SocketRstTest extends AbstractSocketTest {
39      protected void assertRstOnCloseException(IOException cause, Channel clientChannel) {
40          if (Locale.getDefault() == Locale.US || Locale.getDefault() == Locale.UK) {
41              assertTrue(cause.getMessage().contains("reset") || cause.getMessage().contains("closed"),
42                  "actual message: " + cause.getMessage());
43          }
44      }
45  
46      @Test
47      @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
48      public void testSoLingerZeroCausesOnlyRstOnClose(TestInfo testInfo) throws Throwable {
49          run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
50              @Override
51              public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
52                  testSoLingerZeroCausesOnlyRstOnClose(serverBootstrap, bootstrap);
53              }
54          });
55      }
56  
57      public void testSoLingerZeroCausesOnlyRstOnClose(ServerBootstrap sb, Bootstrap cb) throws Throwable {
58          final AtomicReference<Channel> serverChannelRef = new AtomicReference<Channel>();
59          final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
60          final CountDownLatch latch = new CountDownLatch(1);
61          final CountDownLatch latch2 = new CountDownLatch(1);
62          // SO_LINGER=0 means that we must send ONLY a RST when closing (not a FIN + RST).
63          sb.childOption(ChannelOption.SO_LINGER, 0);
64          sb.childHandler(new ChannelInitializer<Channel>() {
65              @Override
66              protected void initChannel(Channel ch) throws Exception {
67                  serverChannelRef.compareAndSet(null, ch);
68                  latch.countDown();
69              }
70          });
71          cb.handler(new ChannelInitializer<Channel>() {
72              @Override
73              protected void initChannel(Channel ch) throws Exception {
74                  ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
75                      @Override
76                      public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
77                          throwableRef.compareAndSet(null, cause);
78                      }
79  
80                      @Override
81                      public void channelInactive(ChannelHandlerContext ctx) {
82                          latch2.countDown();
83                      }
84                  });
85              }
86          });
87          Channel sc = sb.bind().sync().channel();
88          Channel cc = cb.connect(sc.localAddress()).sync().channel();
89  
90          // Wait for the server to get setup.
91          latch.await();
92  
93          // The server has SO_LINGER=0 and so it must send a RST when close is called.
94          serverChannelRef.get().close();
95  
96          // Wait for the client to get channelInactive.
97          latch2.await();
98  
99          // Verify the client received a RST.
100         Throwable cause = throwableRef.get();
101         assertTrue(cause instanceof IOException,
102             "actual [type, message]: [" + cause.getClass() + ", " + cause.getMessage() + "]");
103 
104         assertRstOnCloseException((IOException) cause, cc);
105     }
106 
107     @Test
108     @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
109     public void testNoRstIfSoLingerOnClose(TestInfo testInfo) throws Throwable {
110         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
111             @Override
112             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
113                 testNoRstIfSoLingerOnClose(serverBootstrap, bootstrap);
114             }
115         });
116     }
117 
118     public void testNoRstIfSoLingerOnClose(ServerBootstrap sb, Bootstrap cb) throws Throwable {
119         final AtomicReference<Channel> serverChannelRef = new AtomicReference<Channel>();
120         final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
121         final CountDownLatch latch = new CountDownLatch(1);
122         final CountDownLatch latch2 = new CountDownLatch(1);
123         sb.childHandler(new ChannelInitializer<Channel>() {
124             @Override
125             protected void initChannel(Channel ch) throws Exception {
126                 serverChannelRef.compareAndSet(null, ch);
127                 latch.countDown();
128             }
129         });
130         cb.handler(new ChannelInitializer<Channel>() {
131             @Override
132             protected void initChannel(Channel ch) throws Exception {
133                 ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
134                     @Override
135                     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
136                         throwableRef.compareAndSet(null, cause);
137                     }
138 
139                     @Override
140                     public void channelInactive(ChannelHandlerContext ctx) {
141                         latch2.countDown();
142                     }
143                 });
144             }
145         });
146         Channel sc = sb.bind().sync().channel();
147         cb.connect(sc.localAddress()).syncUninterruptibly();
148 
149         // Wait for the server to get setup.
150         latch.await();
151 
152         // The server has SO_LINGER=0 and so it must send a RST when close is called.
153         serverChannelRef.get().close();
154 
155         // Wait for the client to get channelInactive.
156         latch2.await();
157 
158         // Verify the client did not received a RST.
159         assertNull(throwableRef.get());
160     }
161 }