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.DataInputStream;
21  import java.io.DataOutputStream;
22  import java.io.FileDescriptor;
23  import java.io.IOException;
24  import java.net.Socket;
25  import java.net.SocketException;
26  import java.nio.ByteBuffer;
27  import java.nio.ByteOrder;
28  import java.nio.channels.SocketChannel;
29  import java.util.concurrent.atomic.AtomicBoolean;
30  
31  import org.eclipse.jdt.annotation.NonNull;
32  
33  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
34  
35  /**
36   * Implementation of an AF_UNIX domain socket.
37   *
38   * @author Christian Kohlschütter
39   */
40  public final class AFUNIXSocket extends AFSocket<AFUNIXSocketAddress> implements
41      AFUNIXSocketExtensions {
42    private static final Constructor<AFUNIXSocketAddress> CONSTRUCTOR_STRICT =
43        new Constructor<AFUNIXSocketAddress>() {
44  
45          @Override
46          public @NonNull AFSocket<AFUNIXSocketAddress> newInstance(FileDescriptor fdObj,
47              AFSocketFactory<AFUNIXSocketAddress> factory) throws SocketException {
48            return new AFUNIXSocket(new AFUNIXSocketImpl(fdObj), factory);
49          }
50        };
51  
52    private AFUNIXSocket(AFSocketImpl<AFUNIXSocketAddress> impl,
53        AFSocketFactory<AFUNIXSocketAddress> factory) throws SocketException {
54      super(impl, factory);
55    }
56  
57    AFUNIXSocket(FileDescriptor fd, AFSocketFactory<AFUNIXSocketAddress> factory)
58        throws SocketException {
59      this(new AFUNIXSocketImpl.Lenient(fd), factory);
60    }
61  
62    @Override
63    protected AFUNIXSocketChannel newChannel() {
64      return new AFUNIXSocketChannel(this);
65    }
66  
67    /**
68     * Creates a new, unbound {@link AFSocket}.
69     *
70     * This "default" implementation is a bit "lenient" with respect to the specification.
71     *
72     * In particular, we ignore calls to {@link Socket#getTcpNoDelay()} and
73     * {@link Socket#setTcpNoDelay(boolean)}.
74     *
75     * @return A new, unbound socket.
76     * @throws IOException if the operation fails.
77     */
78    public static AFUNIXSocket newInstance() throws IOException {
79      return (AFUNIXSocket) AFSocket.newInstance(AFUNIXSocket::new, (AFUNIXSocketFactory) null);
80    }
81  
82    static AFUNIXSocket newLenientInstance() throws IOException {
83      return newInstance();
84    }
85  
86    static AFUNIXSocket newInstance(FileDescriptor fdObj, int localPort, int remotePort)
87        throws IOException {
88      return (AFUNIXSocket) AFSocket.newInstance(AFUNIXSocket::new, (AFUNIXSocketFactory) null, fdObj,
89          localPort, remotePort);
90    }
91  
92    static AFUNIXSocket newInstance(AFUNIXSocketFactory factory) throws SocketException {
93      return (AFUNIXSocket) AFSocket.newInstance(AFUNIXSocket::new, factory);
94    }
95  
96    /**
97     * Creates a new, unbound, "strict" {@link AFSocket}.
98     *
99     * This call uses an implementation that tries to be closer to the specification than
100    * {@link #newInstance()}, at least for some cases.
101    *
102    * @return A new, unbound socket.
103    * @throws IOException if the operation fails.
104    */
105   public static AFUNIXSocket newStrictInstance() throws IOException {
106     return (AFUNIXSocket) AFSocket.newInstance(CONSTRUCTOR_STRICT, (AFUNIXSocketFactory) null);
107   }
108 
109   /**
110    * Creates a new {@link AFSocket} and connects it to the given {@link AFUNIXSocketAddress}.
111    *
112    * @param addr The address to connect to.
113    * @return A new, connected socket.
114    * @throws IOException if the operation fails.
115    */
116   public static AFUNIXSocket connectTo(AFUNIXSocketAddress addr) throws IOException {
117     return (AFUNIXSocket) AFSocket.connectTo(AFUNIXSocket::new, addr);
118   }
119 
120   @Override
121   public AFUNIXSocketChannel getChannel() {
122     return (AFUNIXSocketChannel) super.getChannel();
123   }
124 
125   @Override
126   public AFUNIXSocketCredentials getPeerCredentials() throws IOException {
127     if (isClosed() || !isConnected()) {
128       throw new SocketException("Not connected");
129     }
130     return ((AFUNIXSocketImpl) getAFImpl()).getPeerCredentials();
131   }
132 
133   @Override
134   public FileDescriptor[] getReceivedFileDescriptors() throws IOException {
135     return ((AFUNIXSocketImpl) getAFImpl()).getReceivedFileDescriptors();
136   }
137 
138   @Override
139   public void clearReceivedFileDescriptors() {
140     ((AFUNIXSocketImpl) getAFImpl()).clearReceivedFileDescriptors();
141   }
142 
143   @Override
144   public void setOutboundFileDescriptors(FileDescriptor... fdescs) throws IOException {
145     if (fdescs != null && fdescs.length > 0 && !isConnected()) {
146       throw new SocketException("Not connected");
147     }
148     ((AFUNIXSocketImpl) getAFImpl()).setOutboundFileDescriptors(fdescs);
149   }
150 
151   @Override
152   public boolean hasOutboundFileDescriptors() {
153     return ((AFUNIXSocketImpl) getAFImpl()).hasOutboundFileDescriptors();
154   }
155 
156   /**
157    * Returns <code>true</code> iff {@link AFUNIXSocket}s are supported by the current Java VM.
158    *
159    * To support {@link AFSocket}s, a custom JNI library must be loaded that is supplied with
160    * <em>junixsocket</em>, and the system must support AF_UNIX sockets.
161    *
162    * This call is equivalent to checking {@link AFSocket#isSupported()} and
163    * {@link AFSocket#supports(AFSocketCapability)} with
164    * {@link AFSocketCapability#CAPABILITY_UNIX_DOMAIN}.
165    *
166    * @return {@code true} iff supported.
167    */
168   @SuppressFBWarnings("HSM_HIDING_METHOD")
169   public static boolean isSupported() {
170     return AFSocket.isSupported() && AFSocket.supports(AFSocketCapability.CAPABILITY_UNIX_DOMAIN);
171   }
172 
173   /**
174    * Very basic self-test function.
175    *
176    * Prints "supported" and "capabilities" status to System.out.
177    *
178    * @param args ignored.
179    */
180   public static void main(String[] args) {
181     // If you want to run this directly from within Eclipse, see
182     // org.newsclub.net.unix.domain.SocketTest#testMain.
183     System.out.print(AFUNIXSocket.class.getName() + ".isSupported(): ");
184     System.out.flush();
185     System.out.println(AFUNIXSocket.isSupported());
186 
187     for (AFSocketCapability cap : AFSocketCapability.values()) {
188       System.out.print(cap + ": ");
189       System.out.flush();
190       System.out.println(AFSocket.supports(cap));
191     }
192     System.out.println();
193     if (AFSocket.supports(AFSocketCapability.CAPABILITY_UNIX_DOMAIN)) {
194       System.out.println("Starting mini selftest...");
195       miniSelftest();
196     } else {
197       System.out.println(
198           "Skipping mini selftest; AFSocketCapability.CAPABILITY_UNIX_DOMAIN is missing");
199     }
200   }
201 
202   private static void miniSelftest() {
203     AtomicBoolean success = new AtomicBoolean(true);
204     try {
205       AFUNIXSocketAddress addr = AFUNIXSocketAddress.ofNewTempFile();
206       System.out.println("Using temporary address: " + addr);
207       try (AFUNIXServerSocket server = addr.newBoundServerSocket()) {
208         Thread t = new Thread(() -> {
209           try {
210             try (AFUNIXSocket client = server.accept()) { // NOPMD.UseTryWithResources
211               System.out.println("Server accepted client connection");
212               try (SocketChannel chann = client.getChannel()) {
213                 ByteBuffer bb = ByteBuffer.allocate(64).order(ByteOrder.BIG_ENDIAN);
214 
215                 int numRead = 0;
216                 while (bb.position() != 4 && numRead != -1) {
217                   numRead = chann.read(bb);
218                 }
219                 if (bb.position() != 4) {
220                   throw new IOException("Unexpected number of bytes read: " + bb.position());
221                 }
222                 bb.flip();
223                 int v;
224                 if ((v = bb.getInt()) != 0xABCDEF12) {
225                   throw new IOException("Received unexpected data from client: 0x" + Integer
226                       .toHexString(v));
227                 }
228                 bb.clear();
229                 bb.putLong(0x00112233456789L);
230                 bb.flip();
231                 chann.write(bb);
232               }
233             } finally {
234               server.close();
235             }
236           } catch (Exception e) { // NOPMD
237             success.set(false);
238             e.printStackTrace();
239           }
240         });
241         t.start();
242 
243         try (AFUNIXSocket socket = addr.newConnectedSocket();
244             DataInputStream in = new DataInputStream(socket.getInputStream());
245             DataOutputStream out = new DataOutputStream(socket.getOutputStream());) {
246           out.writeInt(0xABCDEF12);
247           out.flush();
248           long v = in.readLong();
249           if (v != 0x00112233456789L) {
250             throw new IOException("Received unexpected data from server: 0x" + Long.toHexString(v));
251           }
252         }
253         System.out.println("Data exchange succeeded");
254       }
255     } catch (Exception e) { // NOPMD
256       success.set(false);
257       e.printStackTrace();
258       return;
259     } finally {
260       System.out.println("mini selftest " + (success.get() ? "passed" : "failed"));
261     }
262   }
263 }