View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2024 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.newsclub.net.unix;
19  
20  import static org.newsclub.net.unix.NativeUnixSocket.SHUT_RD;
21  import static org.newsclub.net.unix.NativeUnixSocket.SHUT_RD_WR;
22  import static org.newsclub.net.unix.NativeUnixSocket.SHUT_WR;
23  
24  import java.io.EOFException;
25  import java.io.FileDescriptor;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.InterruptedIOException;
29  import java.io.OutputStream;
30  import java.net.InetAddress;
31  import java.net.SocketAddress;
32  import java.net.SocketException;
33  import java.net.SocketImpl;
34  import java.net.SocketOption;
35  import java.net.SocketOptions;
36  import java.net.SocketTimeoutException;
37  import java.nio.ByteBuffer;
38  import java.util.Set;
39  import java.util.concurrent.atomic.AtomicBoolean;
40  import java.util.concurrent.atomic.AtomicInteger;
41  
42  import org.eclipse.jdt.annotation.NonNull;
43  import org.eclipse.jdt.annotation.Nullable;
44  
45  /**
46   * junixsocket-based {@link SocketImpl}.
47   *
48   * @author Christian Kohlschütter
49   * @param <A> The supported address type.
50   */
51  @SuppressWarnings({
52      "PMD.CyclomaticComplexity", "PMD.CouplingBetweenObjects",
53      "UnsafeFinalization" /* errorprone */})
54  public abstract class AFSocketImpl<A extends AFSocketAddress> extends SocketImplShim {
55    private static final int SHUTDOWN_RD_WR = (1 << SHUT_RD) | (1 << SHUT_WR);
56  
57    private final AFSocketStreamCore core;
58    final AncillaryDataSupport ancillaryDataSupport = new AncillaryDataSupport();
59  
60    private final AtomicBoolean bound = new AtomicBoolean(false);
61    private Boolean createType = null;
62    private final AtomicBoolean connected = new AtomicBoolean(false);
63  
64    private volatile boolean closedInputStream = false;
65    private volatile boolean closedOutputStream = false;
66  
67    private final AFInputStream in;
68    private final AFOutputStream out;
69  
70    private boolean reuseAddr = true;
71  
72    private final AtomicInteger socketTimeout = new AtomicInteger(0);
73    private final AFAddressFamily<A> addressFamily;
74  
75    private int shutdownState = 0;
76  
77    private AFSocketImplExtensions<A> implExtensions = null;
78  
79    /**
80     * When the {@link AFSocketImpl} becomes unreachable (but not yet closed), we must ensure that the
81     * underlying socket and all related file descriptors are closed.
82     *
83     * @author Christian Kohlschütter
84     */
85    static final class AFSocketStreamCore extends AFSocketCore {
86      AFSocketStreamCore(AFSocketImpl<?> observed, FileDescriptor fd,
87          AncillaryDataSupport ancillaryDataSupport, AFAddressFamily<?> af) {
88        super(observed, fd, ancillaryDataSupport, af, false);
89      }
90  
91      void createSocket(FileDescriptor fdTarget, AFSocketType type) throws IOException {
92        NativeUnixSocket.createSocket(fdTarget, addressFamily().getDomain(), type.getId());
93      }
94  
95      /**
96       * Unblock other threads that are currently waiting on accept, simply by connecting to the
97       * socket.
98       */
99      @Override
100     protected void unblockAccepts() {
101       if (socketAddress == null || socketAddress.getBytes() == null || inode.get() < 0) {
102         return;
103       }
104 
105       while (hasPendingAccepts()) {
106         try {
107           FileDescriptor tmpFd = new FileDescriptor();
108 
109           try {
110             createSocket(tmpFd, AFSocketType.SOCK_STREAM);
111             ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer();
112             NativeUnixSocket.connect(ab, ab.limit(), tmpFd, inode.get());
113           } catch (IOException e) {
114             // there's nothing more we can do to unlock these accepts
115             // (e.g., SocketException: No such file or directory)
116             return;
117           }
118           if (isShutdownOnClose()) {
119             try {
120               NativeUnixSocket.shutdown(tmpFd, SHUT_RD_WR);
121             } catch (Exception e) {
122               // ignore
123             }
124           }
125           try {
126             NativeUnixSocket.close(tmpFd);
127           } catch (Exception e) {
128             // ignore
129           }
130         } catch (RuntimeException e) {
131           // ignore
132         }
133 
134         // sleep a little to give the cleaners some CPU time to actually clean up
135         try {
136           Thread.sleep(5);
137         } catch (InterruptedException e) {
138           // ignore
139         }
140       }
141     }
142   }
143 
144   /**
145    * Creates a new {@link AFSocketImpl} instance.
146    *
147    * @param addressFamily The address family.
148    * @param fdObj The socket's {@link FileDescriptor}.
149    */
150   protected AFSocketImpl(AFAddressFamily<@NonNull A> addressFamily, FileDescriptor fdObj) {
151     super();
152     this.addressFamily = addressFamily;
153     this.address = InetAddress.getLoopbackAddress();
154     this.core = new AFSocketStreamCore(this, fdObj, ancillaryDataSupport, addressFamily);
155     this.fd = core.fd;
156     this.in = newInputStream();
157     this.out = newOutputStream();
158   }
159 
160   /**
161    * Creates a new {@link InputStream} for this socket.
162    *
163    * @return The new stream.
164    */
165   protected final AFInputStream newInputStream() {
166     return new AFInputStreamImpl();
167   }
168 
169   /**
170    * Creates a new {@link OutputStream} for this socket.
171    *
172    * @return The new stream.
173    */
174   protected final AFOutputStream newOutputStream() {
175     return new AFOutputStreamImpl();
176   }
177 
178   final FileDescriptor getFD() {
179     return fd;
180   }
181 
182   // CPD-OFF
183   final boolean isConnected() {
184     if (connected.get()) {
185       return true;
186     }
187     if (isClosed()) {
188       return false;
189     }
190     if (core.isConnected(false)) {
191       connected.set(true);
192       return true;
193     }
194     return false;
195   }
196 
197   final boolean isBound() {
198     if (bound.get()) {
199       return true;
200     }
201     if (isClosed()) {
202       return false;
203     }
204     if (core.isConnected(true)) {
205       bound.set(true);
206       return true;
207     }
208     return false;
209   }
210 
211   final AFSocketCore getCore() {
212     return core;
213   }
214 
215   boolean isClosed() {
216     return core.isClosed();
217   }
218   // CPD-ON
219 
220   @Override
221   protected final void accept(SocketImpl socket) throws IOException {
222     accept0(socket);
223   }
224 
225   @SuppressWarnings("Finally" /* errorprone */)
226   final boolean accept0(SocketImpl socket) throws IOException {
227     FileDescriptor fdesc = core.validFdOrException();
228     if (isClosed()) {
229       throw new SocketException("Socket is closed");
230     } else if (!isBound()) {
231       throw new SocketException("Socket is not bound");
232     }
233 
234     AFSocketAddress socketAddress = core.socketAddress;
235     AFSocketAddress boundSocketAddress = getLocalSocketAddress();
236     if (boundSocketAddress != null) {
237       // Always resolve bound address from wildcard address, etc.
238       core.socketAddress = socketAddress = boundSocketAddress;
239     }
240 
241     if (socketAddress == null) {
242       throw new SocketException("Socket is not bound");
243     }
244 
245     @SuppressWarnings("unchecked")
246     final AFSocketImpl<A> si = (AFSocketImpl<A>) socket;
247     core.incPendingAccepts();
248     try {
249       ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer();
250 
251       SocketException caught = null;
252       try {
253         if (!NativeUnixSocket.accept(ab, ab.limit(), fdesc, si.fd, core.inode.get(), socketTimeout
254             .get())) {
255           return false;
256         }
257       } catch (SocketException e) { // NOPMD.ExceptionAsFlowControl
258         caught = e;
259       } finally { // NOPMD.DoNotThrowExceptionInFinally
260         if (!isBound() || isClosed()) {
261           if (getCore().isShutdownOnClose()) {
262             try {
263               NativeUnixSocket.shutdown(si.fd, SHUT_RD_WR);
264             } catch (Exception e) {
265               // ignore
266             }
267           }
268           try {
269             NativeUnixSocket.close(si.fd);
270           } catch (Exception e) {
271             // ignore
272           }
273           if (caught != null) {
274             throw caught;
275           } else {
276             throw new SocketClosedException("Socket is closed");
277           }
278         } else if (caught != null) {
279           throw caught;
280         }
281       }
282     } finally {
283       core.decPendingAccepts();
284     }
285     si.setSocketAddress(socketAddress);
286     si.connected.set(true);
287 
288     return true;
289   }
290 
291   final void setSocketAddress(AFSocketAddress socketAddress) {
292     if (socketAddress == null) {
293       this.core.socketAddress = null;
294       this.address = null;
295       this.localport = -1;
296     } else {
297       this.core.socketAddress = socketAddress;
298       this.address = socketAddress.getAddress();
299       if (this.localport <= 0) {
300         this.localport = socketAddress.getPort();
301       }
302     }
303   }
304 
305   @Override
306   protected final int available() throws IOException {
307     FileDescriptor fdesc = core.validFdOrException();
308     return NativeUnixSocket.available(fdesc, core.getThreadLocalDirectByteBuffer(0));
309   }
310 
311   final void bind(SocketAddress addr, int options) throws IOException {
312     if (addr == null) {
313       throw new IllegalArgumentException("Cannot bind to null address");
314     }
315     if (!(addr instanceof AFSocketAddress)) {
316       throw new SocketException("Cannot bind to this type of address: " + addr.getClass());
317     }
318 
319     bound.set(true);
320 
321     if (addr == AFSocketAddress.INTERNAL_DUMMY_BIND) { // NOPMD
322       core.inode.set(0);
323       return;
324     }
325 
326     AFSocketAddress socketAddress = (AFSocketAddress) addr;
327 
328     this.setSocketAddress(socketAddress);
329     ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer();
330     core.inode.set(NativeUnixSocket.bind(ab, ab.limit(), fd, options));
331     core.validFdOrException();
332   }
333 
334   @Override
335   @SuppressWarnings("hiding")
336   protected final void bind(InetAddress host, int port) throws IOException {
337     // ignored
338   }
339 
340   private void checkClose() throws IOException {
341     if (closedInputStream && closedOutputStream) {
342       close();
343     }
344   }
345 
346   @Override
347   protected final void close() throws IOException {
348     shutdown();
349     core.runCleaner();
350   }
351 
352   @Override
353   @SuppressWarnings("hiding")
354   protected final void connect(String host, int port) throws IOException {
355     throw new SocketException("Cannot bind to this type of address: " + InetAddress.class);
356   }
357 
358   @Override
359   @SuppressWarnings("hiding")
360   protected final void connect(InetAddress address, int port) throws IOException {
361     throw new SocketException("Cannot bind to this type of address: " + InetAddress.class);
362   }
363 
364   @Override
365   protected final void connect(SocketAddress addr, int connectTimeout) throws IOException {
366     connect0(addr, connectTimeout);
367   }
368 
369   final boolean connect0(SocketAddress addr, int connectTimeout) throws IOException {
370     if (addr == AFSocketAddress.INTERNAL_DUMMY_CONNECT) { // NOPMD
371       this.connected.set(true);
372       return true;
373     } else if (addr == AFSocketAddress.INTERNAL_DUMMY_DONT_CONNECT) { // NOPMD)
374       return false;
375     }
376 
377     if (!(addr instanceof AFSocketAddress)) {
378       throw new SocketException("Cannot connect to this type of address: " + addr.getClass());
379     }
380 
381     AFSocketAddress socketAddress = (AFSocketAddress) addr;
382     ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer();
383     boolean success = false;
384     boolean ignoreSpuriousTimeout = true;
385     do {
386       try {
387         success = NativeUnixSocket.connect(ab, ab.limit(), fd, -1);
388         break;
389       } catch (SocketTimeoutException e) {
390         // Ignore spurious timeout once when SO_TIMEOUT==0
391         // seen on older Linux kernels with AF_VSOCK running in qemu
392         if (ignoreSpuriousTimeout) {
393           Object o = getOption(SocketOptions.SO_TIMEOUT);
394           if (o instanceof Integer) {
395             if (((Integer) o) == 0) {
396               ignoreSpuriousTimeout = false;
397               continue;
398             }
399           } else if (o == null) {
400             ignoreSpuriousTimeout = false;
401             continue;
402           }
403         }
404         throw e;
405       }
406     } while (!Thread.interrupted());
407     if (success) {
408       setSocketAddress(socketAddress);
409       this.connected.set(true);
410     }
411     core.validFdOrException();
412     return success;
413   }
414 
415   @Override
416   protected final void create(boolean stream) throws IOException {
417     if (isClosed()) {
418       throw new SocketException("Already closed");
419     }
420     if (fd.valid()) {
421       if (createType != null) {
422         if (createType.booleanValue() != stream) { // NOPMD.UnnecessaryBoxing
423           throw new IllegalStateException("Already created with different mode");
424         }
425       } else {
426         createType = stream;
427       }
428       return;
429     }
430     createType = stream;
431     createSocket(fd, stream ? AFSocketType.SOCK_STREAM : AFSocketType.SOCK_DGRAM);
432   }
433 
434   @Override
435   protected final AFInputStream getInputStream() throws IOException {
436     if (!isConnected() && !isBound()) {
437       throw new SocketClosedException("Not connected/not bound");
438     }
439     core.validFdOrException();
440     return in;
441   }
442 
443   @Override
444   protected final AFOutputStream getOutputStream() throws IOException {
445     if (!isClosed() && !isBound()) {
446       throw new SocketClosedException("Not connected/not bound");
447     }
448     core.validFdOrException();
449     return out;
450   }
451 
452   @Override
453   protected final void listen(int backlog) throws IOException {
454     FileDescriptor fdesc = core.validFdOrException();
455     if (backlog <= 0) {
456       backlog = 50;
457     }
458     NativeUnixSocket.listen(fdesc, backlog);
459   }
460 
461   @Override
462   protected final boolean supportsUrgentData() {
463     return false;
464   }
465 
466   @Override
467   protected final void sendUrgentData(int data) throws IOException {
468     throw new UnsupportedOperationException();
469   }
470 
471   private final class AFInputStreamImpl extends AFInputStream {
472     private volatile boolean streamClosed = false;
473     private final AtomicBoolean eofReached = new AtomicBoolean(false);
474 
475     private final int opt = (core.isBlocking() ? 0 : NativeUnixSocket.OPT_NON_BLOCKING);
476 
477     @Override
478     public int read(byte[] buf, int off, int len) throws IOException {
479       if (streamClosed) {
480         throw new SocketClosedException("This InputStream has already been closed.");
481       }
482       if (eofReached.get()) {
483         return -1;
484       }
485 
486       FileDescriptor fdesc = core.validFdOrException();
487       if (len == 0) {
488         return 0;
489       } else if (off < 0 || len < 0 || (len > buf.length - off)) {
490         throw new IndexOutOfBoundsException();
491       }
492 
493       try {
494         return NativeUnixSocket.read(fdesc, buf, off, len, opt, ancillaryDataSupport, socketTimeout
495             .get());
496       } catch (EOFException e) {
497         eofReached.set(true);
498         throw e;
499       }
500     }
501 
502     @Override
503     public int read() throws IOException {
504       FileDescriptor fdesc = core.validFdOrException();
505 
506       if (eofReached.get()) {
507         return -1;
508       }
509 
510       int byteRead = NativeUnixSocket.read(fdesc, null, 0, 1, opt, ancillaryDataSupport,
511           socketTimeout.get());
512       if (byteRead < 0) {
513         eofReached.set(true);
514         return -1;
515       } else {
516         return byteRead;
517       }
518     }
519 
520     @Override
521     public synchronized void close() throws IOException {
522       streamClosed = true;
523       FileDescriptor fdesc = core.validFd();
524       if (fdesc != null && getCore().isShutdownOnClose()) {
525         NativeUnixSocket.shutdown(fdesc, SHUT_RD);
526       }
527 
528       closedInputStream = true;
529       checkClose();
530     }
531 
532     @Override
533     public int available() throws IOException {
534       if (streamClosed) {
535         throw new SocketClosedException("This InputStream has already been closed.");
536       }
537 
538       return AFSocketImpl.this.available();
539     }
540 
541     @Override
542     public FileDescriptor getFileDescriptor() throws IOException {
543       return getFD();
544     }
545   }
546 
547   private static boolean checkWriteInterruptedException(int bytesTransferred)
548       throws InterruptedIOException {
549     if (Thread.interrupted()) {
550       InterruptedIOException ex = new InterruptedIOException("Thread interrupted during write");
551       ex.bytesTransferred = bytesTransferred;
552       Thread.currentThread().interrupt();
553       throw ex;
554     }
555     return true;
556   }
557 
558   private final class AFOutputStreamImpl extends AFOutputStream {
559     private volatile boolean streamClosed = false;
560 
561     private final int opt = (core.isBlocking() ? 0 : NativeUnixSocket.OPT_NON_BLOCKING);
562 
563     @Override
564     public void write(int oneByte) throws IOException {
565       FileDescriptor fdesc = core.validFdOrException();
566 
567       int written;
568       do {
569         written = NativeUnixSocket.write(fdesc, null, oneByte, 1, opt, ancillaryDataSupport);
570         if (written != 0) {
571           break;
572         }
573       } while (checkWriteInterruptedException(0));
574     }
575 
576     @Override
577     public void write(byte[] buf, int off, int len) throws IOException {
578       if (streamClosed) {
579         throw new SocketException("This OutputStream has already been closed.");
580       }
581       if (len < 0 || off < 0 || len > buf.length - off) {
582         throw new IndexOutOfBoundsException();
583       }
584       FileDescriptor fdesc = core.validFdOrException();
585 
586       // NOTE: writing messages with len == 0 should be permissible (unless ignored in native code)
587       // For certain sockets, empty messages can be used to probe if the remote connection is alive
588       if (len == 0 && !AFSocket.supports(AFSocketCapability.CAPABILITY_ZERO_LENGTH_SEND)) {
589         return;
590       }
591 
592       int writtenTotal = 0;
593 
594       do {
595         final int written = NativeUnixSocket.write(fdesc, buf, off, len, opt, ancillaryDataSupport);
596         if (written < 0) {
597           if (len == 0) {
598             // This exception is only useful to detect OS-level bugs that we need to work-around
599             // in native code.
600             // throw new IOException("Error while writing zero-length byte array; try -D"
601             // + AFSocket.PROP_LIBRARY_DISABLE_CAPABILITY_PREFIX
602             // + AFSocketCapability.CAPABILITY_ZERO_LENGTH_SEND.name() + "=true");
603 
604             // ignore
605             return;
606           } else {
607             throw new IOException("Unspecific error while writing");
608           }
609         }
610 
611         len -= written;
612         off += written;
613         writtenTotal += written;
614       } while (len > 0 && checkWriteInterruptedException(writtenTotal));
615     }
616 
617     @Override
618     public synchronized void close() throws IOException {
619       if (streamClosed) {
620         return;
621       }
622       streamClosed = true;
623       FileDescriptor fdesc = core.validFd();
624       if (fdesc != null && getCore().isShutdownOnClose()) {
625         NativeUnixSocket.shutdown(fdesc, SHUT_WR);
626       }
627       closedOutputStream = true;
628       checkClose();
629     }
630 
631     @Override
632     public FileDescriptor getFileDescriptor() throws IOException {
633       return getFD();
634     }
635   }
636 
637   @Override
638   public final String toString() {
639     return super.toString() + "[fd=" + fd + "; addr=" + this.core.socketAddress + "; connected="
640         + connected + "; bound=" + bound + "]";
641   }
642 
643   private static int expectInteger(Object value) throws SocketException {
644     if (value == null) {
645       throw (SocketException) new SocketException("Value must not be null").initCause(
646           new NullPointerException());
647     }
648     try {
649       return (Integer) value;
650     } catch (final ClassCastException e) {
651       throw (SocketException) new SocketException("Unsupported value: " + value).initCause(e);
652     }
653   }
654 
655   private static int expectBoolean(Object value) throws SocketException {
656     if (value == null) {
657       throw (SocketException) new SocketException("Value must not be null").initCause(
658           new NullPointerException());
659     }
660     try {
661       return ((Boolean) value) ? 1 : 0;
662     } catch (final ClassCastException e) {
663       throw (SocketException) new SocketException("Unsupported value: " + value).initCause(e);
664     }
665   }
666 
667   @Override
668   public Object getOption(int optID) throws SocketException {
669     return getOption0(optID);
670   }
671 
672   private Object getOption0(int optID) throws SocketException {
673     if (isClosed()) {
674       throw new SocketException("Socket is closed");
675     }
676     if (optID == SocketOptions.SO_REUSEADDR) {
677       return reuseAddr;
678     }
679 
680     FileDescriptor fdesc = core.validFdOrException();
681     return getOptionDefault(fdesc, optID, socketTimeout, addressFamily);
682   }
683 
684   static final Object getOptionDefault(FileDescriptor fdesc, int optID, AtomicInteger acceptTimeout,
685       AFAddressFamily<?> af) throws SocketException {
686     try {
687       switch (optID) {
688         case SocketOptions.SO_KEEPALIVE:
689           try {
690             return (NativeUnixSocket.getSocketOptionInt(fdesc, optID) != 0);
691           } catch (SocketException e) {
692             // ignore
693             return false;
694           }
695         case SocketOptions.TCP_NODELAY:
696           return (NativeUnixSocket.getSocketOptionInt(fdesc, optID) != 0);
697         case SocketOptions.SO_TIMEOUT:
698           int v = Math.max(NativeUnixSocket.getSocketOptionInt(fdesc, 0x1005), NativeUnixSocket
699               .getSocketOptionInt(fdesc, 0x1006));
700           if (v == -1) {
701             // special value, meaning: do not override infinite timeout from native code
702             return 0;
703           }
704           return Math.max((acceptTimeout == null ? 0 : acceptTimeout.get()), v);
705         case SocketOptions.SO_LINGER:
706         case SocketOptions.SO_RCVBUF:
707         case SocketOptions.SO_SNDBUF:
708           return NativeUnixSocket.getSocketOptionInt(fdesc, optID);
709         case SocketOptions.IP_TOS:
710           return 0;
711         case SocketOptions.SO_BINDADDR:
712           return AFSocketAddress.getInetAddress(fdesc, false, af);
713         case SocketOptions.SO_REUSEADDR:
714           return false;
715         default:
716           throw new SocketException("Unsupported option: " + optID);
717       }
718     } catch (final SocketException e) {
719       throw e;
720     } catch (final Exception e) {
721       throw (SocketException) new SocketException("Could not get option").initCause(e);
722     }
723   }
724 
725   @Override
726   public void setOption(int optID, Object value) throws SocketException {
727     setOption0(optID, value);
728   }
729 
730   private void setOption0(int optID, Object value) throws SocketException {
731     if (isClosed()) {
732       throw new SocketException("Socket is closed");
733     }
734     if (optID == SocketOptions.SO_REUSEADDR) {
735       reuseAddr = (expectBoolean(value) != 0);
736       return;
737     }
738 
739     FileDescriptor fdesc = core.validFdOrException();
740     setOptionDefault(fdesc, optID, value, socketTimeout);
741   }
742 
743   /**
744    * Like {@link #getOption(int)}, but ignores exceptions for certain option IDs.
745    *
746    * @param optID The option ID.
747    * @return The value.
748    * @throws SocketException on error.
749    */
750   protected final Object getOptionLenient(int optID) throws SocketException {
751     try {
752       return getOption0(optID);
753     } catch (SocketException e) {
754       switch (optID) {
755         case SocketOptions.TCP_NODELAY:
756         case SocketOptions.SO_KEEPALIVE:
757           return false;
758         default:
759           throw e;
760       }
761     }
762   }
763 
764   /**
765    * Like {@link #setOption(int, Object)}, but ignores exceptions for certain option IDs.
766    *
767    * @param optID The option ID.
768    * @param value The value.
769    * @throws SocketException on error.
770    */
771   protected final void setOptionLenient(int optID, Object value) throws SocketException {
772     try {
773       setOption0(optID, value);
774     } catch (SocketException e) {
775       switch (optID) {
776         case SocketOptions.TCP_NODELAY:
777           return;
778         default:
779           throw e;
780       }
781     }
782   }
783 
784   static final void setOptionDefault(FileDescriptor fdesc, int optID, Object value,
785       AtomicInteger acceptTimeout) throws SocketException {
786     try {
787       switch (optID) {
788         case SocketOptions.SO_LINGER:
789 
790           if (value instanceof Boolean) {
791             final boolean b = (Boolean) value;
792             if (b) {
793               throw new SocketException("Only accepting Boolean.FALSE here");
794             }
795             NativeUnixSocket.setSocketOptionInt(fdesc, optID, -1);
796             return;
797           }
798           NativeUnixSocket.setSocketOptionInt(fdesc, optID, expectInteger(value));
799           return;
800         case SocketOptions.SO_TIMEOUT: {
801           int timeout = expectInteger(value);
802           try {
803             NativeUnixSocket.setSocketOptionInt(fdesc, 0x1005, timeout);
804           } catch (InvalidArgumentSocketException e) {
805             // Perhaps the socket is shut down?
806           }
807           try {
808             NativeUnixSocket.setSocketOptionInt(fdesc, 0x1006, timeout);
809           } catch (InvalidArgumentSocketException e) {
810             // Perhaps the socket is shut down?
811           }
812           if (acceptTimeout != null) {
813             acceptTimeout.set(timeout);
814           }
815           return;
816         }
817         case SocketOptions.SO_RCVBUF:
818         case SocketOptions.SO_SNDBUF:
819           NativeUnixSocket.setSocketOptionInt(fdesc, optID, expectInteger(value));
820           return;
821         case SocketOptions.SO_KEEPALIVE:
822           try {
823             NativeUnixSocket.setSocketOptionInt(fdesc, optID, expectBoolean(value));
824           } catch (SocketException e) {
825             // ignore
826           }
827           return;
828         case SocketOptions.TCP_NODELAY:
829           NativeUnixSocket.setSocketOptionInt(fdesc, optID, expectBoolean(value));
830           return;
831         case SocketOptions.IP_TOS:
832           // ignore
833           return;
834         case SocketOptions.SO_REUSEADDR:
835           // ignore
836           return;
837         default:
838           throw new SocketException("Unsupported option: " + optID);
839       }
840     } catch (final SocketException e) {
841       throw e;
842     } catch (final Exception e) {
843       throw (SocketException) new SocketException("Error while setting option").initCause(e);
844     }
845   }
846 
847   /**
848    * Shuts down both input and output at once. Equivalent to calling {@link #shutdownInput()} and
849    * {@link #shutdownOutput()}.
850    *
851    * @throws IOException on error.
852    */
853   protected final void shutdown() throws IOException {
854     FileDescriptor fdesc = core.validFd();
855     if (fdesc != null) {
856       NativeUnixSocket.shutdown(fdesc, SHUT_RD_WR);
857       shutdownState = 0;
858     }
859   }
860 
861   @Override
862   protected final void shutdownInput() throws IOException {
863     FileDescriptor fdesc = core.validFd();
864     if (fdesc != null) {
865       NativeUnixSocket.shutdown(fdesc, SHUT_RD);
866       shutdownState |= 1 << (SHUT_RD);
867       if (shutdownState == SHUTDOWN_RD_WR) {
868         NativeUnixSocket.shutdown(fdesc, SHUT_RD_WR);
869         shutdownState = 0;
870       }
871     }
872   }
873 
874   @Override
875   protected final void shutdownOutput() throws IOException {
876     FileDescriptor fdesc = core.validFd();
877     if (fdesc != null) {
878       NativeUnixSocket.shutdown(fdesc, SHUT_WR);
879       shutdownState |= 1 << (SHUT_RD_WR);
880       if (shutdownState == SHUTDOWN_RD_WR) {
881         NativeUnixSocket.shutdown(fdesc, SHUT_RD_WR);
882         shutdownState = 0;
883       }
884     }
885   }
886 
887   final int getAncillaryReceiveBufferSize() {
888     return ancillaryDataSupport.getAncillaryReceiveBufferSize();
889   }
890 
891   final void setAncillaryReceiveBufferSize(int size) {
892     ancillaryDataSupport.setAncillaryReceiveBufferSize(size);
893   }
894 
895   final void ensureAncillaryReceiveBufferSize(int minSize) {
896     ancillaryDataSupport.ensureAncillaryReceiveBufferSize(minSize);
897   }
898 
899   AncillaryDataSupport getAncillaryDataSupport() {
900     return ancillaryDataSupport;
901   }
902 
903   final SocketAddress receive(ByteBuffer dst) throws IOException {
904     return core.receive(dst);
905   }
906 
907   final int send(ByteBuffer src, SocketAddress target) throws IOException {
908     return core.write(src, target, 0);
909   }
910 
911   final int read(ByteBuffer dst, ByteBuffer socketAddressBuffer) throws IOException {
912     return core.read(dst, socketAddressBuffer, 0);
913   }
914 
915   final int write(ByteBuffer src) throws IOException {
916     return core.write(src);
917   }
918 
919   @Override
920   protected final FileDescriptor getFileDescriptor() {
921     return core.fd;
922   }
923 
924   final void updatePorts(int local, int remote) {
925     this.localport = local;
926     if (remote >= 0) {
927       this.port = remote;
928     }
929   }
930 
931   final @Nullable A getLocalSocketAddress() {
932     return AFSocketAddress.getSocketAddress(getFileDescriptor(), false, localport, addressFamily);
933   }
934 
935   final @Nullable A getRemoteSocketAddress() {
936     return AFSocketAddress.getSocketAddress(getFileDescriptor(), true, port, addressFamily);
937   }
938 
939   final int getLocalPort1() {
940     return localport;
941   }
942 
943   final int getRemotePort() {
944     return port;
945   }
946 
947   @Override
948   protected final InetAddress getInetAddress() {
949     @Nullable
950     A rsa = getRemoteSocketAddress();
951     if (rsa == null) {
952       return InetAddress.getLoopbackAddress();
953     } else {
954       return rsa.getInetAddress();
955     }
956   }
957 
958   final void createSocket(FileDescriptor fdTarget, AFSocketType type) throws IOException {
959     NativeUnixSocket.createSocket(fdTarget, addressFamily.getDomain(), type.getId());
960   }
961 
962   final AFAddressFamily<A> getAddressFamily() {
963     return addressFamily;
964   }
965 
966   @Override
967   protected <T> void setOption(SocketOption<T> name, T value) throws IOException {
968     if (name instanceof AFSocketOption<?>) {
969       getCore().setOption((AFSocketOption<T>) name, value);
970       return;
971     }
972     Integer optionId = SocketOptionsMapper.resolve(name);
973     if (optionId == null) {
974       super.setOption(name, value);
975     } else {
976       setOption(optionId, value);
977     }
978   }
979 
980   @SuppressWarnings("unchecked")
981   @Override
982   protected <T> T getOption(SocketOption<T> name) throws IOException {
983     if (name instanceof AFSocketOption<?>) {
984       return getCore().getOption((AFSocketOption<T>) name);
985     }
986     Integer optionId = SocketOptionsMapper.resolve(name);
987     if (optionId == null) {
988       return super.getOption(name);
989     } else {
990       return (T) getOption(optionId);
991     }
992   }
993 
994   @Override
995   protected Set<SocketOption<?>> supportedOptions() {
996     return SocketOptionsMapper.SUPPORTED_SOCKET_OPTIONS;
997   }
998 
999   /**
1000    * Returns the internal helper instance for address-specific extensions.
1001    *
1002    * @return The helper instance.
1003    * @throws UnsupportedOperationException if such extensions are not supported for this address
1004    *           type.
1005    */
1006   protected final synchronized AFSocketImplExtensions<A> getImplExtensions() {
1007     if (implExtensions == null) {
1008       implExtensions = addressFamily.initImplExtensions(ancillaryDataSupport);
1009     }
1010     return implExtensions;
1011   }
1012 }