View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2026 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 java.io.Closeable;
21  import java.io.File;
22  import java.io.FileDescriptor;
23  import java.io.IOException;
24  import java.net.InetAddress;
25  import java.net.ServerSocket;
26  import java.net.SocketAddress;
27  import java.net.SocketException;
28  import java.net.SocketOption;
29  import java.net.SocketOptions;
30  import java.nio.channels.IllegalBlockingModeException;
31  import java.util.Arrays;
32  import java.util.Objects;
33  import java.util.Set;
34  import java.util.concurrent.atomic.AtomicBoolean;
35  
36  import org.eclipse.jdt.annotation.NonNull;
37  import org.eclipse.jdt.annotation.Nullable;
38  
39  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
40  
41  /**
42   * The server part of a junixsocket socket.
43   *
44   * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
45   * @author Christian Kohlschütter
46   */
47  @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.CouplingBetweenObjects"})
48  @SuppressFBWarnings("UNENCRYPTED_SERVER_SOCKET")
49  public abstract class AFServerSocket<A extends AFSocketAddress> extends ServerSocket implements
50      AFSomeSocketThing {
51    private final AFSocketImpl<A> implementation;
52    private @Nullable A boundEndpoint;
53    private final Closeables closeables = new Closeables();
54    private final AtomicBoolean created = new AtomicBoolean(false);
55    private final AtomicBoolean deleteOnClose = new AtomicBoolean(true);
56  
57    @SuppressWarnings("this-escape")
58    private final AFServerSocketChannel<A> channel = newChannel();
59    private @Nullable SocketAddressFilter bindFilter;
60  
61    private final AtomicBoolean closed = new AtomicBoolean(false);
62  
63    /**
64     * The constructor of the concrete subclass.
65     *
66     * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
67     */
68    @FunctionalInterface
69    public interface Constructor<A extends AFSocketAddress> {
70      /**
71       * Creates a new {@link AFServerSocket} instance.
72       *
73       * @param fd The file descriptor.
74       * @return The new instance.
75       * @throws IOException on error.
76       */
77      @NonNull
78      AFServerSocket<A> newInstance(FileDescriptor fd) throws IOException;
79    }
80  
81    /**
82     * Constructs a new, unconnected instance.
83     *
84     * @throws IOException if the operation fails.
85     */
86    @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
87    protected AFServerSocket() throws IOException {
88      this(null);
89    }
90  
91    /**
92     * Constructs a new instance, optionally associated with the given file descriptor.
93     *
94     * @param fdObj The file descriptor, or {@code null}.
95     * @throws IOException if the operation fails.
96     */
97    @SuppressWarnings({"this-escape", "PMD.ConstructorCallsOverridableMethod"})
98    @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
99    protected AFServerSocket(FileDescriptor fdObj) throws IOException {
100     super();
101 
102     this.implementation = newImpl(fdObj);
103     NativeUnixSocket.initServerImpl(this, implementation);
104 
105     getAFImpl().setOption(SocketOptions.SO_REUSEADDR, true);
106   }
107 
108   /**
109    * Creates a new AFServerSocketChannel for this socket.
110    *
111    * @return The new instance.
112    */
113   protected abstract AFServerSocketChannel<A> newChannel();
114 
115   /**
116    * Creates a new AFSocketImpl.
117    *
118    * @param fdObj The file descriptor.
119    * @return The new instance.
120    * @throws IOException on error.
121    */
122   protected abstract AFSocketImpl<A> newImpl(FileDescriptor fdObj) throws IOException;
123 
124   /**
125    * Creates a new AFServerSocket instance, using the given subclass constructor.
126    *
127    * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
128    * @param instanceSupplier The subclass constructor.
129    * @return The new instance.
130    * @throws IOException on error.
131    */
132   protected static <A extends AFSocketAddress> AFServerSocket<A> newInstance(
133       Constructor<A> instanceSupplier) throws IOException {
134     return instanceSupplier.newInstance(null);
135   }
136 
137   /**
138    * Creates a new AFServerSocket instance, using the given subclass constructor.
139    *
140    * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
141    * @param instanceSupplier The subclass constructor.
142    * @param fdObj The file descriptor.
143    * @param localPort The local port.
144    * @param remotePort The remote port.
145    * @return The new instance.
146    * @throws IOException on error.
147    */
148   protected static <A extends AFSocketAddress> AFServerSocket<A> newInstance(
149       Constructor<A> instanceSupplier, FileDescriptor fdObj, int localPort, int remotePort)
150       throws IOException {
151     if (fdObj == null) {
152       return instanceSupplier.newInstance(null);
153     }
154 
155     int status = NativeUnixSocket.socketStatus(fdObj);
156     if (!fdObj.valid() || status == NativeUnixSocket.SOCKETSTATUS_INVALID) {
157       throw new SocketException("Not a valid socket");
158     }
159     AFServerSocket<A> socket = instanceSupplier.newInstance(fdObj);
160     socket.getAFImpl().updatePorts(localPort, remotePort);
161 
162     switch (status) {
163       case NativeUnixSocket.SOCKETSTATUS_CONNECTED:
164         throw new SocketException("Not a ServerSocket");
165       case NativeUnixSocket.SOCKETSTATUS_BOUND:
166         socket.bind(AFSocketAddress.INTERNAL_DUMMY_BIND);
167 
168         socket.setBoundEndpoint(AFSocketAddress.getSocketAddress(fdObj, false, localPort, socket
169             .addressFamily()));
170         break;
171       case NativeUnixSocket.SOCKETSTATUS_UNKNOWN:
172         break;
173       default:
174         throw new IllegalStateException("Invalid socketStatus response: " + status);
175     }
176 
177     socket.getAFImpl().setSocketAddress(socket.getLocalSocketAddress());
178     return socket;
179   }
180 
181   /**
182    * Returns a new {@link ServerSocket} that is bound to the given {@link AFSocketAddress}.
183    *
184    * @param instanceSupplier The constructor of the concrete subclass.
185    * @param addr The socket file to bind to.
186    * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
187    * @return The new, bound {@link AFServerSocket}.
188    * @throws IOException if the operation fails.
189    */
190   protected static <A extends AFSocketAddress> AFServerSocket<A> bindOn(
191       Constructor<A> instanceSupplier, final AFSocketAddress addr) throws IOException {
192     AFServerSocket<A> socket = instanceSupplier.newInstance(null);
193     socket.bind(addr);
194     return socket;
195   }
196 
197   /**
198    * Returns a new {@link ServerSocket} that is bound to the given {@link AFSocketAddress}.
199    *
200    * @param instanceSupplier The constructor of the concrete subclass.
201    * @param addr The socket file to bind to.
202    * @param deleteOnClose If {@code true}, the socket file (if the address points to a file) will be
203    *          deleted upon {@link #close}.
204    * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
205    * @return The new, bound {@link AFServerSocket}.
206    * @throws IOException if the operation fails.
207    */
208   protected static <A extends AFSocketAddress> AFServerSocket<A> bindOn(
209       Constructor<A> instanceSupplier, final A addr, boolean deleteOnClose) throws IOException {
210     AFServerSocket<A> socket = instanceSupplier.newInstance(null);
211     socket.bind(addr);
212     socket.setDeleteOnClose(deleteOnClose);
213     return socket;
214   }
215 
216   /**
217    * Returns a new, <em>unbound</em> {@link ServerSocket} that will always bind to the given
218    * address, regardless of any socket address used in a call to <code>bind</code>.
219    *
220    * @param instanceSupplier The constructor of the concrete subclass.
221    * @param forceAddr The address to use.
222    * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
223    * @return The new, yet unbound {@link AFServerSocket}.
224    * @throws IOException if an exception occurs.
225    */
226   protected static <A extends AFSocketAddress> AFServerSocket<A> forceBindOn(
227       Constructor<A> instanceSupplier, final A forceAddr) throws IOException {
228     AFServerSocket<A> socket = instanceSupplier.newInstance(null);
229     return socket.forceBindAddress(forceAddr);
230   }
231 
232   /**
233    * Forces the address to be used for any subsequent call to {@link #bind(SocketAddress)} to be the
234    * given one, regardless of what'll be passed to {@link #bind(SocketAddress, int)}, but doesn't
235    * bind yet.
236    *
237    * @param endpoint The forced endpoint address.
238    * @return This {@link AFServerSocket}.
239    */
240   public final AFServerSocket<A> forceBindAddress(SocketAddress endpoint) {
241     return bindHook((SocketAddress orig) -> {
242       return orig == null ? null : endpoint;
243     });
244   }
245 
246   @Override
247   public final void bind(SocketAddress endpoint) throws IOException {
248     bind(endpoint, 50);
249   }
250 
251   @SuppressWarnings("unchecked")
252   @Override
253   public final void bind(SocketAddress endpoint, int backlog) throws IOException {
254     if (isClosed()) {
255       throw new SocketException("Socket is closed");
256     }
257 
258     boolean bindErrorOk;
259     if (bindFilter != null) {
260       endpoint = bindFilter.apply(endpoint);
261       bindErrorOk = endpoint != null && isBound();
262     } else {
263       bindErrorOk = false;
264     }
265 
266     endpoint = AFSocketAddress.mapOrFail(endpoint);
267 
268     A endpointCast;
269     try {
270       endpointCast = (A) endpoint;
271     } catch (ClassCastException e) {
272       throw new IllegalArgumentException("Can only bind to specific endpoints", e);
273     }
274 
275     try {
276       getAFImpl().bind(endpoint, getReuseAddress() ? NativeUnixSocket.BIND_OPT_REUSE : 0);
277     } catch (SocketException e) {
278       if (bindErrorOk) {
279         // force-binding an address could mean double-binding the same address, that's OK.
280         return;
281       } else {
282         throw e;
283       }
284     }
285     setBoundEndpoint(getAFImpl().getLocalSocketAddress());
286     if (boundEndpoint0() == null) {
287       setBoundEndpoint(endpointCast);
288     }
289 
290     if (endpoint == AFSocketAddress.INTERNAL_DUMMY_BIND) {
291       return;
292     }
293 
294     implementation.listen(backlog);
295   }
296 
297   @Override
298   public final boolean isBound() {
299     return boundEndpoint0() != null && implementation.getFD().valid();
300   }
301 
302   @Override
303   public final boolean isClosed() {
304     return super.isClosed() || (isBound() && !implementation.getFD().valid()) || implementation
305         .isClosed();
306   }
307 
308   @Override
309   public AFSocket<A> accept() throws IOException {
310     return accept1(true);
311   }
312 
313   AFSocket<A> accept1(boolean throwOnFail) throws IOException {
314     AFSocket<A> as = newSocketInstance();
315 
316     boolean success = implementation.accept0(as.getAFImpl(false));
317     if (isClosed()) {
318       // We may have connected to the socket to unblock it
319       throw new BrokenPipeSocketException("Socket is closed");
320     }
321 
322     if (!success) {
323       if (throwOnFail) {
324         if (getChannel().isBlocking()) {
325           // unexpected
326           return null;
327         } else {
328           // non-blocking socket, nothing to accept
329           throw new IllegalBlockingModeException();
330         }
331       } else {
332         return null;
333       }
334     }
335 
336     as.getAFImpl(true); // trigger create
337     as.connect(AFSocketAddress.INTERNAL_DUMMY_CONNECT);
338     as.getAFImpl().updatePorts(getAFImpl().getLocalPort1(), getAFImpl().getRemotePort());
339 
340     return as;
341   }
342 
343   /**
344    * Returns a new {@link AFSocket} instance.
345    *
346    * @return The new instance.
347    * @throws IOException on error.
348    */
349   protected abstract AFSocket<A> newSocketInstance() throws IOException;
350 
351   @Override
352   public String toString() {
353     return getClass().getSimpleName() + "[" + (isBound() ? boundEndpoint0() : "unbound") + "]";
354   }
355 
356   @Override
357   public void close() throws IOException {
358     if (!closed.compareAndSet(false, true)) {
359       return;
360     }
361     if (isClosed()) {
362       return;
363     }
364 
365     boolean localSocketAddressValid = isLocalSocketAddressValid();
366 
367     AFSocketAddress endpoint = boundEndpoint;
368 
369     IOException superException = null;
370     try {
371       super.close();
372     } catch (IOException e) {
373       superException = e;
374     }
375     if (implementation != null) {
376       try {
377         implementation.close();
378       } catch (IOException e) {
379         if (superException == null) {
380           superException = e;
381         } else {
382           superException.addSuppressed(e);
383         }
384       }
385     }
386 
387     IOException ex = null;
388     try {
389       closeables.close(superException);
390     } finally {
391       if (endpoint != null && endpoint.hasFilename() && localSocketAddressValid
392           && isDeleteOnClose()) {
393         File f = endpoint.getFile();
394         if (!f.delete() && f.exists()) {
395           ex = new IOException("Could not delete socket file after close: " + f);
396         }
397       }
398     }
399     if (ex != null) {
400       throw ex;
401     }
402   }
403 
404   /**
405    * Registers a {@link Closeable} that should be closed when this socket is closed.
406    *
407    * @param closeable The closeable.
408    */
409   public final void addCloseable(Closeable closeable) {
410     closeables.add(closeable);
411   }
412 
413   /**
414    * Unregisters a previously registered {@link Closeable}.
415    *
416    * @param closeable The closeable.
417    */
418   public final void removeCloseable(Closeable closeable) {
419     closeables.remove(closeable);
420   }
421 
422   /**
423    * Checks whether everything is setup to support junixsocket sockets.
424    *
425    * @return {@code true} if supported.
426    */
427   public static boolean isSupported() {
428     return NativeUnixSocket.isLoaded();
429   }
430 
431   @Override
432   public final @Nullable A getLocalSocketAddress() {
433     @Nullable
434     A ep = boundEndpoint0();
435     if (ep == null) {
436       ep = getAFImpl().getLocalSocketAddress();
437       setBoundEndpoint(ep);
438     }
439     return ep;
440   }
441 
442   private synchronized @Nullable A boundEndpoint0() {
443     return boundEndpoint;
444   }
445 
446   /**
447    * Checks if the local socket address returned by {@link #getLocalSocketAddress()} is still valid.
448    *
449    * The address is no longer valid if the server socket has been closed, {@code null}, or another
450    * server socket has been bound on that address.
451    *
452    * @return {@code true} iff still valid.
453    */
454   public boolean isLocalSocketAddressValid() {
455     if (isClosed()) {
456       return false;
457     }
458     @Nullable
459     A addr = getLocalSocketAddress();
460     if (addr == null) {
461       return false;
462     }
463     byte[] addrBytes = addr.getBytes();
464     if (addrBytes == null) {
465       return false;
466     }
467     byte[] sab = getAFImpl().getLocalSocketAddressBytes();
468     if (sab == null) {
469       return false;
470     }
471     return Arrays.equals(sab, addrBytes);
472   }
473 
474   final synchronized void setBoundEndpoint(@Nullable A addr) {
475     this.boundEndpoint = addr;
476     int port;
477     if (addr == null) {
478       port = -1;
479     } else {
480       port = addr.getPort();
481     }
482     getAFImpl().updatePorts(port, -1);
483   }
484 
485   @Override
486   public final int getLocalPort() {
487     if (boundEndpoint0() == null) {
488       setBoundEndpoint(getAFImpl().getLocalSocketAddress());
489     }
490     if (boundEndpoint0() == null) {
491       return -1;
492     } else {
493       return getAFImpl().getLocalPort1();
494     }
495   }
496 
497   /**
498    * Checks if this {@link AFServerSocket}'s file should be removed upon {@link #close()}.
499    *
500    * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract
501    * namespace).
502    *
503    * @return {@code true} if an attempt is made to delete the socket file upon {@link #close()}.
504    */
505   public final boolean isDeleteOnClose() {
506     return deleteOnClose.get();
507   }
508 
509   /**
510    * Enables/disables deleting this {@link AFServerSocket}'s file (or other resource type) upon
511    * {@link #close()}.
512    *
513    * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract
514    * namespace).
515    *
516    * @param b Enabled if {@code true}.
517    */
518   public final void setDeleteOnClose(boolean b) {
519     deleteOnClose.set(b);
520   }
521 
522   final AFSocketImpl<A> getAFImpl() {
523     if (created.compareAndSet(false, true)) {
524       try {
525         getAFImpl().create(true);
526         getSoTimeout(); // trigger create via java.net.Socket
527       } catch (IOException e) {
528         // ignore
529       }
530     }
531     return implementation;
532   }
533 
534   @SuppressFBWarnings("EI_EXPOSE_REP")
535   @Override
536   public AFServerSocketChannel<A> getChannel() {
537     return channel;
538   }
539 
540   @Override
541   public final FileDescriptor getFileDescriptor() throws IOException {
542     return implementation.getFileDescriptor();
543   }
544 
545   /**
546    * Returns the address family supported by this implementation.
547    *
548    * @return The family.
549    */
550   protected final AFAddressFamily<A> addressFamily() {
551     return getAFImpl().getAddressFamily();
552   }
553 
554   /**
555    * Sets the hook for any subsequent call to {@link #bind(SocketAddress)} and
556    * {@link #bind(SocketAddress, int)} to be the given function.
557    *
558    * The function can monitor calls or even alter the endpoint address.
559    *
560    * @param hook The function that gets called for each {@code bind} call.
561    * @return This instance.
562    */
563   public final AFServerSocket<A> bindHook(SocketAddressFilter hook) {
564     this.bindFilter = hook;
565     return this;
566   }
567 
568   @Override
569   public InetAddress getInetAddress() {
570     if (!isBound()) {
571       return null;
572     } else {
573       return getAFImpl().getInetAddress();
574     }
575   }
576 
577   @Override
578   public synchronized void setReceiveBufferSize(int size) throws SocketException {
579     if (size <= 0) {
580       throw new IllegalArgumentException("receive buffer size must be a positive number");
581     }
582     if (isClosed()) {
583       throw new SocketException("Socket is closed");
584     }
585     getAFImpl().setOption(SocketOptions.SO_RCVBUF, size);
586   }
587 
588   @Override
589   public synchronized int getReceiveBufferSize() throws SocketException {
590     if (isClosed()) {
591       throw new SocketException("Socket is closed");
592     }
593     int result = 0;
594     Object o = getAFImpl().getOption(SocketOptions.SO_RCVBUF);
595     if (o instanceof Number) {
596       result = ((Number) o).intValue();
597     }
598     return result;
599   }
600 
601   @Override
602   @SuppressWarnings("UnsynchronizedOverridesSynchronized" /* errorprone */)
603   public void setSoTimeout(int timeout) throws SocketException {
604     if (isClosed()) {
605       throw new SocketException("Socket is closed");
606     }
607     if (timeout < 0) {
608       throw new IllegalArgumentException("timeout < 0");
609     }
610     getAFImpl().setOption(SocketOptions.SO_TIMEOUT, timeout);
611   }
612 
613   @Override
614   @SuppressWarnings("UnsynchronizedOverridesSynchronized" /* errorprone */)
615   public int getSoTimeout() throws IOException {
616     if (isClosed()) {
617       throw new SocketException("Socket is closed");
618     }
619     Object o = getAFImpl().getOption(SocketOptions.SO_TIMEOUT);
620     /* extra type safety */
621     if (o instanceof Number) {
622       return ((Number) o).intValue();
623     } else {
624       return 0;
625     }
626   }
627 
628   @Override
629   public void setReuseAddress(boolean on) throws SocketException {
630     if (isClosed()) {
631       throw new SocketException("Socket is closed");
632     }
633     getAFImpl().setOption(SocketOptions.SO_REUSEADDR, on);
634   }
635 
636   @Override
637   public boolean getReuseAddress() throws SocketException {
638     if (isClosed()) {
639       throw new SocketException("Socket is closed");
640     }
641     return ((Boolean) (getAFImpl().getOption(SocketOptions.SO_REUSEADDR)));
642   }
643 
644   @Override
645   public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
646   }
647 
648   @SuppressWarnings({"all", "MissingOverride" /* errorprone */})
649   public <T> T getOption(SocketOption<T> name) throws IOException {
650     Objects.requireNonNull(name);
651     if (isClosed()) {
652       throw new SocketException("Socket is closed");
653     }
654     return getAFImpl().getOption(name);
655   }
656 
657   @SuppressWarnings({"all", "MissingOverride" /* errorprone */})
658   public <T> ServerSocket setOption(SocketOption<T> name, T value) throws IOException {
659     Objects.requireNonNull(name);
660     if (isClosed()) {
661       throw new SocketException("Socket is closed");
662     }
663     getAFImpl().setOption(name, value);
664     return this;
665   }
666 
667   @SuppressWarnings("all")
668   public Set<SocketOption<?>> supportedOptions() {
669     return getAFImpl().supportedOptions();
670   }
671 
672   @Override
673   public void setShutdownOnClose(boolean enabled) {
674     getAFImpl().getCore().setShutdownOnClose(enabled);
675   }
676 
677   // NOTE: We shall re-implement all methods defined in ServerSocket that internally call getImpl()
678   // and call getAFImpl() here. This is not strictly necessary for environments where we can
679   // override "impl"; however it's the right thing to do.
680 }