AFVSOCKSocketAddress.java
/*
* junixsocket
*
* Copyright 2009-2024 Christian Kohlschütter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.newsclub.net.unix;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.newsclub.net.unix.pool.ObjectPool.Lease;
/**
* An {@link AFSocketAddress} for VSOCK sockets.
*
* @author Christian Kohlschütter
*/
public final class AFVSOCKSocketAddress extends AFSocketAddress {
private static final long serialVersionUID = 1L; // do not change!
private static final Pattern PAT_VSOCK_URI_HOST_AND_PORT = Pattern.compile(
"^(?<port>any|[0-9a-fx\\-]+)(\\.(?<cid>any|hypervisor|local|host|[0-9a-fx\\-]+))?(?:\\:(?<javaPort>[0-9]+))?$");
private static AFAddressFamily<AFVSOCKSocketAddress> afVsock;
/**
* "Any address for binding".
*/
public static final int VMADDR_CID_ANY = -1;
/**
* Reserved for services built into the hypervisor.
*/
public static final int VMADDR_CID_HYPERVISOR = 0;
/**
* The well-known address for local communication (loopback).
*/
public static final int VMADDR_CID_LOCAL = 1;
/**
* The well-known address of the host.
*/
public static final int VMADDR_CID_HOST = 2;
/**
* Any port number for binding.
*/
public static final int VMADDR_PORT_ANY = -1;
private AFVSOCKSocketAddress(int port, final byte[] socketAddress,
Lease<ByteBuffer> nativeAddress) throws SocketException {
super(port, socketAddress, nativeAddress, addressFamily());
}
private static AFVSOCKSocketAddress newAFSocketAddress(int port, final byte[] socketAddress,
Lease<ByteBuffer> nativeAddress) throws SocketException {
return newDeserializedAFSocketAddress(port, socketAddress, nativeAddress, addressFamily(),
AFVSOCKSocketAddress::new);
}
/**
* Returns an {@link AFVSOCKSocketAddress} that refers to a given VSOCK port and CID; the "java
* port" is set to -1.
*
* @param port The VSOCK port
* @param cid The CID.
* @return A corresponding {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails.
*/
public static AFVSOCKSocketAddress ofPortAndCID(int port, int cid) throws SocketException {
return ofPortAndCID(-1, port, cid);
}
/**
* Returns an {@link AFVSOCKSocketAddress} that refers to a given VSOCK port on the hypervisor;
* the "java port" is set to -1.
*
* @param port The VSOCK port
* @return A corresponding {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails.
*/
public static AFVSOCKSocketAddress ofHypervisorPort(int port) throws SocketException {
return ofPortAndCID(port, VMADDR_CID_HYPERVISOR);
}
/**
* Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
* port on the hypervisor; the "java port" is set to -1.
*
* @return A corresponding {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails.
*/
public static AFVSOCKSocketAddress ofAnyHypervisorPort() throws SocketException {
return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_HYPERVISOR);
}
/**
* Returns an {@link AFVSOCKSocketAddress} that refers to the given port with the local/loopback
* CID; the "java port" is set to -1.
*
* @param port The VSOCK port.
* @return A corresponding {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails.
*/
public static AFVSOCKSocketAddress ofLocalPort(int port) throws SocketException {
return ofPortAndCID(port, VMADDR_CID_LOCAL);
}
/**
* Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
* port with the local/loopback CID; the "java port" is set to -1.
*
* @return A corresponding {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails.
*/
public static AFVSOCKSocketAddress ofAnyLocalPort() throws SocketException {
return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_LOCAL);
}
/**
* Returns an {@link AFVSOCKSocketAddress} that refers to a given VSOCK port on the host; the
* "java port" is set to -1.
*
* @param port The VSOCK port
* @return A corresponding {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails.
*/
public static AFVSOCKSocketAddress ofHostPort(int port) throws SocketException {
return ofPortAndCID(port, VMADDR_CID_HOST);
}
/**
* Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
* port on the host; the "java port" is set to -1.
*
* @return A corresponding {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails.
*/
public static AFVSOCKSocketAddress ofAnyHostPort() throws SocketException {
return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_HOST);
}
/**
* Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to "any"
* port and CID; the "java port" is set to -1.
*
* @return A corresponding {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails.
*/
public static AFVSOCKSocketAddress ofAnyPort() throws SocketException {
return ofPortAndCID(VMADDR_PORT_ANY, VMADDR_CID_ANY);
}
/**
* Returns an {@link AFVSOCKSocketAddress}, especially useful for binding, that refers to the
* given port with "any CID"; the "java port" is set to -1.
*
* @param port The VSOCK port.
* @return A corresponding {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails.
*/
public static AFVSOCKSocketAddress ofPortWithAnyCID(int port) throws SocketException {
return ofPortAndCID(port, VMADDR_CID_ANY);
}
/**
* Returns an {@link AFVSOCKSocketAddress} that refers to a given port and CID.
*
* @param javaPort The Java port number.
* @param vsockPort The vsock port.
* @param cid The CID.
* @return A corresponding {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails.
*/
public static AFVSOCKSocketAddress ofPortAndCID(int javaPort, int vsockPort, int cid)
throws SocketException {
return resolveAddress(toBytes(vsockPort, cid), javaPort, addressFamily());
}
/**
* Returns an {@link AFVSOCKSocketAddress} given a special {@link InetAddress} that encodes the
* byte sequence of an AF_VSOCK socket address, like those returned by {@link #wrapAddress()}.
*
* @param address The "special" {@link InetAddress}.
* @param port The port (use 0 for "none").
* @return The {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails, for example when an unsupported address is
* specified.
*/
public static AFVSOCKSocketAddress unwrap(InetAddress address, int port) throws SocketException {
return AFSocketAddress.unwrap(address, port, addressFamily());
}
/**
* Returns an {@link AFVSOCKSocketAddress} given a special {@link InetAddress} hostname that
* encodes the byte sequence of an AF_VSOCK socket address, like those returned by
* {@link #wrapAddress()}.
*
* @param hostname The "special" hostname, as provided by {@link InetAddress#getHostName()}.
* @param port The port (use 0 for "none").
* @return The {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails, for example when an unsupported address is
* specified.
*/
public static AFVSOCKSocketAddress unwrap(String hostname, int port) throws SocketException {
return AFSocketAddress.unwrap(hostname, port, addressFamily());
}
/**
* Returns an {@link AFVSOCKSocketAddress} given a generic {@link SocketAddress}.
*
* @param address The address to unwrap.
* @return The {@link AFVSOCKSocketAddress} instance.
* @throws SocketException if the operation fails, for example when an unsupported address is
* specified.
*/
public static AFVSOCKSocketAddress unwrap(SocketAddress address) throws SocketException {
Objects.requireNonNull(address);
if (!isSupportedAddress(address)) {
throw new SocketException("Unsupported address");
}
return (AFVSOCKSocketAddress) address;
}
/**
* Returns the "VSOCK port" part of this address.
*
* @return The VSOCK port identifier
* @see #getPort()
*/
public int getVSOCKPort() {
ByteBuffer bb = ByteBuffer.wrap(getBytes());
int a = bb.getInt(1 * 4);
return a;
}
/**
* Returns the "VSOCK CID" part of this address.
*
* @return The VSOCK CID identifier.
*/
public int getVSOCKCID() {
ByteBuffer bb = ByteBuffer.wrap(getBytes());
int a = bb.getInt(2 * 4);
return a;
}
/**
* Returns the "VSOCK reserved1" part of this address.
*
* @return The "reserved1" identifier, which should be 0.
*/
public int getVSOCKReserved1() {
ByteBuffer bb = ByteBuffer.wrap(getBytes());
int a = bb.getInt(0 * 4);
return a;
}
@Override
public String toString() {
int port = getPort();
byte[] bytes = getBytes();
if (bytes.length != (3 * 4)) {
return getClass().getName() + "[" + (port == 0 ? "" : "port=" + port) + ";UNKNOWN" + "]";
}
ByteBuffer bb = ByteBuffer.wrap(bytes);
int reserved1 = bb.getInt();
int vsockPort = bb.getInt();
int cid = bb.getInt();
String vsockPortString;
if (vsockPort >= -1) {
vsockPortString = Integer.toString(vsockPort);
} else {
vsockPortString = String.format(Locale.ENGLISH, "0x%08x", vsockPort);
}
String typeString = (reserved1 == 0 ? "" : "reserved1=" + reserved1 + ";") + "vsockPort="
+ vsockPortString + ";cid=" + cid;
return getClass().getName() + "[" + (port == 0 ? "" : "port=" + port + ";") + typeString + "]";
}
@Override
public boolean hasFilename() {
return false;
}
@Override
public File getFile() throws FileNotFoundException {
throw new FileNotFoundException("no file");
}
/**
* Checks if an {@link InetAddress} can be unwrapped to an {@link AFVSOCKSocketAddress}.
*
* @param addr The instance to check.
* @return {@code true} if so.
* @see #wrapAddress()
* @see #unwrap(InetAddress, int)
*/
public static boolean isSupportedAddress(InetAddress addr) {
return AFSocketAddress.isSupportedAddress(addr, addressFamily());
}
/**
* Checks if a {@link SocketAddress} can be unwrapped to an {@link AFVSOCKSocketAddress}.
*
* @param addr The instance to check.
* @return {@code true} if so.
* @see #unwrap(InetAddress, int)
*/
public static boolean isSupportedAddress(SocketAddress addr) {
return (addr instanceof AFVSOCKSocketAddress);
}
@SuppressWarnings("cast")
private static byte[] toBytes(int port, int cid) {
ByteBuffer bb = ByteBuffer.allocate(3 * 4);
bb.putInt(0); // svm_reserved1
bb.putInt(port); // svm_port
bb.putInt(cid); // svm_cid
return (byte[]) bb.flip().array();
}
/**
* Returns the corresponding {@link AFAddressFamily}.
*
* @return The address family instance.
*/
@SuppressWarnings("null")
public static synchronized AFAddressFamily<AFVSOCKSocketAddress> addressFamily() {
if (afVsock == null) {
afVsock = AFAddressFamily.registerAddressFamily("vsock", //
AFVSOCKSocketAddress.class, new AFSocketAddressConfig<AFVSOCKSocketAddress>() {
private final AFSocketAddressConstructor<AFVSOCKSocketAddress> addrConstr =
isUseDeserializationForInit() ? AFVSOCKSocketAddress::newAFSocketAddress
: AFVSOCKSocketAddress::new;
@Override
protected AFVSOCKSocketAddress parseURI(URI u, int port) throws SocketException {
return AFVSOCKSocketAddress.of(u, port);
}
@Override
protected AFSocketAddressConstructor<AFVSOCKSocketAddress> addressConstructor() {
return addrConstr;
}
@Override
protected String selectorProviderClassname() {
return "org.newsclub.net.unix.vsock.AFVSOCKSelectorProvider";
}
@Override
protected Set<String> uriSchemes() {
return new HashSet<>(Arrays.asList("vsock", "http+vsock", "https+vsock"));
}
});
try {
Class.forName("org.newsclub.net.unix.vsock.AFVSOCKSelectorProvider");
} catch (ClassNotFoundException e) {
// ignore
}
}
return afVsock;
}
/**
* Returns an {@link AFVSOCKSocketAddress} for the given URI, if possible.
*
* @param uri The URI.
* @return The address.
* @throws SocketException if the operation fails.
*/
@SuppressWarnings("PMD.ShortMethodName")
public static AFVSOCKSocketAddress of(URI uri) throws SocketException {
return of(uri, -1);
}
/**
* Returns an {@link AFVSOCKSocketAddress} for the given URI, if possible.
*
* @param uri The URI.
* @param overridePort The port to forcibly use, or {@code -1} for "don't override".
* @return The address.
* @throws SocketException if the operation fails.
*/
@SuppressWarnings({
"PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.ExcessiveMethodLength",
"PMD.NcssCount", "PMD.NPathComplexity", "PMD.ShortMethodName"})
public static AFVSOCKSocketAddress of(URI uri, int overridePort) throws SocketException {
switch (uri.getScheme()) {
case "vsock":
case "http+vsock":
case "https+vsock":
break;
default:
throw new SocketException("Unsupported URI scheme: " + uri.getScheme());
}
String host = uri.getHost();
if (host == null) {
host = uri.getAuthority();
if (host != null) {
int at = host.indexOf('@');
if (at >= 0) {
host = host.substring(at + 1);
}
}
}
if (host == null) {
throw new SocketException("Cannot get hostname from URI: " + uri);
}
try {
Matcher m = PAT_VSOCK_URI_HOST_AND_PORT.matcher(host);
if (!m.matches()) {
throw new SocketException("Invalid VSOCK URI: " + uri);
}
String cidStr = m.group("cid");
String portStr = m.group("port");
String javaPortStr = m.group("javaPort");
int cid;
switch (cidStr == null ? "" : cidStr) {
case "":
case "any":
cid = VMADDR_CID_ANY;
break;
case "hypervisor":
cid = VMADDR_CID_HYPERVISOR;
break;
case "local":
cid = VMADDR_CID_LOCAL;
break;
case "host":
cid = VMADDR_CID_HOST;
break;
default:
cid = parseInt(cidStr);
break;
}
int port;
switch (portStr == null ? "" : portStr) {
case "any":
case "":
port = VMADDR_PORT_ANY;
break;
default:
port = parseInt(portStr);
break;
}
int javaPort = overridePort != -1 ? overridePort : uri.getPort();
if (javaPortStr != null && !javaPortStr.isEmpty()) {
javaPort = parseInt(javaPortStr);
}
return ofPortAndCID(javaPort, port, cid);
} catch (IllegalArgumentException e) {
throw (SocketException) new SocketException("Invalid VSOCK URI: " + uri).initCause(e);
}
}
@Override
@SuppressWarnings({"PMD.CognitiveComplexity", "PMD.CompareObjectsWithEquals"})
public URI toURI(String scheme, URI template) throws IOException {
switch (scheme) {
case "vsock":
case "http+vsock":
case "https+vsock":
break;
default:
return super.toURI(scheme, template);
}
byte[] bytes = getBytes();
if (bytes.length != (3 * 4)) {
return super.toURI(scheme, template);
}
StringBuilder sb = new StringBuilder();
String portStr;
int port;
switch ((port = getVSOCKPort())) {
case VMADDR_PORT_ANY:
portStr = "any";
break;
default:
portStr = toUnsignedString(port);
break;
}
sb.append(portStr);
sb.append('.');
String cidStr;
int cid;
switch ((cid = getVSOCKCID())) {
case VMADDR_CID_ANY:
cidStr = "any";
break;
case VMADDR_CID_HYPERVISOR:
cidStr = "hypervisor";
break;
case VMADDR_CID_LOCAL:
cidStr = "local";
break;
case VMADDR_CID_HOST:
cidStr = "host";
break;
default:
cidStr = toUnsignedString(cid);
break;
}
sb.append(cidStr);
return new HostAndPort(sb.toString(), getPort()).toURI(scheme, template);
}
private static int parseInt(String v) {
if (v.startsWith("0x")) {
return parseUnsignedInt(v.substring(2), 16);
} else if (v.startsWith("-")) {
return Integer.parseInt(v);
} else {
return parseUnsignedInt(v, 10);
}
}
/**
* Checks if the given address could cover another address.
*
* By default, this is only true if both addresses are regarded equal using
* {@link #equals(Object)}.
*
* However, implementations may support "wildcard" addresses, and this method would compare a
* wildcard address against some non-wildcard address, for example.
*
* @param covered The other address that could be covered by this address.
* @return {@code true} if the other address could be covered.
*/
@Override
public boolean covers(AFSocketAddress covered) {
if (super.covers(covered)) {
return true;
} else if (covered instanceof AFVSOCKSocketAddress) {
AFVSOCKSocketAddress other = (AFVSOCKSocketAddress) covered;
if (getVSOCKCID() == VMADDR_CID_ANY) {
if (getVSOCKPort() == VMADDR_PORT_ANY) {
return true;
} else {
return getVSOCKPort() == other.getVSOCKPort();
}
} else if (getVSOCKPort() == VMADDR_PORT_ANY) {
return getVSOCKCID() == other.getVSOCKCID();
}
}
return equals(covered);
}
}