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