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