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.FileDescriptor;
22  import java.io.IOException;
23  import java.net.Socket;
24  import java.net.SocketAddress;
25  import java.net.SocketException;
26  import java.net.SocketImpl;
27  import java.util.concurrent.atomic.AtomicBoolean;
28  
29  import org.eclipse.jdt.annotation.NonNull;
30  import org.eclipse.jdt.annotation.Nullable;
31  
32  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
33  
34  /**
35   * junixsocket's base implementation of a {@link Socket}.
36   *
37   * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
38   * @author Christian Kohlschütter
39   */
40  @SuppressWarnings({"PMD.CouplingBetweenObjects", "PMD.CyclomaticComplexity"})
41  public abstract class AFSocket<A extends AFSocketAddress> extends Socket implements AFSomeSocket,
42      AFSocketExtensions {
43    static final String PROP_LIBRARY_DISABLE_CAPABILITY_PREFIX =
44        "org.newsclub.net.unix.library.disable.";
45  
46    private static final byte[] ZERO_BYTES = new byte[0];
47  
48    @SuppressWarnings("PMD.MutableStaticState")
49    static String loadedLibrary; // set by NativeLibraryLoader
50  
51    private static Integer capabilitiesValue = null;
52  
53    private final AFSocketImpl<A> impl;
54  
55    private final AFSocketAddressFromHostname<A> afh;
56    private final Closeables closeables = new Closeables();
57    private final AtomicBoolean created = new AtomicBoolean(false);
58  
59    @SuppressWarnings("this-escape")
60    private final AFSocketChannel<A> channel = newChannel();
61  
62    private @Nullable SocketAddressFilter connectFilter;
63  
64    /**
65     * Creates a new {@link AFSocket} instance.
66     *
67     * @param impl The corresponding {@link SocketImpl} class.
68     * @param afh The conversion helper to get a socket address from an encoded hostname.
69     * @throws SocketException on error.
70     */
71    @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
72    protected AFSocket(final AFSocketImpl<A> impl, AFSocketAddressFromHostname<A> afh)
73        throws SocketException {
74      super(impl);
75      this.afh = afh;
76      this.impl = impl;
77    }
78  
79    /**
80     * Returns the {@link AFSocketAddress} type supported by this socket.
81     *
82     * @return The supported {@link AFSocketAddress}.
83     */
84    protected final Class<? extends AFSocketAddress> socketAddressClass() {
85      return getAFImpl(false).getAddressFamily().getSocketAddressClass();
86    }
87  
88    /**
89     * Creates a new {@link AFSocketChannel} for this socket.
90     *
91     * @return The new instance.
92     */
93    protected abstract AFSocketChannel<A> newChannel();
94  
95    /**
96     * The reference to the constructor of an {@link AFSocket} subclass.
97     *
98     * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
99     */
100   @FunctionalInterface
101   public interface Constructor<A extends AFSocketAddress> {
102     /**
103      * Constructs a new {@link AFSocket} subclass instance.
104      *
105      * @param fdObj The file descriptor.
106      * @param factory The socket factory instance.
107      * @return The instance.
108      * @throws SocketException on error.
109      */
110     @NonNull
111     AFSocket<A> newInstance(FileDescriptor fdObj, AFSocketFactory<A> factory)
112         throws SocketException;
113   }
114 
115   static <A extends AFSocketAddress> AFSocket<A> newInstance(Constructor<A> constr,
116       AFSocketFactory<A> sf, FileDescriptor fdObj, int localPort, int remotePort)
117       throws IOException {
118     if (!fdObj.valid()) {
119       throw new SocketException("Invalid file descriptor");
120     }
121     int status = NativeUnixSocket.socketStatus(fdObj);
122     if (status == NativeUnixSocket.SOCKETSTATUS_INVALID) {
123       throw new SocketException("Not a valid socket");
124     }
125 
126     AFSocket<A> socket = newInstance0(constr, fdObj, sf);
127     socket.getAFImpl().updatePorts(localPort, remotePort);
128 
129     switch (status) {
130       case NativeUnixSocket.SOCKETSTATUS_CONNECTED:
131         socket.internalDummyConnect();
132         break;
133       case NativeUnixSocket.SOCKETSTATUS_BOUND:
134         socket.internalDummyBind();
135         break;
136       case NativeUnixSocket.SOCKETSTATUS_UNKNOWN:
137         break;
138       default:
139         throw new IllegalStateException("Invalid socketStatus response: " + status);
140     }
141     socket.getAFImpl().setSocketAddress(socket.getLocalSocketAddress());
142 
143     return socket;
144   }
145 
146   /**
147    * Creates a new, unbound {@link AFSocket}.
148    *
149    * This "default" implementation is a bit "lenient" with respect to the specification.
150    *
151    * In particular, we may ignore calls to {@link Socket#getTcpNoDelay()} and
152    * {@link Socket#setTcpNoDelay(boolean)}.
153    *
154    * @param <A> The corresponding address type.
155    * @param constr The implementation's {@link AFSocket} constructor
156    * @param factory The corresponding socket factory, or {@code null}.
157    * @return A new, unbound socket.
158    * @throws SocketException if the operation fails.
159    */
160   protected static final <A extends AFSocketAddress> AFSocket<A> newInstance(Constructor<A> constr,
161       AFSocketFactory<A> factory) throws SocketException {
162     return newInstance0(constr, null, factory);
163   }
164 
165   private static <A extends AFSocketAddress> @NonNull AFSocket<A> newInstance0(
166       Constructor<A> constr, FileDescriptor fdObj, AFSocketFactory<A> factory)
167       throws SocketException {
168     return constr.newInstance(fdObj, factory);
169   }
170 
171   /**
172    * Creates a new {@link AFSocket} and connects it to the given {@link AFSocketAddress}.
173    *
174    * @param <A> The corresponding address type.
175    * @param constr The implementation's {@link AFSocket} constructor
176    * @param addr The address to connect to.
177    * @return A new, connected socket.
178    * @throws IOException if the operation fails.
179    */
180   protected static final <A extends AFSocketAddress> @NonNull AFSocket<A> connectTo(
181       Constructor<A> constr, A addr) throws IOException {
182     AFSocket<A> socket = constr.newInstance(null, null);
183     socket.connect(addr);
184     return socket;
185   }
186 
187   /**
188    * Creates a new {@link AFSocket} and connects it to the given {@link AFSocketAddress} using the
189    * default implementation suited for that address type.
190    *
191    * @param <A> The corresponding address type.
192    * @param addr The address to connect to.
193    * @return A new, connected socket.
194    * @throws IOException if the operation fails.
195    */
196   public static final <A extends AFSocketAddress> AFSocket<?> connectTo(@NonNull A addr)
197       throws IOException {
198     AFSocket<?> socket = addr.getAddressFamily().getSocketConstructor().newInstance(null, null);
199     socket.connect(addr);
200     return socket;
201   }
202 
203   /**
204    * Not supported, since it's not necessary for client sockets.
205    *
206    * @see AFServerSocket
207    */
208   @Override
209   public final void bind(SocketAddress bindpoint) throws IOException {
210     if (bindpoint == null) {
211       throw new IllegalArgumentException();
212     }
213     if (isClosed()) {
214       throw new SocketException("Socket is closed");
215     }
216     if (isBound()) {
217       throw new SocketException("Already bound");
218     }
219     preprocessSocketAddress(bindpoint);
220     throw new SocketException("Use AF*ServerSocket#bind or #bindOn");
221   }
222 
223   @Override
224   public final boolean isBound() {
225     return impl.getFD().valid() && (super.isBound() || impl.isBound());
226   }
227 
228   @Override
229   public final boolean isConnected() {
230     return impl.getFD().valid() && (super.isConnected() || impl.isConnected());
231   }
232 
233   @Override
234   public final void connect(SocketAddress endpoint) throws IOException {
235     connect(endpoint, 0);
236   }
237 
238   @Override
239   public final void connect(SocketAddress endpoint, int timeout) throws IOException {
240     connect0(endpoint, timeout);
241   }
242 
243   private AFSocketAddress preprocessSocketAddress(SocketAddress endpoint) throws SocketException {
244     if (endpoint == null) {
245       throw new IllegalArgumentException("endpoint is null");
246     } else if (endpoint instanceof SentinelSocketAddress) {
247       return (AFSocketAddress) endpoint;
248     } else {
249       return AFSocketAddress.preprocessSocketAddress(socketAddressClass(), endpoint, afh);
250     }
251   }
252 
253   final boolean connect0(SocketAddress endpoint, int timeout) throws IOException {
254     if (timeout < 0) {
255       throw new IllegalArgumentException("connect: timeout can't be negative");
256     }
257     if (isClosed()) {
258       throw new SocketException("Socket is closed");
259     }
260 
261     if (connectFilter != null) {
262       endpoint = connectFilter.apply(endpoint);
263     }
264 
265     AFSocketAddress address = preprocessSocketAddress(endpoint);
266 
267     if (!isBound()) {
268       internalDummyBind();
269     }
270 
271     boolean success = getAFImpl().connect0(address, timeout);
272     if (success) {
273       int port = address.getPort();
274       if (port > 0) {
275         getAFImpl().updatePorts(getLocalPort(), port);
276       }
277     }
278     internalDummyConnect();
279     return success;
280   }
281 
282   final void internalDummyConnect() throws IOException {
283     if (!isConnected()) {
284       super.connect(AFSocketAddress.INTERNAL_DUMMY_CONNECT, 0);
285     }
286   }
287 
288   final void internalDummyBind() throws IOException {
289     if (!isBound()) {
290       super.bind(AFSocketAddress.INTERNAL_DUMMY_BIND);
291     }
292   }
293 
294   @Override
295   public final String toString() {
296     return getClass().getName() + "@" + Integer.toHexString(hashCode()) + toStringSuffix();
297   }
298 
299   final String toStringSuffix() {
300     if (impl.getFD().valid()) {
301       return "[local=" + getLocalSocketAddress() + ";remote=" + getRemoteSocketAddress() + "]";
302     } else {
303       return "[invalid]";
304     }
305   }
306 
307   /**
308    * Returns <code>true</code> iff {@link AFSocket}s are supported by the current Java VM.
309    *
310    * To support {@link AFSocket}s, a custom JNI library must be loaded that is supplied with
311    * <em>junixsocket</em>.
312    *
313    * @return {@code true} iff supported.
314    */
315   public static boolean isSupported() {
316     return NativeUnixSocket.isLoaded();
317   }
318 
319   /**
320    * Checks if {@link AFSocket}s are supported by the current Java VM.
321    *
322    * If not, an {@link UnsupportedOperationException} is thrown.
323    *
324    * @throws UnsupportedOperationException if not supported.
325    */
326   public static void ensureSupported() throws UnsupportedOperationException {
327     NativeUnixSocket.ensureSupported();
328   }
329 
330   /**
331    * Returns the version of the junixsocket library, as a string, for debugging purposes.
332    *
333    * NOTE: Do not rely on the format of the version identifier, use socket capabilities instead.
334    *
335    * @return String The version identifier, or {@code null} if it could not be determined.
336    * @see #supports(AFSocketCapability)
337    */
338   public static final String getVersion() {
339     String v = BuildProperties.getBuildProperties().get("git.build.version");
340     if (v != null && !v.startsWith("$")) {
341       return v;
342     }
343 
344     try {
345       return NativeLibraryLoader.getJunixsocketVersion();
346     } catch (IOException e) {
347       return null;
348     }
349   }
350 
351   /**
352    * Returns an identifier of the loaded native library, or {@code null} if the library hasn't been
353    * loaded yet.
354    *
355    * The identifier is useful mainly for debugging purposes.
356    *
357    * @return The identifier of the loaded junixsocket-native library, or {@code null}.
358    */
359   public static final String getLoadedLibrary() {
360     return loadedLibrary;
361   }
362 
363   @Override
364   public final boolean isClosed() {
365     return super.isClosed() || (isConnected() && !impl.getFD().valid()) || impl.isClosed();
366   }
367 
368   @Override
369   public final int getAncillaryReceiveBufferSize() {
370     return impl.getAncillaryReceiveBufferSize();
371   }
372 
373   @Override
374   public final void setAncillaryReceiveBufferSize(int size) {
375     impl.setAncillaryReceiveBufferSize(size);
376   }
377 
378   @Override
379   public final void ensureAncillaryReceiveBufferSize(int minSize) {
380     impl.ensureAncillaryReceiveBufferSize(minSize);
381   }
382 
383   private static boolean isCapDisabled(AFSocketCapability cap) {
384     return Boolean.parseBoolean(System.getProperty(PROP_LIBRARY_DISABLE_CAPABILITY_PREFIX + cap
385         .name(), "false"));
386   }
387 
388   private static int initCapabilities() {
389     if (!isSupported()) {
390       return 0;
391     } else {
392       int v = NativeUnixSocket.capabilities();
393 
394       if (System.getProperty("osv.version") != null) {
395         // no fork, no redirect...
396         v &= ~(AFSocketCapability.CAPABILITY_FD_AS_REDIRECT.getBitmask());
397       }
398 
399       for (AFSocketCapability cap : AFSocketCapability.values()) {
400         if (isCapDisabled(cap)) {
401           v &= ~(cap.getBitmask());
402         }
403       }
404 
405       return v;
406     }
407   }
408 
409   private static synchronized int capabilities() {
410     if (capabilitiesValue == null) {
411       capabilitiesValue = initCapabilities();
412     }
413     return capabilitiesValue;
414   }
415 
416   /**
417    * Checks if the current environment (system platform, native library, etc.) supports a given
418    * junixsocket capability.
419    *
420    * Deprecated. Please use {@link #supports(AFSocketCapability)} instead.
421    *
422    * NOTE: The result may or may not be cached from a previous call or from a check upon
423    * initialization.
424    *
425    * @param capability The capability.
426    * @return true if supported.
427    * @see #supports(AFSocketCapability)
428    */
429   @Deprecated
430   public static final boolean supports(AFUNIXSocketCapability capability) {
431     return (capabilities() & capability.getBitmask()) != 0;
432   }
433 
434   /**
435    * Checks if the current environment (system platform, native library, etc.) supports a given
436    * junixsocket capability.
437    *
438    * NOTE: The result may or may not be cached from a previous call or from a check upon
439    * initialization.
440    *
441    * @param capability The capability.
442    * @return true if supported.
443    */
444   public static final boolean supports(AFSocketCapability capability) {
445     return (capabilities() & capability.getBitmask()) != 0;
446   }
447 
448   /**
449    * Checks if the current environment (system platform, native library, etc.) supports "unsafe"
450    * operations (as controlled via the {@link AFSocketCapability#CAPABILITY_UNSAFE} capability).
451    *
452    * If supported, the method returns normally. If not supported, an {@link IOException} is thrown.
453    *
454    * @throws IOException if "unsafe" operations are not supported.
455    * @see Unsafe
456    */
457   public static final void ensureUnsafeSupported() throws IOException {
458     if (!AFSocket.supports(AFSocketCapability.CAPABILITY_UNSAFE)) {
459       throw new IOException("Unsafe operations are not supported in this environment");
460     }
461   }
462 
463   @Override
464   public final synchronized void close() throws IOException {
465     IOException superException = null;
466     try {
467       super.close();
468     } catch (IOException e) {
469       superException = e;
470     }
471     closeables.close(superException);
472   }
473 
474   /**
475    * Registers a {@link Closeable} that should be closed when this socket is closed.
476    *
477    * @param closeable The closeable.
478    */
479   public final void addCloseable(Closeable closeable) {
480     closeables.add(closeable);
481   }
482 
483   /**
484    * Unregisters a previously registered {@link Closeable}.
485    *
486    * @param closeable The closeable.
487    */
488   public final void removeCloseable(Closeable closeable) {
489     closeables.remove(closeable);
490   }
491 
492   final AFSocketImpl<A> getAFImpl() {
493     return getAFImpl(true);
494   }
495 
496   final AFSocketImpl<A> getAFImpl(boolean createSocket) {
497     if (createSocket && created.compareAndSet(false, true)) {
498       try {
499         getSoTimeout(); // trigger create via java.net.Socket
500       } catch (SocketException e) {
501         // ignore
502       }
503     }
504     return impl;
505   }
506 
507   @SuppressFBWarnings("EI_EXPOSE_REP")
508   @Override
509   public AFSocketChannel<A> getChannel() {
510     return channel;
511   }
512 
513   @SuppressWarnings("null")
514   @Override
515   public final synchronized A getRemoteSocketAddress() {
516     if (!isConnected()) {
517       return null;
518     }
519     return impl.getRemoteSocketAddress();
520   }
521 
522   @SuppressWarnings("null")
523   @Override
524   public final A getLocalSocketAddress() {
525     if (isClosed()) {
526       return null;
527     }
528     return impl.getLocalSocketAddress();
529   }
530 
531   @Override
532   public final FileDescriptor getFileDescriptor() throws IOException {
533     return impl.getFileDescriptor();
534   }
535 
536   @Override
537   public final AFInputStream getInputStream() throws IOException {
538     return getAFImpl().getInputStream();
539   }
540 
541   @Override
542   public final AFOutputStream getOutputStream() throws IOException {
543     return getAFImpl().getOutputStream();
544   }
545 
546   /**
547    * Returns the internal helper instance for address-specific extensions.
548    *
549    * @return The helper instance.
550    * @throws UnsupportedOperationException if such extensions are not supported for this address
551    *           type.
552    */
553   protected final AFSocketImplExtensions<A> getImplExtensions() {
554     return getAFImpl(false).getImplExtensions();
555   }
556 
557   /**
558    * Forces the address to be used for any subsequent call to {@link #connect(SocketAddress)} to be
559    * the given one, regardless of what'll be passed there.
560    *
561    * @param endpoint The forced endpoint address.
562    * @return This instance.
563    */
564   public final AFSocket<A> forceConnectAddress(SocketAddress endpoint) {
565     return connectHook((SocketAddress orig) -> {
566       return orig == null ? null : endpoint;
567     });
568   }
569 
570   /**
571    * Sets the hook for any subsequent call to {@link #connect(SocketAddress)} or
572    * {@link #connect(SocketAddress, int)} to be the given function.
573    *
574    * The function can monitor events or even alter the target address.
575    *
576    * @param hook The function that gets called for each connect call.
577    * @return This instance.
578    */
579   public final AFSocket<A> connectHook(SocketAddressFilter hook) {
580     this.connectFilter = hook;
581     return this;
582   }
583 
584   /**
585    * Probes the status of the socket connection.
586    *
587    * This usually involves checking for {@link #isConnected()}, and if assumed connected, also
588    * sending a zero-length message to the remote.
589    *
590    * @return {@code true} if the connection is known to be closed, {@code false} if the connection
591    *         is open/not closed or the condition is unknown.
592    * @throws IOException on an unexpected error.
593    */
594   public boolean checkConnectionClosed() throws IOException {
595     if (!isConnected()) {
596       return true;
597     }
598     try {
599       if (!AFSocket.supports(AFSocketCapability.CAPABILITY_ZERO_LENGTH_SEND)) {
600         return false;
601       }
602       getOutputStream().write(ZERO_BYTES);
603       return false;
604     } catch (SocketClosedException e) {
605       return true;
606     } catch (IOException e) {
607       if (!isConnected()) {
608         return true;
609       } else {
610         throw e;
611       }
612     }
613   }
614 
615   /**
616    * Checks if we're running on Android (as far as junixsocket is concerned).
617    *
618    * @return {@code true} if running on Android.
619    */
620   public static boolean isRunningOnAndroid() {
621     return NativeLibraryLoader.isAndroid();
622   }
623 
624   @Override
625   public void setShutdownOnClose(boolean enabled) {
626     getAFImpl().getCore().setShutdownOnClose(enabled);
627   }
628 }