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.File;
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.net.InetAddress;
24  import java.net.SocketAddress;
25  import java.net.SocketException;
26  import java.net.URI;
27  import java.nio.ByteBuffer;
28  import java.util.Arrays;
29  import java.util.HashSet;
30  import java.util.Locale;
31  import java.util.Objects;
32  import java.util.Set;
33  import java.util.regex.Matcher;
34  import java.util.regex.Pattern;
35  
36  import org.newsclub.net.unix.pool.ObjectPool.Lease;
37  
38  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
39  
40  /**
41   * An {@link AFSocketAddress} for VSOCK sockets.
42   *
43   * @author Christian Kohlschütter
44   */
45  @SuppressFBWarnings({"REDOS", "USO_UNSAFE_STATIC_METHOD_SYNCHRONIZATION"})
46  public final class AFVSOCKSocketAddress extends AFSocketAddress {
47    private static final long serialVersionUID = 1L; // do not change!
48  
49    private static final Pattern PAT_VSOCK_URI_HOST_AND_PORT = Pattern.compile(
50        "^(?<port>any|[0-9a-fx\\-]+)(\\.(?<cid>any|hypervisor|local|host|[0-9a-fx\\-]+))?(?:\\:(?<javaPort>[0-9]+))?$");
51  
52    private static AFAddressFamily<AFVSOCKSocketAddress> afVsock;
53  
54    /**
55     * "Any address for binding".
56     */
57    public static final int VMADDR_CID_ANY = -1;
58  
59    /**
60     * Reserved for services built into the hypervisor.
61     */
62    public static final int VMADDR_CID_HYPERVISOR = 0;
63  
64    /**
65     * The well-known address for local communication (loopback).
66     */
67    public static final int VMADDR_CID_LOCAL = 1;
68  
69    /**
70     * The well-known address of the host.
71     */
72    public static final int VMADDR_CID_HOST = 2;
73  
74    /**
75     * Any port number for binding.
76     */
77    public static final int VMADDR_PORT_ANY = -1;
78  
79    private AFVSOCKSocketAddress(int port, final byte[] socketAddress,
80        Lease<ByteBuffer> nativeAddress) throws SocketException {
81      super(port, socketAddress, nativeAddress, addressFamily());
82    }
83  
84    private static AFVSOCKSocketAddress newAFSocketAddress(int port, final byte[] socketAddress,
85        Lease<ByteBuffer> nativeAddress) throws SocketException {
86      return newDeserializedAFSocketAddress(port, socketAddress, nativeAddress, addressFamily(),
87          AFVSOCKSocketAddress::new);
88    }
89  
90    /**
91     * Returns an {@link AFVSOCKSocketAddress} that refers to a given VSOCK port and CID; the "java
92     * port" is set to -1.
93     *
94     * @param port The VSOCK port
95     * @param cid The CID.
96     * @return A corresponding {@link AFVSOCKSocketAddress} instance.
97     * @throws SocketException if the operation fails.
98     */
99    public static AFVSOCKSocketAddress ofPortAndCID(int port, int cid) throws SocketException {
100     return ofPortAndCID(-1, port, cid);
101   }
102 
103   /**
104    * Returns an {@link AFVSOCKSocketAddress} that refers to a given VSOCK port on the hypervisor;
105    * the "java port" is set to -1.
106    *
107    * @param port The VSOCK port
108    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
109    * @throws SocketException if the operation fails.
110    */
111   public static AFVSOCKSocketAddress ofHypervisorPort(int port) throws SocketException {
112     return ofPortAndCID(port, VMADDR_CID_HYPERVISOR);
113   }
114 
115   /**
116    * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
117    * port on the hypervisor; the "java port" is set to -1.
118    *
119    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
120    * @throws SocketException if the operation fails.
121    */
122   public static AFVSOCKSocketAddress ofAnyHypervisorPort() throws SocketException {
123     return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_HYPERVISOR);
124   }
125 
126   /**
127    * Returns an {@link AFVSOCKSocketAddress} that refers to the given port with the local/loopback
128    * CID; the "java port" is set to -1.
129    *
130    * @param port The VSOCK port.
131    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
132    * @throws SocketException if the operation fails.
133    */
134   public static AFVSOCKSocketAddress ofLocalPort(int port) throws SocketException {
135     return ofPortAndCID(port, VMADDR_CID_LOCAL);
136   }
137 
138   /**
139    * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
140    * port with the local/loopback CID; the "java port" is set to -1.
141    *
142    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
143    * @throws SocketException if the operation fails.
144    */
145   public static AFVSOCKSocketAddress ofAnyLocalPort() throws SocketException {
146     return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_LOCAL);
147   }
148 
149   /**
150    * Returns an {@link AFVSOCKSocketAddress} that refers to a given VSOCK port on the host; the
151    * "java port" is set to -1.
152    *
153    * @param port The VSOCK port
154    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
155    * @throws SocketException if the operation fails.
156    */
157   public static AFVSOCKSocketAddress ofHostPort(int port) throws SocketException {
158     return ofPortAndCID(port, VMADDR_CID_HOST);
159   }
160 
161   /**
162    * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
163    * port on the host; the "java port" is set to -1.
164    *
165    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
166    * @throws SocketException if the operation fails.
167    */
168   public static AFVSOCKSocketAddress ofAnyHostPort() throws SocketException {
169     return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_HOST);
170   }
171 
172   /**
173    * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
174    * port and CID; the "java port" is set to -1.
175    *
176    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
177    * @throws SocketException if the operation fails.
178    */
179   public static AFVSOCKSocketAddress ofAnyPort() throws SocketException {
180     return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_ANY);
181   }
182 
183   /**
184    * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to the
185    * given port with "any CID"; the "java port" is set to -1.
186    *
187    * @param port The VSOCK port.
188    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
189    * @throws SocketException if the operation fails.
190    */
191   public static AFVSOCKSocketAddress ofPortWithAnyCID(int port) throws SocketException {
192     return ofPortAndCID(port, VMADDR_CID_ANY);
193   }
194 
195   /**
196    * Returns an {@link AFVSOCKSocketAddress} that refers to a given port and CID.
197    *
198    * @param javaPort The Java port number.
199    * @param vsockPort The vsock port.
200    * @param cid The CID.
201    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
202    * @throws SocketException if the operation fails.
203    */
204   public static AFVSOCKSocketAddress ofPortAndCID(int javaPort, int vsockPort, int cid)
205       throws SocketException {
206     return resolveAddress(toBytes(vsockPort, cid), javaPort, addressFamily());
207   }
208 
209   /**
210    * Returns an {@link AFVSOCKSocketAddress} given a special {@link InetAddress} that encodes the
211    * byte sequence of an AF_VSOCK socket address, like those returned by {@link #wrapAddress()}.
212    *
213    * @param address The "special" {@link InetAddress}.
214    * @param port The port (use 0 for "none").
215    * @return The {@link AFVSOCKSocketAddress} instance.
216    * @throws SocketException if the operation fails, for example when an unsupported address is
217    *           specified.
218    */
219   public static AFVSOCKSocketAddress unwrap(InetAddress address, int port) throws SocketException {
220     return AFSocketAddress.unwrap(address, port, addressFamily());
221   }
222 
223   /**
224    * Returns an {@link AFVSOCKSocketAddress} given a special {@link InetAddress} hostname that
225    * encodes the byte sequence of an AF_VSOCK socket address, like those returned by
226    * {@link #wrapAddress()}.
227    *
228    * @param hostname The "special" hostname, as provided by {@link InetAddress#getHostName()}.
229    * @param port The port (use 0 for "none").
230    * @return The {@link AFVSOCKSocketAddress} instance.
231    * @throws SocketException if the operation fails, for example when an unsupported address is
232    *           specified.
233    */
234   public static AFVSOCKSocketAddress unwrap(String hostname, int port) throws SocketException {
235     return AFSocketAddress.unwrap(hostname, port, addressFamily());
236   }
237 
238   /**
239    * Returns an {@link AFVSOCKSocketAddress} given a generic {@link SocketAddress}.
240    *
241    * @param address The address to unwrap.
242    * @return The {@link AFVSOCKSocketAddress} instance.
243    * @throws SocketException if the operation fails, for example when an unsupported address is
244    *           specified.
245    */
246   public static AFVSOCKSocketAddress unwrap(SocketAddress address) throws SocketException {
247     Objects.requireNonNull(address);
248     if (!isSupportedAddress(address)) {
249       throw new SocketException("Unsupported address");
250     }
251     return (AFVSOCKSocketAddress) address;
252   }
253 
254   /**
255    * Returns the "VSOCK port" part of this address.
256    *
257    * @return The VSOCK port identifier
258    * @see #getPort()
259    */
260   public int getVSOCKPort() {
261     ByteBuffer bb = ByteBuffer.wrap(getBytes());
262     return bb.getInt(1 * 4);
263   }
264 
265   /**
266    * Returns the "VSOCK CID" part of this address.
267    *
268    * @return The VSOCK CID identifier.
269    */
270   public int getVSOCKCID() {
271     ByteBuffer bb = ByteBuffer.wrap(getBytes());
272     return bb.getInt(2 * 4);
273   }
274 
275   /**
276    * Returns the "VSOCK reserved1" part of this address.
277    *
278    * @return The "reserved1" identifier, which should be 0.
279    */
280   public int getVSOCKReserved1() {
281     ByteBuffer bb = ByteBuffer.wrap(getBytes());
282     return bb.getInt(0 * 4);
283   }
284 
285   @Override
286   public String toString() {
287     int port = getPort();
288 
289     byte[] bytes = getBytes();
290     if (bytes.length != (3 * 4)) {
291       return getClass().getName() + "[" + (port == 0 ? "" : "port=" + port) + ";UNKNOWN" + "]";
292     }
293 
294     ByteBuffer bb = ByteBuffer.wrap(bytes);
295     int reserved1 = bb.getInt();
296     int vsockPort = bb.getInt();
297     int cid = bb.getInt();
298 
299     String vsockPortString;
300     if (vsockPort >= -1) {
301       vsockPortString = Integer.toString(vsockPort);
302     } else {
303       vsockPortString = String.format(Locale.ENGLISH, "0x%08x", vsockPort);
304     }
305 
306     String typeString = (reserved1 == 0 ? "" : "reserved1=" + reserved1 + ";") + "vsockPort="
307         + vsockPortString + ";cid=" + cid;
308 
309     return getClass().getName() + "[" + (port == 0 ? "" : "port=" + port + ";") + typeString + "]";
310   }
311 
312   @Override
313   public boolean hasFilename() {
314     return false;
315   }
316 
317   @Override
318   public File getFile() throws FileNotFoundException {
319     throw new FileNotFoundException("no file");
320   }
321 
322   /**
323    * Checks if an {@link InetAddress} can be unwrapped to an {@link AFVSOCKSocketAddress}.
324    *
325    * @param addr The instance to check.
326    * @return {@code true} if so.
327    * @see #wrapAddress()
328    * @see #unwrap(InetAddress, int)
329    */
330   public static boolean isSupportedAddress(InetAddress addr) {
331     return AFSocketAddress.isSupportedAddress(addr, addressFamily());
332   }
333 
334   /**
335    * Checks if a {@link SocketAddress} can be unwrapped to an {@link AFVSOCKSocketAddress}.
336    *
337    * @param addr The instance to check.
338    * @return {@code true} if so.
339    * @see #unwrap(InetAddress, int)
340    */
341   public static boolean isSupportedAddress(SocketAddress addr) {
342     return (addr instanceof AFVSOCKSocketAddress);
343   }
344 
345   @SuppressWarnings("cast")
346   private static byte[] toBytes(int port, int cid) {
347     ByteBuffer bb = ByteBuffer.allocate(3 * 4);
348     bb.putInt(0); // svm_reserved1
349     bb.putInt(port); // svm_port
350     bb.putInt(cid); // svm_cid
351     return (byte[]) bb.flip().array();
352   }
353 
354   /**
355    * Returns the corresponding {@link AFAddressFamily}.
356    *
357    * @return The address family instance.
358    */
359   @SuppressWarnings("null")
360   public static synchronized AFAddressFamily<AFVSOCKSocketAddress> addressFamily() {
361     if (afVsock == null) {
362       afVsock = AFAddressFamily.registerAddressFamily("vsock", //
363           AFVSOCKSocketAddress.class, new AFSocketAddressConfig<AFVSOCKSocketAddress>() {
364 
365             private final AFSocketAddressConstructor<AFVSOCKSocketAddress> addrConstr =
366                 isUseDeserializationForInit() ? AFVSOCKSocketAddress::newAFSocketAddress
367                     : AFVSOCKSocketAddress::new;
368 
369             @Override
370             protected AFVSOCKSocketAddress parseURI(URI u, int port) throws SocketException {
371               return AFVSOCKSocketAddress.of(u, port);
372             }
373 
374             @Override
375             protected AFSocketAddressConstructor<AFVSOCKSocketAddress> addressConstructor() {
376               return addrConstr;
377             }
378 
379             @Override
380             protected String selectorProviderClassname() {
381               return "org.newsclub.net.unix.vsock.AFVSOCKSelectorProvider";
382             }
383 
384             @Override
385             protected Set<String> uriSchemes() {
386               return new HashSet<>(Arrays.asList("vsock", "http+vsock", "https+vsock"));
387             }
388           });
389       try {
390         Class.forName("org.newsclub.net.unix.vsock.AFVSOCKSelectorProvider");
391       } catch (ClassNotFoundException e) {
392         // ignore
393       }
394     }
395     return afVsock;
396   }
397 
398   /**
399    * Returns an {@link AFVSOCKSocketAddress} for the given URI, if possible.
400    *
401    * @param uri The URI.
402    * @return The address.
403    * @throws SocketException if the operation fails.
404    */
405   @SuppressWarnings("PMD.ShortMethodName")
406   public static AFVSOCKSocketAddress of(URI uri) throws SocketException {
407     return of(uri, -1);
408   }
409 
410   /**
411    * Returns an {@link AFVSOCKSocketAddress} for the given URI, if possible.
412    *
413    * @param uri The URI.
414    * @param overridePort The port to forcibly use, or {@code -1} for "don't override".
415    * @return The address.
416    * @throws SocketException if the operation fails.
417    */
418   @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.ShortMethodName"})
419   public static AFVSOCKSocketAddress of(URI uri, int overridePort) throws SocketException {
420     switch (uri.getScheme()) {
421       case "vsock":
422       case "http+vsock":
423       case "https+vsock":
424         break;
425       default:
426         throw new SocketException("Unsupported URI scheme: " + uri.getScheme());
427     }
428 
429     String host = uri.getHost();
430     if (host == null) {
431       host = uri.getAuthority();
432       if (host != null) {
433         int at = host.indexOf('@');
434         if (at >= 0) {
435           host = host.substring(at + 1);
436         }
437       }
438     }
439     if (host == null) {
440       throw new SocketException("Cannot get hostname from URI: " + uri);
441     }
442 
443     try {
444       Matcher m = PAT_VSOCK_URI_HOST_AND_PORT.matcher(host);
445       if (!m.matches()) {
446         throw new SocketException("Invalid VSOCK URI: " + uri);
447       }
448 
449       String cidStr = m.group("cid");
450       String portStr = m.group("port");
451       String javaPortStr = m.group("javaPort");
452 
453       int cid;
454       switch (cidStr == null ? "" : cidStr) {
455         case "":
456         case "any":
457           cid = VMADDR_CID_ANY;
458           break;
459         case "hypervisor":
460           cid = VMADDR_CID_HYPERVISOR;
461           break;
462         case "local":
463           cid = VMADDR_CID_LOCAL;
464           break;
465         case "host":
466           cid = VMADDR_CID_HOST;
467           break;
468         default:
469           cid = parseInt(cidStr);
470           break;
471       }
472 
473       int port;
474       switch (portStr == null ? "" : portStr) {
475         case "any":
476         case "":
477           port = VMADDR_PORT_ANY;
478           break;
479         default:
480           port = parseInt(portStr);
481           break;
482       }
483 
484       int javaPort = overridePort != -1 ? overridePort : uri.getPort();
485       if (javaPortStr != null && !javaPortStr.isEmpty()) {
486         javaPort = parseInt(javaPortStr);
487       }
488 
489       return ofPortAndCID(javaPort, port, cid);
490     } catch (IllegalArgumentException e) {
491       throw (SocketException) new SocketException("Invalid VSOCK URI: " + uri).initCause(e);
492     }
493   }
494 
495   @Override
496   public URI toURI(String scheme, URI template) throws IOException {
497     switch (scheme) {
498       case "vsock":
499       case "http+vsock":
500       case "https+vsock":
501         break;
502       default:
503         return super.toURI(scheme, template);
504     }
505 
506     byte[] bytes = getBytes();
507     if (bytes.length != (3 * 4)) {
508       return super.toURI(scheme, template);
509     }
510 
511     StringBuilder sb = new StringBuilder();
512 
513     String portStr;
514     int port;
515     switch ((port = getVSOCKPort())) {
516       case VMADDR_PORT_ANY:
517         portStr = "any";
518         break;
519       default:
520         portStr = toUnsignedString(port);
521         break;
522     }
523 
524     sb.append(portStr);
525     sb.append('.');
526     String cidStr;
527     int cid;
528     switch ((cid = getVSOCKCID())) {
529       case VMADDR_CID_ANY:
530         cidStr = "any";
531         break;
532       case VMADDR_CID_HYPERVISOR:
533         cidStr = "hypervisor";
534         break;
535       case VMADDR_CID_LOCAL:
536         cidStr = "local";
537         break;
538       case VMADDR_CID_HOST:
539         cidStr = "host";
540         break;
541       default:
542         cidStr = toUnsignedString(cid);
543         break;
544     }
545 
546     sb.append(cidStr);
547 
548     return new HostAndPort(sb.toString(), getPort()).toURI(scheme, template);
549   }
550 
551   private static int parseInt(String v) {
552     if (v.startsWith("0x")) {
553       return parseUnsignedInt(v.substring(2), 16);
554     } else if (v.startsWith("-")) {
555       return Integer.parseInt(v);
556     } else {
557       return parseUnsignedInt(v, 10);
558     }
559   }
560 
561   /**
562    * Checks if the given address could cover another address.
563    *
564    * By default, this is only true if both addresses are regarded equal using
565    * {@link #equals(Object)}.
566    *
567    * However, implementations may support "wildcard" addresses, and this method would compare a
568    * wildcard address against some non-wildcard address, for example.
569    *
570    * @param covered The other address that could be covered by this address.
571    * @return {@code true} if the other address could be covered.
572    */
573   @Override
574   public boolean covers(AFSocketAddress covered) {
575     if (super.covers(covered)) {
576       return true;
577     } else if (covered instanceof AFVSOCKSocketAddress) {
578       AFVSOCKSocketAddress other = (AFVSOCKSocketAddress) covered;
579 
580       if (getVSOCKCID() == VMADDR_CID_ANY) {
581         if (getVSOCKPort() == VMADDR_PORT_ANY) {
582           return true;
583         } else {
584           return getVSOCKPort() == other.getVSOCKPort();
585         }
586       } else if (getVSOCKPort() == VMADDR_PORT_ANY) {
587         return getVSOCKCID() == other.getVSOCKCID();
588       }
589     }
590 
591     return equals(covered);
592   }
593 }