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.FileDescriptor;
21  import java.io.IOException;
22  import java.net.DatagramPacket;
23  import java.net.DatagramSocket;
24  import java.net.InetAddress;
25  import java.net.InetSocketAddress;
26  import java.net.SocketAddress;
27  import java.net.SocketException;
28  import java.net.SocketImpl;
29  import java.nio.channels.AlreadyBoundException;
30  import java.nio.channels.DatagramChannel;
31  import java.nio.channels.IllegalBlockingModeException;
32  import java.util.concurrent.atomic.AtomicBoolean;
33  
34  import org.eclipse.jdt.annotation.Nullable;
35  
36  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
37  
38  /**
39   * A {@link DatagramSocket} implementation that works with junixsocket.
40   *
41   * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
42   * @author Christian Kohlschütter
43   */
44  public abstract class AFDatagramSocket<A extends AFSocketAddress> extends DatagramSocketShim
45      implements AFSomeSocket, AFSocketExtensions {
46    private static final InetSocketAddress WILDCARD_ADDRESS = new InetSocketAddress(0);
47  
48    private final AFDatagramSocketImpl<A> impl;
49    private final AncillaryDataSupport ancillaryDataSupport;
50    private final AtomicBoolean created = new AtomicBoolean(false);
51    private final AtomicBoolean deleteOnClose = new AtomicBoolean(true);
52  
53    @SuppressWarnings("this-escape")
54    private final AFDatagramChannel<A> channel = newChannel();
55  
56    /**
57     * Creates a new {@link AFDatagramSocket} instance.
58     *
59     * @param impl The corresponding {@link SocketImpl} class.
60     */
61    protected AFDatagramSocket(final AFDatagramSocketImpl<A> impl) {
62      super(impl);
63      this.impl = impl;
64      this.ancillaryDataSupport = impl.ancillaryDataSupport;
65    }
66  
67    /**
68     * Creates a new {@link DatagramChannel} that is associated with this socket.
69     *
70     * @return The channel.
71     */
72    protected abstract AFDatagramChannel<A> newChannel();
73  
74    /**
75     * Returns the {@code AncillaryDataSupport} instance.
76     *
77     * @return The instance.
78     */
79    final AncillaryDataSupport getAncillaryDataSupport() {
80      return ancillaryDataSupport;
81    }
82  
83    /**
84     * A reference to the constructor of an {@link AFDatagramSocket} subclass.
85     *
86     * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
87     */
88    @FunctionalInterface
89    public interface Constructor<A extends AFSocketAddress> {
90      /**
91       * Constructs a new {@link DatagramSocket} instance.
92       *
93       * @param fd The file descriptor.
94       * @return The new instance.
95       * @throws IOException on error.
96       */
97      AFDatagramSocket<A> newSocket(FileDescriptor fd) throws IOException;
98    }
99  
100   /**
101    * Returns the {@link AFSocketAddress} type supported by this socket.
102    *
103    * @return The supported {@link AFSocketAddress}.
104    */
105   protected final Class<? extends AFSocketAddress> socketAddressClass() {
106     return impl.getAddressFamily().getSocketAddressClass();
107   }
108 
109   /**
110    * Returns a new {@link AFDatagramSocket} instance.
111    *
112    * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
113    * @param constructor The supplying constructor.
114    * @return The new instance.
115    * @throws IOException on error.
116    */
117   protected static final <A extends AFSocketAddress> AFDatagramSocket<A> newInstance(
118       Constructor<A> constructor) throws IOException {
119     return constructor.newSocket(null);
120   }
121 
122   /**
123    * Creates a new {@link AFDatagramSocket}.
124    *
125    * @param <A> The concrete {@link AFSocketAddress} that is supported by this type.
126    * @param constructor The supplying constructor.
127    * @param fdObj The file descriptor.
128    * @param localPort The local port.
129    * @param remotePort The remote port.
130    * @return The new instance.
131    * @throws IOException on error.
132    */
133   protected static final <A extends AFSocketAddress> AFDatagramSocket<A> newInstance(
134       Constructor<A> constructor, FileDescriptor fdObj, int localPort, int remotePort)
135       throws IOException {
136     if (fdObj == null) {
137       return newInstance(constructor);
138     }
139     if (!fdObj.valid()) {
140       throw new SocketException("Invalid file descriptor");
141     }
142 
143     int status = NativeUnixSocket.socketStatus(fdObj);
144     if (status == NativeUnixSocket.SOCKETSTATUS_INVALID) {
145       throw new SocketException("Not a valid socket");
146     }
147 
148     AFDatagramSocket<A> socket = constructor.newSocket(fdObj);
149     socket.getAFImpl().updatePorts(localPort, remotePort);
150 
151     switch (status) {
152       case NativeUnixSocket.SOCKETSTATUS_CONNECTED:
153         socket.internalDummyConnect();
154         break;
155       case NativeUnixSocket.SOCKETSTATUS_BOUND:
156         socket.internalDummyBind();
157         break;
158       case NativeUnixSocket.SOCKETSTATUS_UNKNOWN:
159         break;
160       default:
161         throw new IllegalStateException("Invalid socketStatus response: " + status);
162     }
163 
164     return socket;
165   }
166 
167   @Override
168   public final void connect(InetAddress address, int port) {
169     throw new IllegalArgumentException("Cannot connect to InetAddress");
170   }
171 
172   /**
173    * Reads the next received packet without actually removing it from the queue.
174    *
175    * In other words, once a packet is received, calling this method multiple times in a row will not
176    * have further effects on the packet contents.
177    *
178    * This call still blocks until at least one packet has been received and added to the queue.
179    *
180    * @param p The packet.
181    * @throws IOException on error.
182    */
183   public final void peek(DatagramPacket p) throws IOException {
184     synchronized (p) {
185       if (isClosed()) {
186         throw new SocketException("Socket is closed");
187       }
188       getAFImpl().peekData(p);
189     }
190   }
191 
192   @Override
193   public final void send(DatagramPacket p) throws IOException {
194     synchronized (p) {
195       if (isClosed()) {
196         throw new SocketException("Socket is closed");
197       }
198       if (!isBound()) {
199         internalDummyBind();
200       }
201       getAFImpl().send(p);
202     }
203   }
204 
205   final void internalDummyConnect() throws SocketException {
206     super.connect(AFSocketAddress.INTERNAL_DUMMY_DONT_CONNECT);
207   }
208 
209   final void internalDummyBind() throws SocketException {
210     bind(AFSocketAddress.INTERNAL_DUMMY_BIND);
211   }
212 
213   @Override
214   public final synchronized void connect(SocketAddress addr) throws SocketException {
215     if (!isBound()) {
216       internalDummyBind();
217     }
218     internalDummyConnect();
219     try {
220       getAFImpl().connect(AFSocketAddress.preprocessSocketAddress(socketAddressClass(), addr,
221           null));
222     } catch (SocketException e) {
223       throw e;
224     } catch (IOException e) {
225       throw (SocketException) new SocketException(e.getMessage()).initCause(e);
226     }
227   }
228 
229   @Override
230   public final synchronized @Nullable A getRemoteSocketAddress() {
231     return getAFImpl().getRemoteSocketAddress();
232   }
233 
234   @Override
235   public final boolean isConnected() {
236     return super.isConnected() || impl.isConnected();
237   }
238 
239   @Override
240   public final boolean isBound() {
241     return super.isBound() || impl.isBound();
242   }
243 
244   @Override
245   public final void close() {
246     // IMPORTANT This method must not be synchronized on "this",
247     // otherwise we can't unblock a pending read
248     if (isClosed()) {
249       return;
250     }
251     getAFImpl().close();
252     boolean wasBound = isBound();
253     if (wasBound && deleteOnClose.get()) {
254       InetAddress addr = getLocalAddress();
255       if (AFInetAddress.isSupportedAddress(addr, addressFamily())) {
256         try {
257           AFSocketAddress socketAddress = AFSocketAddress.unwrap(addr, 0, addressFamily());
258           if (socketAddress != null && socketAddress.hasFilename()) {
259             if (!socketAddress.getFile().delete()) {
260               // ignore
261             }
262           }
263         } catch (IOException e) {
264           // ignore
265         }
266       }
267     }
268     super.close();
269   }
270 
271   @Override
272   @SuppressWarnings("PMD.CognitiveComplexity")
273   public final synchronized void bind(SocketAddress addr) throws SocketException {
274     boolean isBound = isBound();
275     if (isBound) {
276       if (addr == AFSocketAddress.INTERNAL_DUMMY_BIND) { // NOPMD
277         return;
278       }
279       // getAFImpl().bind(null); // try unbind (may not succeed)
280     }
281     if (isClosed()) {
282       throw new SocketException("Socket is closed");
283     }
284     if (!isBound) {
285       try {
286         super.bind(AFSocketAddress.INTERNAL_DUMMY_BIND);
287       } catch (AlreadyBoundException e) {
288         // ignore
289       } catch (SocketException e) {
290         String message = e.getMessage();
291         if (message != null && message.contains("already bound")) {
292           // ignore (Java 14 or older)
293         } else {
294           throw e;
295         }
296       }
297     }
298 
299     boolean isWildcardBind = WILDCARD_ADDRESS.equals(addr);
300 
301     AFSocketAddress epoint = (addr == null || isWildcardBind) ? null : AFSocketAddress
302         .preprocessSocketAddress(socketAddressClass(), addr, null);
303     if (epoint instanceof SentinelSocketAddress) {
304       return;
305     }
306 
307     try {
308       getAFImpl().bind(epoint);
309     } catch (SocketException e) {
310       if (isWildcardBind) {
311         // permit errors on wildcard bind
312       } else {
313         getAFImpl().close();
314         throw e;
315       }
316     }
317   }
318 
319   @Override
320   public final @Nullable A getLocalSocketAddress() {
321     if (isClosed()) {
322       return null;
323     }
324     if (!isBound()) {
325       return null;
326     }
327     return getAFImpl().getLocalSocketAddress();
328   }
329 
330   /**
331    * Checks if this {@link AFDatagramSocket}'s bound filename should be removed upon
332    * {@link #close()}.
333    *
334    * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract
335    * namespace).
336    *
337    * @return {@code true} if an attempt is made to delete the socket file upon {@link #close()}.
338    */
339   public final boolean isDeleteOnClose() {
340     return deleteOnClose.get();
341   }
342 
343   /**
344    * Enables/disables deleting this {@link AFDatagramSocket}'s bound filename upon {@link #close()}.
345    *
346    * Deletion is not guaranteed, especially when not supported (e.g., addresses in the abstract
347    * namespace).
348    *
349    * @param b Enabled if {@code true}.
350    */
351   public final void setDeleteOnClose(boolean b) {
352     deleteOnClose.set(b);
353   }
354 
355   final AFDatagramSocketImpl<A> getAFImpl() {
356     if (created.compareAndSet(false, true)) {
357       try {
358         getSoTimeout(); // trigger create via java.net.Socket
359       } catch (SocketException e) {
360         // ignore
361       }
362     }
363     return impl;
364   }
365 
366   final AFDatagramSocketImpl<A> getAFImpl(boolean create) {
367     if (create) {
368       return getAFImpl();
369     } else {
370       return impl;
371     }
372   }
373 
374   @Override
375   public final int getAncillaryReceiveBufferSize() {
376     return ancillaryDataSupport.getAncillaryReceiveBufferSize();
377   }
378 
379   @Override
380   public final void setAncillaryReceiveBufferSize(int size) {
381     ancillaryDataSupport.setAncillaryReceiveBufferSize(size);
382   }
383 
384   @Override
385   public final void ensureAncillaryReceiveBufferSize(int minSize) {
386     ancillaryDataSupport.ensureAncillaryReceiveBufferSize(minSize);
387   }
388 
389   @Override
390   public final boolean isClosed() {
391     return super.isClosed() || getAFImpl().isClosed();
392   }
393 
394   @SuppressFBWarnings("EI_EXPOSE_REP")
395   @Override
396   public AFDatagramChannel<A> getChannel() {
397     return channel;
398   }
399 
400   @Override
401   public final FileDescriptor getFileDescriptor() throws IOException {
402     return getAFImpl().getFileDescriptor();
403   }
404 
405   @Override
406   public final void receive(DatagramPacket p) throws IOException {
407     getAFImpl().receive(p);
408   }
409 
410   /**
411    * Returns the address family supported by this implementation.
412    *
413    * @return The family.
414    */
415   protected final AFAddressFamily<A> addressFamily() {
416     return getAFImpl().getAddressFamily();
417   }
418 
419   /**
420    * Returns the internal helper instance for address-specific extensions.
421    *
422    * @return The helper instance.
423    * @throws UnsupportedOperationException if such extensions are not supported for this address
424    *           type.
425    */
426   protected AFSocketImplExtensions<A> getImplExtensions() {
427     return getAFImpl(false).getImplExtensions();
428   }
429 
430   /**
431    * Returns the value of a junixsocket socket option.
432    *
433    * @param <T> The type of the socket option value.
434    * @param name The socket option.
435    * @return The value of the socket option.
436    * @throws IOException on error.
437    */
438   @Override
439   public <T> T getOption(AFSocketOption<T> name) throws IOException {
440     return getAFImpl().getCore().getOption(name);
441   }
442 
443   /**
444    * Sets the value of a socket option.
445    *
446    * @param <T> The type of the socket option value.
447    * @param name The socket option.
448    * @param value The value of the socket option.
449    * @return this DatagramSocket.
450    * @throws IOException on error.
451    */
452   @Override
453   public <T> DatagramSocket setOption(AFSocketOption<T> name, T value) throws IOException {
454     getAFImpl().getCore().setOption(name, value);
455     return this;
456   }
457 
458   /**
459    * Accepts a connection to this socket. Note that 1., the socket must be in {@code listen} state
460    * by calling {@link #bind(SocketAddress)}, followed by {@link #listen(int)}, and 2., the socket
461    * type must allow listen/accept. This is true for {@link AFSocketType#SOCK_SEQPACKET} AF_UNIX
462    * sockets, for example.
463    *
464    * @return The accepted datagram socket.
465    * @throws IOException on error.
466    * @see #listen(int)
467    */
468   public AFDatagramSocket<A> accept() throws IOException {
469     return accept1(true);
470   }
471 
472   /**
473    * Sets this socket into "listen" state, which allows subsequent calls to {@link #accept()}
474    * receive any connection attempt. Note that 1., the socket must be bound to a local address using
475    * {@link #bind(SocketAddress)}, and 2., the socket type must allow listen/accept. This is true
476    * for {@link AFSocketType#SOCK_SEQPACKET} AF_UNIX sockets, for example.
477    *
478    * @param backlog The backlog, or {@code 0} for default.
479    * @throws IOException on error.
480    */
481   public final void listen(int backlog) throws IOException {
482     FileDescriptor fdesc = getAFImpl().getCore().validFdOrException();
483     if (backlog <= 0) {
484       backlog = 50;
485     }
486     NativeUnixSocket.listen(fdesc, backlog);
487   }
488 
489   /**
490    * Returns a new {@link AFDatagramSocket} instance to be used for {@link #accept()}, i.e., no
491    * {@link FileDescriptor} is associated.
492    *
493    * @return The new instance.
494    * @throws IOException on error.
495    */
496   protected abstract AFDatagramSocket<A> newDatagramSocketInstance() throws IOException;
497 
498   // CPD-OFF
499   AFDatagramSocket<A> accept1(boolean throwOnFail) throws IOException {
500     AFDatagramSocket<A> as = newDatagramSocketInstance();
501 
502     boolean success = getAFImpl().accept0(as.getAFImpl(false));
503     if (isClosed()) {
504       // We may have connected to the socket to unblock it
505       throw new SocketClosedException("Socket is closed");
506     }
507 
508     if (!success) {
509       if (throwOnFail) {
510         if (getChannel().isBlocking()) {
511           // unexpected
512           return null;
513         } else {
514           // non-blocking socket, nothing to accept
515           throw new IllegalBlockingModeException();
516         }
517       } else {
518         return null;
519       }
520     }
521 
522     as.getAFImpl(true); // trigger create
523     as.connect(AFSocketAddress.INTERNAL_DUMMY_CONNECT);
524     as.getAFImpl().updatePorts(getAFImpl().getLocalPort1(), getAFImpl().getRemotePort());
525 
526     return as;
527   }
528 
529   @Override
530   public void setShutdownOnClose(boolean enabled) {
531     getAFImpl().getCore().setShutdownOnClose(enabled);
532   }
533 }