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.netty5.testsuite.transport.socket;
17  
18  import io.netty5.bootstrap.Bootstrap;
19  import io.netty5.bootstrap.ServerBootstrap;
20  import io.netty5.channel.Channel;
21  import io.netty5.channel.ChannelHandler;
22  import io.netty5.channel.ChannelHandlerContext;
23  import io.netty5.channel.ChannelInitializer;
24  import io.netty5.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, this::testSoLingerZeroCausesOnlyRstOnClose);
50      }
51  
52      public void testSoLingerZeroCausesOnlyRstOnClose(ServerBootstrap sb, Bootstrap cb) throws Throwable {
53          final AtomicReference<Channel> serverChannelRef = new AtomicReference<>();
54          final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
55          final CountDownLatch latch = new CountDownLatch(1);
56          final CountDownLatch latch2 = new CountDownLatch(1);
57          // SO_LINGER=0 means that we must send ONLY a RST when closing (not a FIN + RST).
58          sb.childOption(ChannelOption.SO_LINGER, 0);
59          sb.childHandler(new ChannelInitializer<>() {
60              @Override
61              protected void initChannel(Channel ch) throws Exception {
62                  serverChannelRef.compareAndSet(null, ch);
63                  latch.countDown();
64              }
65          });
66          cb.handler(new ChannelInitializer<>() {
67              @Override
68              protected void initChannel(Channel ch) throws Exception {
69                  ch.pipeline().addLast(new ChannelHandler() {
70                      @Override
71                      public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
72                          throwableRef.compareAndSet(null, cause);
73                      }
74  
75                      @Override
76                      public void channelInactive(ChannelHandlerContext ctx) {
77                          latch2.countDown();
78                      }
79                  });
80              }
81          });
82          Channel sc = sb.bind().asStage().get();
83          Channel cc = cb.connect(sc.localAddress()).asStage().get();
84  
85          // Wait for the server to get setup.
86          latch.await();
87  
88          // The server has SO_LINGER=0 and so it must send a RST when close is called.
89          serverChannelRef.get().close();
90  
91          // Wait for the client to get channelInactive.
92          latch2.await();
93  
94          // Verify the client received a RST.
95          Throwable cause = throwableRef.get();
96          assertTrue(cause instanceof IOException,
97                     "actual [type, message]: [" + cause.getClass() + ", " + cause.getMessage() + ']');
98  
99          assertRstOnCloseException((IOException) cause, cc);
100     }
101 
102     @Test
103     @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
104     public void testNoRstIfSoLingerOnClose(TestInfo testInfo) throws Throwable {
105         run(testInfo, this::testNoRstIfSoLingerOnClose);
106     }
107 
108     public void testNoRstIfSoLingerOnClose(ServerBootstrap sb, Bootstrap cb) throws Throwable {
109         final AtomicReference<Channel> serverChannelRef = new AtomicReference<>();
110         final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
111         final CountDownLatch latch = new CountDownLatch(1);
112         final CountDownLatch latch2 = new CountDownLatch(1);
113         sb.childHandler(new ChannelInitializer<>() {
114             @Override
115             protected void initChannel(Channel ch) throws Exception {
116                 serverChannelRef.compareAndSet(null, ch);
117                 latch.countDown();
118             }
119         });
120         cb.handler(new ChannelInitializer<>() {
121             @Override
122             protected void initChannel(Channel ch) throws Exception {
123                 ch.pipeline().addLast(new ChannelHandler() {
124                     @Override
125                     public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
126                         throwableRef.compareAndSet(null, cause);
127                     }
128 
129                     @Override
130                     public void channelInactive(ChannelHandlerContext ctx) {
131                         latch2.countDown();
132                     }
133                 });
134             }
135         });
136         Channel sc = sb.bind().asStage().get();
137         cb.connect(sc.localAddress()).asStage().sync();
138 
139         // Wait for the server to get setup.
140         latch.await();
141 
142         // The server has SO_LINGER=0 and so it must send a RST when close is called.
143         serverChannelRef.get().close();
144 
145         // Wait for the client to get channelInactive.
146         latch2.await();
147 
148         // Verify the client did not received a RST.
149         assertNull(throwableRef.get());
150     }
151 }