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      SocketProtocolFamily family() {
59          return ipv6 ? SocketProtocolFamily.INET6 : SocketProtocolFamily.INET;
60      }
61  
62      void setAcceptFilter(AcceptFilter acceptFilter) throws IOException {
63          setAcceptFilter(intValue(), acceptFilter.filterName(), acceptFilter.filterArgs());
64      }
65  
66      void setTcpNoPush(boolean tcpNoPush) throws IOException {
67          setTcpNoPush(intValue(), tcpNoPush ? 1 : 0);
68      }
69  
70      void setSndLowAt(int lowAt) throws IOException {
71          setSndLowAt(intValue(), lowAt);
72      }
73  
74      public void setTcpFastOpen(boolean enableTcpFastOpen) throws IOException {
75          setTcpFastOpen(intValue(), enableTcpFastOpen ? 1 : 0);
76      }
77  
78      boolean isTcpNoPush() throws IOException {
79          return getTcpNoPush(intValue()) != 0;
80      }
81  
82      int getSndLowAt() throws IOException {
83          return getSndLowAt(intValue());
84      }
85  
86      AcceptFilter getAcceptFilter() throws IOException {
87          String[] result = getAcceptFilter(intValue());
88          return result == null ? PLATFORM_UNSUPPORTED : new AcceptFilter(result[0], result[1]);
89      }
90  
91      public boolean isTcpFastOpen() throws IOException {
92          return isTcpFastOpen(intValue()) != 0;
93      }
94  
95      PeerCredentials getPeerCredentials() throws IOException {
96          return getPeerCredentials(intValue());
97      }
98  
99      long sendFile(DefaultFileRegion src, long baseOffset, long offset, long length) throws IOException {
100         // Open the file-region as it may be created via the lazy constructor. This is needed as we directly access
101         // the FileChannel field via JNI.
102         src.open();
103 
104         long res = sendFile(intValue(), src, baseOffset, offset, length);
105         if (res >= 0) {
106             return res;
107         }
108         return ioResult("sendfile", (int) res);
109     }
110 
111     /**
112      * Establish a connection to the given destination address, and send the given data to it.
113      *
114      * <strong>Note:</strong> This method relies on the {@code connectx(2)} system call, which is MacOS specific.
115      *
116      * @param source      the source address we are connecting from.
117      * @param destination the destination address we are connecting to.
118      * @param data        the data to copy to the kernel-side socket buffer.
119      * @param tcpFastOpen if {@code true}, set the flags needed to enable TCP FastOpen connecting.
120      * @return The number of bytes copied to the kernel-side socket buffer, or the number of bytes sent to the
121      * destination. This number is <em>negative</em> if connecting is left in an in-progress state,
122      * or <em>positive</em> if the connection was immediately established.
123      * @throws IOException if an IO error occurs, if the {@code data} is too big to send in one go,
124      * or if the system call is not supported on your platform.
125      */
126     int connectx(InetSocketAddress source, InetSocketAddress destination, IovArray data, boolean tcpFastOpen)
127             throws IOException {
128         checkNotNull(destination, "Destination InetSocketAddress cannot be null.");
129         int flags = tcpFastOpen ? CONNECT_TCP_FASTOPEN : 0;
130 
131         boolean sourceIPv6;
132         byte[] sourceAddress;
133         int sourceScopeId;
134         int sourcePort;
135         if (source == null) {
136             sourceIPv6 = false;
137             sourceAddress = null;
138             sourceScopeId = 0;
139             sourcePort = 0;
140         } else {
141             InetAddress sourceInetAddress = source.getAddress();
142             sourceIPv6 = useIpv6(this, sourceInetAddress);
143             if (sourceInetAddress instanceof Inet6Address) {
144                 sourceAddress = sourceInetAddress.getAddress();
145                 sourceScopeId = ((Inet6Address) sourceInetAddress).getScopeId();
146             } else {
147                 // convert to ipv4 mapped ipv6 address;
148                 sourceScopeId = 0;
149                 sourceAddress = ipv4MappedIpv6Address(sourceInetAddress.getAddress());
150             }
151             sourcePort = source.getPort();
152         }
153 
154         InetAddress destinationInetAddress = destination.getAddress();
155         boolean destinationIPv6 = useIpv6(this, destinationInetAddress);
156         byte[] destinationAddress;
157         int destinationScopeId;
158         if (destinationInetAddress instanceof Inet6Address) {
159             destinationAddress = destinationInetAddress.getAddress();
160             destinationScopeId = ((Inet6Address) destinationInetAddress).getScopeId();
161         } else {
162             // convert to ipv4 mapped ipv6 address;
163             destinationScopeId = 0;
164             destinationAddress = ipv4MappedIpv6Address(destinationInetAddress.getAddress());
165         }
166         int destinationPort = destination.getPort();
167 
168         long iovAddress;
169         int iovCount;
170         int iovDataLength;
171         if (data == null || data.count() == 0) {
172             iovAddress = 0;
173             iovCount = 0;
174             iovDataLength = 0;
175         } else {
176             iovAddress = data.memoryAddress(0);
177             iovCount = data.count();
178             long size = data.size();
179             if (size > Integer.MAX_VALUE) {
180                 throw new IOException("IovArray.size() too big: " + size + " bytes.");
181             }
182             iovDataLength = (int) size;
183         }
184 
185         int result = connectx(intValue(),
186                 UNSPECIFIED_SOURCE_INTERFACE, sourceIPv6, sourceAddress, sourceScopeId, sourcePort,
187                 destinationIPv6, destinationAddress, destinationScopeId, destinationPort,
188                 flags, iovAddress, iovCount, iovDataLength);
189         if (result == ERRNO_EINPROGRESS_NEGATIVE) {
190             // This is normal for non-blocking sockets.
191             // We'll know the connection has been established when the socket is selectable for writing.
192             // Tell the channel the data was written, so the outbound buffer can update its position.
193             return -iovDataLength;
194         }
195         if (result < 0) {
196             return ioResult("connectx", result);
197         }
198         return result;
199     }
200 
201     public static BsdSocket newSocketStream() {
202         return new BsdSocket(newSocketStream0());
203     }
204 
205     /**
206      * @deprecated use {@link #newSocketStream(SocketProtocolFamily)}
207      */
208     public static BsdSocket newSocketStream(InternetProtocolFamily protocol) {
209         return new BsdSocket(newSocketStream0(protocol));
210     }
211 
212     public static BsdSocket newSocketStream(SocketProtocolFamily protocol) {
213         return new BsdSocket(newSocketStream0(protocol));
214     }
215 
216     public static BsdSocket newSocketDgram() {
217         return new BsdSocket(newSocketDgram0());
218     }
219 
220     /**
221      * @deprecated use {@link #newSocketDgram(SocketProtocolFamily)}
222      */
223     public static BsdSocket newSocketDgram(InternetProtocolFamily protocol) {
224         return new BsdSocket(newSocketDgram0(protocol));
225     }
226 
227     public static BsdSocket newSocketDgram(SocketProtocolFamily protocol) {
228         return new BsdSocket(newSocketDgram0(protocol));
229     }
230 
231     public static BsdSocket newSocketDomain() {
232         return new BsdSocket(newSocketDomain0());
233     }
234 
235     public static BsdSocket newSocketDomainDgram() {
236         return new BsdSocket(newSocketDomainDgram0());
237     }
238 
239     private static native long sendFile(int socketFd, DefaultFileRegion src, long baseOffset,
240                                         long offset, long length) throws IOException;
241 
242     /**
243      * @return If successful, zero or positive number of bytes transferred, otherwise negative errno.
244      */
245     private static native int connectx(
246             int socketFd,
247             // sa_endpoints_t *endpoints:
248             int sourceInterface,
249             boolean sourceIPv6, byte[] sourceAddress, int sourceScopeId, int sourcePort,
250             boolean destinationIPv6, byte[] destinationAddress, int destinationScopeId, int destinationPort,
251             // sae_associd_t associd is reserved
252             int flags,
253             long iovAddress, int iovCount, int iovDataLength
254             // sae_connid_t *connid is reserved
255     );
256 
257     private static native String[] getAcceptFilter(int fd) throws IOException;
258 
259     private static native int getTcpNoPush(int fd) throws IOException;
260 
261     private static native int getSndLowAt(int fd) throws IOException;
262 
263     private static native int isTcpFastOpen(int fd) throws IOException;
264 
265     private static native PeerCredentials getPeerCredentials(int fd) throws IOException;
266 
267     private static native void setAcceptFilter(int fd, String filterName, String filterArgs) throws IOException;
268 
269     private static native void setTcpNoPush(int fd, int tcpNoPush) throws IOException;
270 
271     private static native void setSndLowAt(int fd, int lowAt) throws IOException;
272 
273     private static native void setTcpFastOpen(int fd, int enableFastOpen) throws IOException;
274 }