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  import org.newsclub.net.unix.pool.ObjectPool.Lease;
37  
38  /**
39   * An {@link AFSocketAddress} for VSOCK sockets.
40   *
41   * @author Christian Kohlschütter
42   */
43  public final class AFVSOCKSocketAddress extends AFSocketAddress {
44    private static final long serialVersionUID = 1L; // do not change!
45  
46    private static final Pattern PAT_VSOCK_URI_HOST_AND_PORT = Pattern.compile(
47        "^(?<port>any|[0-9a-fx\\-]+)(\\.(?<cid>any|hypervisor|local|host|[0-9a-fx\\-]+))?(?:\\:(?<javaPort>[0-9]+))?$");
48  
49    private static AFAddressFamily<AFVSOCKSocketAddress> afVsock;
50  
51    /**
52     * "Any address for binding".
53     */
54    public static final int VMADDR_CID_ANY = -1;
55  
56    /**
57     * Reserved for services built into the hypervisor.
58     */
59    public static final int VMADDR_CID_HYPERVISOR = 0;
60  
61    /**
62     * The well-known address for local communication (loopback).
63     */
64    public static final int VMADDR_CID_LOCAL = 1;
65  
66    /**
67     * The well-known address of the host.
68     */
69    public static final int VMADDR_CID_HOST = 2;
70  
71    /**
72     * Any port number for binding.
73     */
74    public static final int VMADDR_PORT_ANY = -1;
75  
76    private AFVSOCKSocketAddress(int port, final byte[] socketAddress,
77        Lease<ByteBuffer> nativeAddress) throws SocketException {
78      super(port, socketAddress, nativeAddress, addressFamily());
79    }
80  
81    private static AFVSOCKSocketAddress newAFSocketAddress(int port, final byte[] socketAddress,
82        Lease<ByteBuffer> nativeAddress) throws SocketException {
83      return newDeserializedAFSocketAddress(port, socketAddress, nativeAddress, addressFamily(),
84          AFVSOCKSocketAddress::new);
85    }
86  
87    /**
88     * Returns an {@link AFVSOCKSocketAddress} that refers to a given VSOCK port and CID; the "java
89     * port" is set to -1.
90     *
91     * @param port The VSOCK port
92     * @param cid The CID.
93     * @return A corresponding {@link AFVSOCKSocketAddress} instance.
94     * @throws SocketException if the operation fails.
95     */
96    public static AFVSOCKSocketAddress ofPortAndCID(int port, int cid) throws SocketException {
97      return ofPortAndCID(-1, port, cid);
98    }
99  
100   /**
101    * Returns an {@link AFVSOCKSocketAddress} that refers to a given VSOCK port on the hypervisor;
102    * the "java port" is set to -1.
103    *
104    * @param port The VSOCK port
105    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
106    * @throws SocketException if the operation fails.
107    */
108   public static AFVSOCKSocketAddress ofHypervisorPort(int port) throws SocketException {
109     return ofPortAndCID(port, VMADDR_CID_HYPERVISOR);
110   }
111 
112   /**
113    * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
114    * port on the hypervisor; the "java port" is set to -1.
115    *
116    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
117    * @throws SocketException if the operation fails.
118    */
119   public static AFVSOCKSocketAddress ofAnyHypervisorPort() throws SocketException {
120     return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_HYPERVISOR);
121   }
122 
123   /**
124    * Returns an {@link AFVSOCKSocketAddress} that refers to the given port with the local/loopback
125    * CID; the "java port" is set to -1.
126    *
127    * @param port The VSOCK port.
128    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
129    * @throws SocketException if the operation fails.
130    */
131   public static AFVSOCKSocketAddress ofLocalPort(int port) throws SocketException {
132     return ofPortAndCID(port, VMADDR_CID_LOCAL);
133   }
134 
135   /**
136    * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
137    * port with the local/loopback CID; the "java port" is set to -1.
138    *
139    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
140    * @throws SocketException if the operation fails.
141    */
142   public static AFVSOCKSocketAddress ofAnyLocalPort() throws SocketException {
143     return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_LOCAL);
144   }
145 
146   /**
147    * Returns an {@link AFVSOCKSocketAddress} that refers to a given VSOCK port on the host; the
148    * "java port" is set to -1.
149    *
150    * @param port The VSOCK port
151    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
152    * @throws SocketException if the operation fails.
153    */
154   public static AFVSOCKSocketAddress ofHostPort(int port) throws SocketException {
155     return ofPortAndCID(port, VMADDR_CID_HOST);
156   }
157 
158   /**
159    * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
160    * port on the host; the "java port" is set to -1.
161    *
162    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
163    * @throws SocketException if the operation fails.
164    */
165   public static AFVSOCKSocketAddress ofAnyHostPort() throws SocketException {
166     return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_HOST);
167   }
168 
169   /**
170    * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
171    * port and CID; the "java port" is set to -1.
172    *
173    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
174    * @throws SocketException if the operation fails.
175    */
176   public static AFVSOCKSocketAddress ofAnyPort() throws SocketException {
177     return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_ANY);
178   }
179 
180   /**
181    * Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to the
182    * given port with "any CID"; the "java port" is set to -1.
183    *
184    * @param port The VSOCK port.
185    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
186    * @throws SocketException if the operation fails.
187    */
188   public static AFVSOCKSocketAddress ofPortWithAnyCID(int port) throws SocketException {
189     return ofPortAndCID(port, VMADDR_CID_ANY);
190   }
191 
192   /**
193    * Returns an {@link AFVSOCKSocketAddress} that refers to a given port and CID.
194    *
195    * @param javaPort The Java port number.
196    * @param vsockPort The vsock port.
197    * @param cid The CID.
198    * @return A corresponding {@link AFVSOCKSocketAddress} instance.
199    * @throws SocketException if the operation fails.
200    */
201   public static AFVSOCKSocketAddress ofPortAndCID(int javaPort, int vsockPort, int cid)
202       throws SocketException {
203     return resolveAddress(toBytes(vsockPort, cid), javaPort, addressFamily());
204   }
205 
206   /**
207    * Returns an {@link AFVSOCKSocketAddress} given a special {@link InetAddress} that encodes the
208    * byte sequence of an AF_VSOCK socket address, like those returned by {@link #wrapAddress()}.
209    *
210    * @param address The "special" {@link InetAddress}.
211    * @param port The port (use 0 for "none").
212    * @return The {@link AFVSOCKSocketAddress} instance.
213    * @throws SocketException if the operation fails, for example when an unsupported address is
214    *           specified.
215    */
216   public static AFVSOCKSocketAddress unwrap(InetAddress address, int port) throws SocketException {
217     return AFSocketAddress.unwrap(address, port, addressFamily());
218   }
219 
220   /**
221    * Returns an {@link AFVSOCKSocketAddress} given a special {@link InetAddress} hostname that
222    * encodes the byte sequence of an AF_VSOCK socket address, like those returned by
223    * {@link #wrapAddress()}.
224    *
225    * @param hostname The "special" hostname, as provided by {@link InetAddress#getHostName()}.
226    * @param port The port (use 0 for "none").
227    * @return The {@link AFVSOCKSocketAddress} instance.
228    * @throws SocketException if the operation fails, for example when an unsupported address is
229    *           specified.
230    */
231   public static AFVSOCKSocketAddress unwrap(String hostname, int port) throws SocketException {
232     return AFSocketAddress.unwrap(hostname, port, addressFamily());
233   }
234 
235   /**
236    * Returns an {@link AFVSOCKSocketAddress} given a generic {@link SocketAddress}.
237    *
238    * @param address The address to unwrap.
239    * @return The {@link AFVSOCKSocketAddress} instance.
240    * @throws SocketException if the operation fails, for example when an unsupported address is
241    *           specified.
242    */
243   public static AFVSOCKSocketAddress unwrap(SocketAddress address) throws SocketException {
244     Objects.requireNonNull(address);
245     if (!isSupportedAddress(address)) {
246       throw new SocketException("Unsupported address");
247     }
248     return (AFVSOCKSocketAddress) address;
249   }
250 
251   /**
252    * Returns the "VSOCK port" part of this address.
253    *
254    * @return The VSOCK port identifier
255    * @see #getPort()
256    */
257   public int getVSOCKPort() {
258     ByteBuffer bb = ByteBuffer.wrap(getBytes());
259     int a = bb.getInt(1 * 4);
260     return a;
261   }
262 
263   /**
264    * Returns the "VSOCK CID" part of this address.
265    *
266    * @return The VSOCK CID identifier.
267    */
268   public int getVSOCKCID() {
269     ByteBuffer bb = ByteBuffer.wrap(getBytes());
270     int a = bb.getInt(2 * 4);
271     return a;
272   }
273 
274   /**
275    * Returns the "VSOCK reserved1" part of this address.
276    *
277    * @return The "reserved1" identifier, which should be 0.
278    */
279   public int getVSOCKReserved1() {
280     ByteBuffer bb = ByteBuffer.wrap(getBytes());
281     int a = bb.getInt(0 * 4);
282     return a;
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({
419       "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.ExcessiveMethodLength",
420       "PMD.NcssCount", "PMD.NPathComplexity", "PMD.ShortMethodName"})
421   public static AFVSOCKSocketAddress of(URI uri, int overridePort) throws SocketException {
422     switch (uri.getScheme()) {
423       case "vsock":
424       case "http+vsock":
425       case "https+vsock":
426         break;
427       default:
428         throw new SocketException("Unsupported URI scheme: " + uri.getScheme());
429     }
430 
431     String host = uri.getHost();
432     if (host == null) {
433       host = uri.getAuthority();
434       if (host != null) {
435         int at = host.indexOf('@');
436         if (at >= 0) {
437           host = host.substring(at + 1);
438         }
439       }
440     }
441     if (host == null) {
442       throw new SocketException("Cannot get hostname from URI: " + uri);
443     }
444 
445     try {
446       Matcher m = PAT_VSOCK_URI_HOST_AND_PORT.matcher(host);
447       if (!m.matches()) {
448         throw new SocketException("Invalid VSOCK URI: " + uri);
449       }
450 
451       String cidStr = m.group("cid");
452       String portStr = m.group("port");
453       String javaPortStr = m.group("javaPort");
454 
455       int cid;
456       switch (cidStr == null ? "" : cidStr) {
457         case "":
458         case "any":
459           cid = VMADDR_CID_ANY;
460           break;
461         case "hypervisor":
462           cid = VMADDR_CID_HYPERVISOR;
463           break;
464         case "local":
465           cid = VMADDR_CID_LOCAL;
466           break;
467         case "host":
468           cid = VMADDR_CID_HOST;
469           break;
470         default:
471           cid = parseInt(cidStr);
472           break;
473       }
474 
475       int port;
476       switch (portStr == null ? "" : portStr) {
477         case "any":
478         case "":
479           port = VMADDR_PORT_ANY;
480           break;
481         default:
482           port = parseInt(portStr);
483           break;
484       }
485 
486       int javaPort = overridePort != -1 ? overridePort : uri.getPort();
487       if (javaPortStr != null && !javaPortStr.isEmpty()) {
488         javaPort = parseInt(javaPortStr);
489       }
490 
491       return ofPortAndCID(javaPort, port, cid);
492     } catch (IllegalArgumentException e) {
493       throw (SocketException) new SocketException("Invalid VSOCK URI: " + uri).initCause(e);
494     }
495   }
496 
497   @Override
498   @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.CompareObjectsWithEquals"})
499   public URI toURI(String scheme, URI template) throws IOException {
500     switch (scheme) {
501       case "vsock":
502       case "http+vsock":
503       case "https+vsock":
504         break;
505       default:
506         return super.toURI(scheme, template);
507     }
508 
509     byte[] bytes = getBytes();
510     if (bytes.length != (3 * 4)) {
511       return super.toURI(scheme, template);
512     }
513 
514     StringBuilder sb = new StringBuilder();
515 
516     String portStr;
517     int port;
518     switch ((port = getVSOCKPort())) {
519       case VMADDR_PORT_ANY:
520         portStr = "any";
521         break;
522       default:
523         portStr = toUnsignedString(port);
524         break;
525     }
526 
527     sb.append(portStr);
528     sb.append('.');
529     String cidStr;
530     int cid;
531     switch ((cid = getVSOCKCID())) {
532       case VMADDR_CID_ANY:
533         cidStr = "any";
534         break;
535       case VMADDR_CID_HYPERVISOR:
536         cidStr = "hypervisor";
537         break;
538       case VMADDR_CID_LOCAL:
539         cidStr = "local";
540         break;
541       case VMADDR_CID_HOST:
542         cidStr = "host";
543         break;
544       default:
545         cidStr = toUnsignedString(cid);
546         break;
547     }
548 
549     sb.append(cidStr);
550 
551     return new HostAndPort(sb.toString(), getPort()).toURI(scheme, template);
552   }
553 
554   private static int parseInt(String v) {
555     if (v.startsWith("0x")) {
556       return parseUnsignedInt(v.substring(2), 16);
557     } else if (v.startsWith("-")) {
558       return Integer.parseInt(v);
559     } else {
560       return parseUnsignedInt(v, 10);
561     }
562   }
563 
564   /**
565    * Checks if the given address could cover another address.
566    *
567    * By default, this is only true if both addresses are regarded equal using
568    * {@link #equals(Object)}.
569    *
570    * However, implementations may support "wildcard" addresses, and this method would compare a
571    * wildcard address against some non-wildcard address, for example.
572    *
573    * @param covered The other address that could be covered by this address.
574    * @return {@code true} if the other address could be covered.
575    */
576   @Override
577   public boolean covers(AFSocketAddress covered) {
578     if (super.covers(covered)) {
579       return true;
580     } else if (covered instanceof AFVSOCKSocketAddress) {
581       AFVSOCKSocketAddress other = (AFVSOCKSocketAddress) covered;
582 
583       if (getVSOCKCID() == VMADDR_CID_ANY) {
584         if (getVSOCKPort() == VMADDR_PORT_ANY) {
585           return true;
586         } else {
587           return getVSOCKPort() == other.getVSOCKPort();
588         }
589       } else if (getVSOCKPort() == VMADDR_PORT_ANY) {
590         return getVSOCKCID() == other.getVSOCKCID();
591       }
592     }
593 
594     return equals(covered);
595   }
596 }