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.vsock;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.net.SocketException;
24  import java.nio.charset.StandardCharsets;
25  import java.util.regex.Matcher;
26  import java.util.regex.Pattern;
27  
28  import org.newsclub.net.unix.AFSocket;
29  import org.newsclub.net.unix.AFSocketAddress;
30  import org.newsclub.net.unix.AFSocketConnector;
31  import org.newsclub.net.unix.AFUNIXSocket;
32  import org.newsclub.net.unix.AFUNIXSocketAddress;
33  import org.newsclub.net.unix.AFVSOCKSocketAddress;
34  import org.newsclub.net.unix.AddressUnavailableSocketException;
35  
36  /**
37   * Provides access to AF_VSOCK connections that aren't directly accessible but exposed via a
38   * proxying/multiplexing Unix domain socket.
39   *
40   * @author Christian Kohlschütter
41   * @see #openFirecrackerStyleConnector(AFUNIXSocketAddress, int)
42   * @see #openDirectConnector()
43   */
44  public final class AFVSOCKProxySocketConnector implements
45      AFSocketConnector<AFVSOCKSocketAddress, AFSocketAddress> {
46    private static final AFSocketConnector<AFVSOCKSocketAddress, AFSocketAddress> DIRECT_CONNECTOR =
47        new AFSocketConnector<AFVSOCKSocketAddress, AFSocketAddress>() {
48  
49          @Override
50          public AFSocket<? extends AFSocketAddress> connect(AFVSOCKSocketAddress addr)
51              throws IOException {
52            return addr.newConnectedSocket();
53          }
54        };
55  
56    private static final Pattern PAT_OK = Pattern.compile("OK ([0-9]+)");
57    private static final byte[] OK = {'O', 'K', ' '};
58    private final AFUNIXSocketAddress connectorAddress;
59    private final int allowedCID;
60  
61    private AFVSOCKProxySocketConnector(AFUNIXSocketAddress connectorAddress, int allowedCID) {
62      this.connectorAddress = connectorAddress;
63      this.allowedCID = allowedCID;
64    }
65  
66    /**
67     * Returns an instance that is configured to support
68     * [Firecracker-style](https://github.com/firecracker-microvm/firecracker/blob/main/docs/vsock.md)
69     * Unix domain sockets.
70     *
71     * @param connectorAddress The unix socket address pointing at the Firecracker-style multiplexing
72     *          domain socket.
73     * @param allowedCID The permitted CID, or {@link AFVSOCKSocketAddress#VMADDR_CID_ANY} for "any".
74     * @return The instance.
75     */
76    public static AFSocketConnector<AFVSOCKSocketAddress, AFSocketAddress> openFirecrackerStyleConnector(
77        AFUNIXSocketAddress connectorAddress, int allowedCID) {
78      return new AFVSOCKProxySocketConnector(connectorAddress, allowedCID);
79    }
80  
81    /**
82     * Returns an instance that is configured to connect directly to the given address.
83     *
84     * @return The direct instance.
85     */
86    public static AFSocketConnector<AFVSOCKSocketAddress, AFSocketAddress> openDirectConnector() {
87      return DIRECT_CONNECTOR;
88    }
89  
90    /**
91     * Connects to the given AF_VSOCK address.
92     *
93     * @param vsockAddress The address to connect to.
94     * @return The connected socket.
95     * @throws IOException on error.
96     * @throws AddressUnavailableSocketException if the CID is not covered by this connector.
97     */
98    @Override
99    @SuppressWarnings("Finally" /* errorprone */)
100   public AFSocket<?> connect(AFVSOCKSocketAddress vsockAddress) throws IOException {
101     int cid = vsockAddress.getVSOCKCID();
102     if (cid != allowedCID && cid != AFVSOCKSocketAddress.VMADDR_CID_ANY
103         && allowedCID != AFVSOCKSocketAddress.VMADDR_CID_ANY) {
104       throw new AddressUnavailableSocketException("Connector does not cover CID " + cid);
105     }
106 
107     @SuppressWarnings("resource")
108     AFUNIXSocket sock = connectorAddress.newConnectedSocket();
109     InputStream in = sock.getInputStream();
110     OutputStream out = sock.getOutputStream();
111 
112     boolean success = false;
113 
114     try { // NOPMD.UseTryWithResources
115       String connectLine = "CONNECT " + vsockAddress.getVSOCKPort() + "\n";
116       out.write(connectLine.getBytes(StandardCharsets.ISO_8859_1));
117 
118       byte[] buf = new byte[16];
119       int b;
120       int i = 0;
121       while ((b = in.read()) != -1 && b != '\n' && i < buf.length) {
122         buf[i] = (byte) b;
123         if (i < 3) {
124           if (OK[i] != b) {
125             break;
126           }
127         }
128         i++;
129       }
130       if (b == '\n' && i > 3) {
131         Matcher m = PAT_OK.matcher(new String(buf, 0, i, StandardCharsets.ISO_8859_1));
132         if (m.matches()) {
133           /* int hostPort = */ Integer.parseInt(m.group(1));
134           success = true;
135         }
136       }
137     } finally { // NOPMD.DoNotThrowExceptionInFinally
138       if (!success) {
139         sock.close();
140         throw new SocketException("Unexpected response from proxy socket");
141       }
142     }
143 
144     return sock;
145   }
146 }