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