View Javadoc
1   /*
2    * Copyright 2015 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.channel.unix.tests;
17  
18  import io.netty.channel.unix.Buffer;
19  import io.netty.channel.unix.DomainSocketAddress;
20  import io.netty.channel.unix.Errors;
21  import io.netty.channel.unix.Errors.NativeIoException;
22  import io.netty.channel.unix.Limits;
23  import io.netty.channel.unix.Socket;
24  import io.netty.channel.unix.UnixChannelUtil;
25  import io.netty.util.internal.CleanableDirectBuffer;
26  import io.netty.util.internal.PlatformDependent;
27  import org.junit.jupiter.api.AfterEach;
28  import org.junit.jupiter.api.BeforeEach;
29  import org.junit.jupiter.api.Test;
30  import org.opentest4j.TestAbortedException;
31  
32  import java.io.File;
33  import java.io.IOException;
34  import java.nio.ByteBuffer;
35  import java.nio.charset.StandardCharsets;
36  import java.nio.file.Files;
37  import java.util.Arrays;
38  
39  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
40  import static org.junit.jupiter.api.Assertions.assertEquals;
41  import static org.junit.jupiter.api.Assertions.assertFalse;
42  import static org.junit.jupiter.api.Assertions.assertNotEquals;
43  import static org.junit.jupiter.api.Assertions.assertThrows;
44  import static org.junit.jupiter.api.Assertions.assertTrue;
45  
46  public abstract class SocketTest<T extends Socket> {
47      protected T socket;
48  
49      protected abstract T newSocket();
50  
51      @BeforeEach
52      public void setup() {
53          socket = newSocket();
54      }
55  
56      @AfterEach
57      public void tearDown() throws IOException {
58          socket.close();
59      }
60  
61      @Test
62      public void testKeepAlive() throws Exception {
63          assertFalse(socket.isKeepAlive());
64          socket.setKeepAlive(true);
65          assertTrue(socket.isKeepAlive());
66      }
67  
68      @Test
69      public void testTcpNoDelay() throws Exception {
70          assertFalse(socket.isTcpNoDelay());
71          socket.setTcpNoDelay(true);
72          assertTrue(socket.isTcpNoDelay());
73      }
74  
75      @Test
76      public void testReceivedBufferSize() throws Exception {
77          int size = socket.getReceiveBufferSize();
78          int newSize = 65535;
79          assertTrue(size > 0);
80          socket.setReceiveBufferSize(newSize);
81          // Linux usually set it to double what is specified
82          assertTrue(newSize <= socket.getReceiveBufferSize());
83      }
84  
85      @Test
86      public void testSendBufferSize() throws Exception {
87          int size = socket.getSendBufferSize();
88          int newSize = 65535;
89          assertTrue(size > 0);
90          socket.setSendBufferSize(newSize);
91          // Linux usually set it to double what is specified
92          assertTrue(newSize <= socket.getSendBufferSize());
93      }
94  
95      @Test
96      public void testSoLinger() throws Exception {
97          assertEquals(-1, socket.getSoLinger());
98          socket.setSoLinger(10);
99          assertEquals(10, socket.getSoLinger());
100     }
101 
102     @Test
103     public void testDoubleCloseDoesNotThrow() throws IOException {
104         Socket socket = Socket.newSocketStream();
105         socket.close();
106         socket.close();
107     }
108 
109     @Test
110     public void testTrafficClass() throws IOException {
111         // IPTOS_THROUGHPUT
112         final int value = 0x08;
113         socket.setTrafficClass(value);
114         assertEquals(value, socket.getTrafficClass());
115     }
116 
117     @Test
118     public void testIntOpt() throws IOException {
119         socket.setReuseAddress(false);
120         socket.setIntOpt(level(), optname(), 1);
121         // Anything which is != 0 is considered enabled
122         assertNotEquals(0, socket.getIntOpt(level(), optname()));
123         socket.setIntOpt(level(), optname(), 0);
124         // This should be disabled again
125         assertEquals(0, socket.getIntOpt(level(), optname()));
126     }
127 
128     @Test
129     public void testRawOpt() throws IOException {
130         CleanableDirectBuffer cleanableDirectBuffer = Buffer.allocateDirectBufferWithNativeOrder(4);
131         ByteBuffer buffer = cleanableDirectBuffer.buffer();
132         buffer.putInt(1).flip();
133         socket.setRawOpt(level(), optname(), buffer);
134 
135         ByteBuffer out = ByteBuffer.allocate(4);
136         socket.getRawOpt(level(), optname(), out);
137         assertFalse(out.hasRemaining());
138 
139         out.flip();
140         assertNotEquals(ByteBuffer.allocate(0), out);
141         cleanableDirectBuffer.clean();
142     }
143 
144     @Test
145     public void testSendToDomainSocketWithTooLongPath() throws IOException {
146         byte[] path = new byte[4096];
147         Arrays.fill(path, (byte) 'a');
148         path[0] = '/';
149 
150         CleanableDirectBuffer cleanableDirectBuffer = Buffer.allocateDirectBufferWithNativeOrder(1);
151         Socket domainSocket = Socket.newSocketDomainDgram();
152         try {
153             ByteBuffer buffer = cleanableDirectBuffer.buffer();
154             // The path must be rejected instead of silently being truncated and sent to a different address.
155             NativeIoException e = assertThrows(NativeIoException.class,
156                     () -> domainSocket.sendToDomainSocket(buffer, 0, 1, path));
157             assertEquals(Errors.ERRNO_ENAMETOOLONG_NEGATIVE, e.expectedErr());
158         } finally {
159             domainSocket.close();
160             cleanableDirectBuffer.clean();
161         }
162     }
163 
164     protected int level() {
165         throw new TestAbortedException("Not supported");
166     }
167 
168     protected int optname() {
169         throw new TestAbortedException("Not supported");
170     }
171 
172     @Test
173     public void testBindDomainSocketDoesNotRemoveRegularFile() throws Exception {
174         File file = PlatformDependent.createTempFile("NETTY", "UDS", null);
175         try {
176             byte[] content = "do not delete".getBytes(StandardCharsets.US_ASCII);
177             Files.write(file.toPath(), content);
178 
179             Socket domainSocket = Socket.newSocketDomain();
180             try {
181                 assertThrows(IOException.class, () -> domainSocket.bind(new DomainSocketAddress(file)));
182             } finally {
183                 domainSocket.close();
184             }
185             assertTrue(file.isFile());
186             assertArrayEquals(content, Files.readAllBytes(file.toPath()));
187         } finally {
188             file.delete();
189         }
190     }
191 
192     @Test
193     public void testBindDomainSocketReplacesStaleSocket() throws Exception {
194         DomainSocketAddress address = UnixTestUtils.newDomainSocketAddress();
195         File file = new File(address.path());
196         try {
197             Socket first = Socket.newSocketDomain();
198             try {
199                 first.bind(address);
200             } finally {
201                 first.close();
202             }
203             // The socket inode is left behind after close.
204             assertTrue(file.exists());
205 
206             Socket second = Socket.newSocketDomain();
207             try {
208                 second.bind(address);
209             } finally {
210                 second.close();
211             }
212         } finally {
213             file.delete();
214         }
215     }
216 
217     @Test
218     public void testDeleteDomainSocketFileOnlyDeletesSockets() throws Exception {
219         File regular = PlatformDependent.createTempFile("NETTY", "UDS", null);
220         DomainSocketAddress address = UnixTestUtils.newDomainSocketAddress();
221         File socketFile = new File(address.path());
222         try {
223             assertFalse(UnixChannelUtil.deleteDomainSocketFile(regular.getPath()));
224             assertTrue(regular.exists());
225 
226             Socket domainSocket = Socket.newSocketDomain();
227             try {
228                 domainSocket.bind(address);
229             } finally {
230                 domainSocket.close();
231             }
232             assertTrue(UnixChannelUtil.deleteDomainSocketFile(address.path()));
233             assertFalse(socketFile.exists());
234             // Does not exist anymore.
235             assertFalse(UnixChannelUtil.deleteDomainSocketFile(address.path()));
236         } finally {
237             regular.delete();
238             socketFile.delete();
239         }
240     }
241 
242     @Test
243     public void testConnectDomainSocketWithMaxLengthPath() throws Exception {
244         int max = Limits.UDS_SUN_PATH_SIZE;
245         assertTrue(max >= 100);
246         // A path that fills sun_path completely does not need a NUL terminator and must be handled deterministically.
247         for (int i = 0; i < 100; i++) {
248             assertFalse(isConnectPathTooLong(max));
249         }
250         assertTrue(isConnectPathTooLong(max + 1));
251     }
252 
253     private static boolean isConnectPathTooLong(int length) throws IOException {
254         StringBuilder path = new StringBuilder("/");
255         while (path.length() < length) {
256             path.append('a');
257         }
258         Socket domainSocket = Socket.newSocketDomain();
259         try {
260             domainSocket.connect(new DomainSocketAddress(path.toString()));
261             return false;
262         } catch (IOException e) {
263             return e.getMessage() != null && e.getMessage().contains("too long");
264         } finally {
265             domainSocket.close();
266         }
267     }
268 }