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.channel.kqueue;
17  
18  import io.netty5.channel.DefaultFileRegion;
19  import io.netty5.channel.socket.SocketProtocolFamily;
20  import io.netty5.channel.unix.IovArray;
21  import io.netty5.channel.unix.PeerCredentials;
22  import io.netty5.channel.unix.Socket;
23  
24  import java.io.IOException;
25  import java.net.Inet6Address;
26  import java.net.InetAddress;
27  import java.net.InetSocketAddress;
28  import java.net.ProtocolFamily;
29  
30  import static io.netty5.channel.kqueue.AcceptFilter.PLATFORM_UNSUPPORTED;
31  import static io.netty5.channel.kqueue.Native.CONNECT_TCP_FASTOPEN;
32  import static io.netty5.channel.unix.Errors.ERRNO_EINPROGRESS_NEGATIVE;
33  import static io.netty5.channel.unix.Errors.ioResult;
34  import static io.netty5.channel.unix.NativeInetAddress.ipv4MappedIpv6Address;
35  import static java.util.Objects.requireNonNull;
36  
37  /**
38   * A socket which provides access BSD native methods.
39   */
40  final class BsdSocket extends Socket {
41  
42      // These limits are just based on observations. I couldn't find anything in header files which formally
43      // define these limits.
44      private static final int APPLE_SND_LOW_AT_MAX = 1 << 17;
45      private static final int FREEBSD_SND_LOW_AT_MAX = 1 << 15;
46      static final int BSD_SND_LOW_AT_MAX = Math.min(APPLE_SND_LOW_AT_MAX, FREEBSD_SND_LOW_AT_MAX);
47      /**
48       * The `endpoints` structure passed to `connectx(2)` has an optional "source interface" field,
49       * which is the index of the network interface to use.
50       * According to `if_nametoindex(3)`, the value 0 is used when no interface is specified.
51       */
52      private static final int UNSPECIFIED_SOURCE_INTERFACE = 0;
53  
54      BsdSocket(int fd, SocketProtocolFamily protocolFamily) {
55          super(fd, protocolFamily);
56      }
57  
58      void setAcceptFilter(AcceptFilter acceptFilter) throws IOException {
59          setAcceptFilter(intValue(), acceptFilter.filterName(), acceptFilter.filterArgs());
60      }
61  
62      void setTcpNoPush(boolean tcpNoPush) throws IOException {
63          setTcpNoPush(intValue(), tcpNoPush ? 1 : 0);
64      }
65  
66      void setSndLowAt(int lowAt) throws IOException {
67          setSndLowAt(intValue(), lowAt);
68      }
69  
70      public void setTcpFastOpen(boolean enableTcpFastOpen) throws IOException {
71          setTcpFastOpen(intValue(), enableTcpFastOpen ? 1 : 0);
72      }
73  
74      boolean isTcpNoPush() throws IOException {
75          return getTcpNoPush(intValue()) != 0;
76      }
77  
78      int getSndLowAt() throws IOException {
79          return getSndLowAt(intValue());
80      }
81  
82      AcceptFilter getAcceptFilter() throws IOException {
83          String[] result = getAcceptFilter(intValue());
84          return result == null ? PLATFORM_UNSUPPORTED : new AcceptFilter(result[0], result[1]);
85      }
86  
87      public boolean isTcpFastOpen() throws IOException {
88          return isTcpFastOpen(intValue()) != 0;
89      }
90  
91      PeerCredentials getPeerCredentials() throws IOException {
92          return getPeerCredentials(intValue());
93      }
94  
95      long sendFile(DefaultFileRegion src, long baseOffset, long offset, long length) throws IOException {
96          // Open the file-region as it may be created via the lazy constructor. This is needed as we directly access
97          // the FileChannel field via JNI.
98          src.open();
99  
100         long res = sendFile(intValue(), src, baseOffset, offset, length);
101         if (res >= 0) {
102             return res;
103         }
104         return ioResult("sendfile", (int) res);
105     }
106 
107     /**
108      * Establish a connection to the given destination address, and send the given data to it.
109      *
110      * <strong>Note:</strong> This method relies on the {@code connectx(2)} system call, which is MacOS specific.
111      *
112      * @param source      the source address we are connecting from.
113      * @param destination the destination address we are connecting to.
114      * @param data        the data to copy to the kernel-side socket buffer.
115      * @param tcpFastOpen if {@code true}, set the flags needed to enable TCP FastOpen connecting.
116      * @return The number of bytes copied to the kernel-side socket buffer, or the number of bytes sent to the
117      * destination. This number is <em>negative</em> if connecting is left in an in-progress state,
118      * or <em>positive</em> if the connection was immediately established.
119      * @throws IOException if an IO error occurs, if the {@code data} is too big to send in one go,
120      * or if the system call is not supported on your platform.
121      */
122     int connectx(InetSocketAddress source, InetSocketAddress destination, IovArray data, boolean tcpFastOpen)
123             throws IOException {
124         requireNonNull(destination, "Destination InetSocketAddress cannot be null.");
125         int flags = tcpFastOpen ? CONNECT_TCP_FASTOPEN : 0;
126 
127         boolean sourceIPv6;
128         byte[] sourceAddress;
129         int sourceScopeId;
130         int sourcePort;
131         if (source == null) {
132             sourceIPv6 = false;
133             sourceAddress = null;
134             sourceScopeId = 0;
135             sourcePort = 0;
136         } else {
137             InetAddress sourceInetAddress = source.getAddress();
138             sourceIPv6 = useIpv6(this, sourceInetAddress);
139             if (sourceInetAddress instanceof Inet6Address) {
140                 sourceAddress = sourceInetAddress.getAddress();
141                 sourceScopeId = ((Inet6Address) sourceInetAddress).getScopeId();
142             } else {
143                 // convert to ipv4 mapped ipv6 address;
144                 sourceScopeId = 0;
145                 sourceAddress = ipv4MappedIpv6Address(sourceInetAddress.getAddress());
146             }
147             sourcePort = source.getPort();
148         }
149 
150         InetAddress destinationInetAddress = destination.getAddress();
151         boolean destinationIPv6 = useIpv6(this, destinationInetAddress);
152         byte[] destinationAddress;
153         int destinationScopeId;
154         if (destinationInetAddress instanceof Inet6Address) {
155             destinationAddress = destinationInetAddress.getAddress();
156             destinationScopeId = ((Inet6Address) destinationInetAddress).getScopeId();
157         } else {
158             // convert to ipv4 mapped ipv6 address;
159             destinationScopeId = 0;
160             destinationAddress = ipv4MappedIpv6Address(destinationInetAddress.getAddress());
161         }
162         int destinationPort = destination.getPort();
163 
164         long iovAddress;
165         int iovCount;
166         int iovDataLength;
167         if (data == null || data.count() == 0) {
168             iovAddress = 0;
169             iovCount = 0;
170             iovDataLength = 0;
171         } else {
172             iovAddress = data.memoryAddress(0);
173             iovCount = data.count();
174             long size = data.size();
175             if (size > Integer.MAX_VALUE) {
176                 throw new IOException("IovArray.size() too big: " + size + " bytes.");
177             }
178             iovDataLength = (int) size;
179         }
180 
181         int result = connectx(intValue(),
182                 UNSPECIFIED_SOURCE_INTERFACE, sourceIPv6, sourceAddress, sourceScopeId, sourcePort,
183                 destinationIPv6, destinationAddress, destinationScopeId, destinationPort,
184                 flags, iovAddress, iovCount, iovDataLength);
185         if (result == ERRNO_EINPROGRESS_NEGATIVE) {
186             // This is normal for non-blocking sockets.
187             // We'll know the connection has been established when the socket is selectable for writing.
188             // Tell the channel the data was written, so the outbound buffer can update its position.
189             return -iovDataLength;
190         }
191         if (result < 0) {
192             return ioResult("connectx", result);
193         }
194         return result;
195     }
196 
197     public static BsdSocket newDatagramSocket(ProtocolFamily family) {
198         if (family == null) {
199             return newSocketDgram();
200         }
201         SocketProtocolFamily protocolFamily = SocketProtocolFamily.of(family);
202         switch (protocolFamily) {
203             case UNIX:
204                 return newSocketDomainDgram();
205             case INET6:
206             case INET:
207                 return newSocketDgram(protocolFamily);
208             default:
209                 throw new UnsupportedOperationException();
210         }
211     }
212 
213     public static BsdSocket newSocket(ProtocolFamily family) {
214         if (family == null) {
215             return newSocketStream();
216         }
217         SocketProtocolFamily protocolFamily = SocketProtocolFamily.of(family);
218         switch (protocolFamily) {
219             case UNIX:
220                 return newSocketDomain();
221             case INET6:
222             case INET:
223                 return newSocketStream(protocolFamily);
224             default:
225                 throw new UnsupportedOperationException();
226         }
227     }
228 
229     public static BsdSocket newSocketStream() {
230         return new BsdSocket(newSocketStream0(), isIPv6Preferred() ?
231                 SocketProtocolFamily.INET6 : SocketProtocolFamily.INET);
232     }
233 
234     private static BsdSocket newSocketStream(SocketProtocolFamily protocol) {
235         return new BsdSocket(newSocketStream0(protocol), protocol);
236     }
237 
238     public static BsdSocket newSocketDgram() {
239         return new BsdSocket(newSocketDgram0(), isIPv6Preferred() ?
240                 SocketProtocolFamily.INET6 : SocketProtocolFamily.INET);
241     }
242 
243     public static BsdSocket newSocketDgram(ProtocolFamily protocol) {
244         if (protocol == null) {
245             protocol = isIPv6Preferred() ?
246                     SocketProtocolFamily.INET6 : SocketProtocolFamily.INET;
247         }
248         return new BsdSocket(newSocketDgram0(protocol), SocketProtocolFamily.of(protocol));
249     }
250 
251     public static BsdSocket newSocketDomain() {
252         return new BsdSocket(newSocketDomain0(), SocketProtocolFamily.UNIX);
253     }
254 
255     public static BsdSocket newSocketDomainDgram() {
256         return new BsdSocket(newSocketDomainDgram0(), SocketProtocolFamily.UNIX);
257     }
258 
259     private static native long sendFile(int socketFd, DefaultFileRegion src, long baseOffset,
260                                         long offset, long length) throws IOException;
261 
262     /**
263      * @return If successful, zero or positive number of bytes transfered, otherwise negative errno.
264      */
265     private static native int connectx(
266             int socketFd,
267             // sa_endpoints_t *endpoints:
268             int sourceInterface,
269             boolean sourceIPv6, byte[] sourceAddress, int sourceScopeId, int sourcePort,
270             boolean destinationIPv6, byte[] destinationAddress, int destinationScopeId, int destinationPort,
271             // sae_associd_t associd is reserved
272             int flags,
273             long iovAddress, int iovCount, int iovDataLength
274             // sae_connid_t *connid is reserved
275     );
276 
277     private static native String[] getAcceptFilter(int fd) throws IOException;
278 
279     private static native int getTcpNoPush(int fd) throws IOException;
280 
281     private static native int getSndLowAt(int fd) throws IOException;
282 
283     private static native int isTcpFastOpen(int fd) throws IOException;
284 
285     private static native PeerCredentials getPeerCredentials(int fd) throws IOException;
286 
287     private static native void setAcceptFilter(int fd, String filterName, String filterArgs) throws IOException;
288 
289     private static native void setTcpNoPush(int fd, int tcpNoPush) throws IOException;
290 
291     private static native void setSndLowAt(int fd, int lowAt) throws IOException;
292 
293     private static native void setTcpFastOpen(int fd, int enableFastOpen) throws IOException;
294 }