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.channel.kqueue;
17  
18  import io.netty.channel.DefaultFileRegion;
19  import io.netty.channel.socket.InternetProtocolFamily;
20  import io.netty.channel.socket.SocketProtocolFamily;
21  import io.netty.channel.unix.IovArray;
22  import io.netty.channel.unix.PeerCredentials;
23  import io.netty.channel.unix.Socket;
24  
25  import java.io.IOException;
26  import java.net.Inet6Address;
27  import java.net.InetAddress;
28  import java.net.InetSocketAddress;
29  
30  import static io.netty.channel.kqueue.AcceptFilter.PLATFORM_UNSUPPORTED;
31  import static io.netty.channel.kqueue.Native.CONNECT_TCP_FASTOPEN;
32  import static io.netty.channel.unix.Errors.ERRNO_EINPROGRESS_NEGATIVE;
33  import static io.netty.channel.unix.Errors.ioResult;
34  import static io.netty.channel.unix.NativeInetAddress.ipv4MappedIpv6Address;
35  import static io.netty.util.internal.ObjectUtil.checkNotNull;
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) {
55          super(fd);
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         checkNotNull(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 newSocketStream() {
198         return new BsdSocket(newSocketStream0());
199     }
200 
201     /**
202      * @deprecated use {@link #newSocketStream(SocketProtocolFamily)}
203      */
204     public static BsdSocket newSocketStream(InternetProtocolFamily protocol) {
205         return new BsdSocket(newSocketStream0(protocol));
206     }
207 
208     public static BsdSocket newSocketStream(SocketProtocolFamily protocol) {
209         return new BsdSocket(newSocketStream0(protocol));
210     }
211 
212     public static BsdSocket newSocketDgram() {
213         return new BsdSocket(newSocketDgram0());
214     }
215 
216     /**
217      * @deprecated use {@link #newSocketDgram(SocketProtocolFamily)}
218      */
219     public static BsdSocket newSocketDgram(InternetProtocolFamily protocol) {
220         return new BsdSocket(newSocketDgram0(protocol));
221     }
222 
223     public static BsdSocket newSocketDgram(SocketProtocolFamily protocol) {
224         return new BsdSocket(newSocketDgram0(protocol));
225     }
226 
227     public static BsdSocket newSocketDomain() {
228         return new BsdSocket(newSocketDomain0());
229     }
230 
231     public static BsdSocket newSocketDomainDgram() {
232         return new BsdSocket(newSocketDomainDgram0());
233     }
234 
235     private static native long sendFile(int socketFd, DefaultFileRegion src, long baseOffset,
236                                         long offset, long length) throws IOException;
237 
238     /**
239      * @return If successful, zero or positive number of bytes transfered, otherwise negative errno.
240      */
241     private static native int connectx(
242             int socketFd,
243             // sa_endpoints_t *endpoints:
244             int sourceInterface,
245             boolean sourceIPv6, byte[] sourceAddress, int sourceScopeId, int sourcePort,
246             boolean destinationIPv6, byte[] destinationAddress, int destinationScopeId, int destinationPort,
247             // sae_associd_t associd is reserved
248             int flags,
249             long iovAddress, int iovCount, int iovDataLength
250             // sae_connid_t *connid is reserved
251     );
252 
253     private static native String[] getAcceptFilter(int fd) throws IOException;
254 
255     private static native int getTcpNoPush(int fd) throws IOException;
256 
257     private static native int getSndLowAt(int fd) throws IOException;
258 
259     private static native int isTcpFastOpen(int fd) throws IOException;
260 
261     private static native PeerCredentials getPeerCredentials(int fd) throws IOException;
262 
263     private static native void setAcceptFilter(int fd, String filterName, String filterArgs) throws IOException;
264 
265     private static native void setTcpNoPush(int fd, int tcpNoPush) throws IOException;
266 
267     private static native void setSndLowAt(int fd, int lowAt) throws IOException;
268 
269     private static native void setTcpFastOpen(int fd, int enableFastOpen) throws IOException;
270 }