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