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