mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 02:48:24 +00:00
feat: support linux vsock (#28725)
impl support for vsock https://man7.org/linux/man-pages/man7/vsock.7.html
This commit is contained in:
parent
7218113d24
commit
9da231dc7a
17 changed files with 680 additions and 28 deletions
25
Cargo.lock
generated
25
Cargo.lock
generated
|
@ -2240,6 +2240,7 @@ dependencies = [
|
|||
"socket2",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-vsock",
|
||||
"url",
|
||||
"web-transport-proto",
|
||||
]
|
||||
|
@ -5588,6 +5589,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -8696,6 +8698,19 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-vsock"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "074885a713a0e1e8f2cc6855a004c7c882572d980d4f8262523dc2b094c96da8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"libc",
|
||||
"tokio",
|
||||
"vsock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
|
@ -9199,6 +9214,16 @@ version = "0.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
|
||||
|
||||
[[package]]
|
||||
name = "vsock"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e8b4d00e672f147fc86a09738fadb1445bd1c0a40542378dfb82909deeee688"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"nix 0.29.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.11.1"
|
||||
|
|
|
@ -369,6 +369,7 @@ syn = { version = "2", features = ["full", "extra-traits"] }
|
|||
|
||||
# unix deps
|
||||
nix = "=0.27.1"
|
||||
tokio-vsock = "0.7"
|
||||
|
||||
# windows deps
|
||||
junction = "=1.2.0"
|
||||
|
|
|
@ -32,6 +32,7 @@ pub fn validator(host_and_port: &str) -> Result<String, String> {
|
|||
if Url::parse(&format!("internal://{host_and_port}")).is_ok()
|
||||
|| host_and_port.parse::<IpAddr>().is_ok()
|
||||
|| host_and_port.parse::<BarePort>().is_ok()
|
||||
|| NetDescriptor::parse(host_and_port).is_ok()
|
||||
{
|
||||
Ok(host_and_port.to_string())
|
||||
} else {
|
||||
|
|
95
cli/tsc/dts/lib.deno.ns.d.ts
vendored
95
cli/tsc/dts/lib.deno.ns.d.ts
vendored
|
@ -5160,6 +5160,23 @@ declare namespace Deno {
|
|||
path: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that can be passed to `Deno.serve` to create a server listening on
|
||||
* a vsock socket.
|
||||
*
|
||||
* @category HTTP Server
|
||||
*/
|
||||
export interface ServeVsockOptions extends ServeOptions<Deno.VsockAddr> {
|
||||
/** The transport to use. */
|
||||
transport?: "vsock";
|
||||
|
||||
/** The context identifier to use. */
|
||||
cid: number;
|
||||
|
||||
/** The port to use. */
|
||||
port: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @category HTTP Server
|
||||
*/
|
||||
|
@ -5261,6 +5278,56 @@ declare namespace Deno {
|
|||
options: ServeUnixOptions,
|
||||
handler: ServeHandler<Deno.UnixAddr>,
|
||||
): HttpServer<Deno.UnixAddr>;
|
||||
/** Serves HTTP requests with the given option bag and handler.
|
||||
*
|
||||
* You can specify the socket path with `path` option.
|
||||
*
|
||||
* ```ts
|
||||
* Deno.serve(
|
||||
* { cid: -1, port: 3000 },
|
||||
* (_req) => new Response("Hello, world")
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* You can stop the server with an {@linkcode AbortSignal}. The abort signal
|
||||
* needs to be passed as the `signal` option in the options bag. The server
|
||||
* aborts when the abort signal is aborted. To wait for the server to close,
|
||||
* await the promise returned from the `Deno.serve` API.
|
||||
*
|
||||
* ```ts
|
||||
* const ac = new AbortController();
|
||||
*
|
||||
* const server = Deno.serve(
|
||||
* { signal: ac.signal, cid: -1, port: 3000 },
|
||||
* (_req) => new Response("Hello, world")
|
||||
* );
|
||||
* server.finished.then(() => console.log("Server closed"));
|
||||
*
|
||||
* console.log("Closing server...");
|
||||
* ac.abort();
|
||||
* ```
|
||||
*
|
||||
* By default `Deno.serve` prints the message
|
||||
* `Listening on path/to/socket` on listening. If you like to
|
||||
* change this behavior, you can specify a custom `onListen` callback.
|
||||
*
|
||||
* ```ts
|
||||
* Deno.serve({
|
||||
* onListen({ cid, port }) {
|
||||
* console.log(`Server started at ${cid}:${port}`);
|
||||
* // ... more info specific to your server ..
|
||||
* },
|
||||
* cid: -1,
|
||||
* port: 3000,
|
||||
* }, (_req) => new Response("Hello, world"));
|
||||
* ```
|
||||
*
|
||||
* @category HTTP Server
|
||||
*/
|
||||
export function serve(
|
||||
options: ServeVsockOptions,
|
||||
handler: ServeHandler<Deno.VsockAddr>,
|
||||
): HttpServer<Deno.VsockAddr>;
|
||||
/** Serves HTTP requests with the given option bag and handler.
|
||||
*
|
||||
* You can specify an object with a port and hostname option, which is the
|
||||
|
@ -5348,6 +5415,34 @@ declare namespace Deno {
|
|||
export function serve(
|
||||
options: ServeUnixOptions & ServeInit<Deno.UnixAddr>,
|
||||
): HttpServer<Deno.UnixAddr>;
|
||||
/** Serves HTTP requests with the given option bag.
|
||||
*
|
||||
* You can specify an object with the path option, which is the
|
||||
* vsock socket to listen on.
|
||||
*
|
||||
* ```ts
|
||||
* const ac = new AbortController();
|
||||
*
|
||||
* const server = Deno.serve({
|
||||
* cid: -1,
|
||||
* port: 3000,
|
||||
* handler: (_req) => new Response("Hello, world"),
|
||||
* signal: ac.signal,
|
||||
* onListen({ cid, port }) {
|
||||
* console.log(`Server started at ${cid}:${port}`);
|
||||
* },
|
||||
* });
|
||||
* server.finished.then(() => console.log("Server closed"));
|
||||
*
|
||||
* console.log("Closing server...");
|
||||
* ac.abort();
|
||||
* ```
|
||||
*
|
||||
* @category HTTP Server
|
||||
*/
|
||||
export function serve(
|
||||
options: ServeVsockOptions & ServeInit<Deno.VsockAddr>,
|
||||
): HttpServer<Deno.VsockAddr>;
|
||||
/** Serves HTTP requests with the given option bag.
|
||||
*
|
||||
* You can specify an object with a port and hostname option, which is the
|
||||
|
|
71
cli/tsc/dts/lib.deno_net.d.ts
vendored
71
cli/tsc/dts/lib.deno_net.d.ts
vendored
|
@ -19,7 +19,14 @@ declare namespace Deno {
|
|||
}
|
||||
|
||||
/** @category Network */
|
||||
export type Addr = NetAddr | UnixAddr;
|
||||
export interface VsockAddr {
|
||||
transport: "vsock";
|
||||
cid: number;
|
||||
port: number;
|
||||
}
|
||||
|
||||
/** @category Network */
|
||||
export type Addr = NetAddr | UnixAddr | VsockAddr;
|
||||
|
||||
/** A generic network listener for stream-oriented protocols.
|
||||
*
|
||||
|
@ -67,6 +74,12 @@ declare namespace Deno {
|
|||
*/
|
||||
export type UnixListener = Listener<UnixConn, UnixAddr>;
|
||||
|
||||
/** Specialized listener that accepts Vsock connections.
|
||||
*
|
||||
* @category Network
|
||||
*/
|
||||
export type VsockListener = Listener<VsockConn, VsockAddr>;
|
||||
|
||||
/** @category Network */
|
||||
export interface Conn<A extends Addr = Addr> extends Disposable {
|
||||
/** Read the incoming data from the connection into an array buffer (`p`).
|
||||
|
@ -223,6 +236,32 @@ declare namespace Deno {
|
|||
options: UnixListenOptions & { transport: "unix" },
|
||||
): UnixListener;
|
||||
|
||||
/** Options which can be set when opening a vsock listener via
|
||||
* {@linkcode Deno.listen}.
|
||||
*
|
||||
* @category Network
|
||||
*/
|
||||
export interface VsockListenOptions {
|
||||
cid: number;
|
||||
port: number;
|
||||
}
|
||||
|
||||
/** Listen announces on the local transport address.
|
||||
*
|
||||
* ```ts
|
||||
* const listener = Deno.listen({ cid: -1, port: 80, transport: "vsock" })
|
||||
* ```
|
||||
*
|
||||
* Requires `allow-net` permission.
|
||||
*
|
||||
* @tags allow-net
|
||||
* @category Network
|
||||
*/
|
||||
// deno-lint-ignore adjacent-overload-signatures
|
||||
export function listen(
|
||||
options: VsockListenOptions & { transport: "vsock" },
|
||||
): VsockListener;
|
||||
|
||||
/**
|
||||
* Provides certified key material from strings. The key material is provided in
|
||||
* `PEM`-format (Privacy Enhanced Mail, https://www.rfc-editor.org/rfc/rfc1422) which can be identified by having
|
||||
|
@ -350,6 +389,36 @@ declare namespace Deno {
|
|||
// deno-lint-ignore adjacent-overload-signatures
|
||||
export function connect(options: UnixConnectOptions): Promise<UnixConn>;
|
||||
|
||||
/** @category Network */
|
||||
export interface VsockConnectOptions {
|
||||
transport: "vsock";
|
||||
cid: number;
|
||||
port: number;
|
||||
}
|
||||
|
||||
/** @category Network */
|
||||
export interface VsockConn extends Conn<VsockAddr> {}
|
||||
|
||||
/** Connects to the hostname (default is "127.0.0.1") and port on the named
|
||||
* transport (default is "tcp"), and resolves to the connection (`Conn`).
|
||||
*
|
||||
* ```ts
|
||||
* const conn1 = await Deno.connect({ port: 80 });
|
||||
* const conn2 = await Deno.connect({ hostname: "192.0.2.1", port: 80 });
|
||||
* const conn3 = await Deno.connect({ hostname: "[2001:db8::1]", port: 80 });
|
||||
* const conn4 = await Deno.connect({ hostname: "golang.org", port: 80, transport: "tcp" });
|
||||
* const conn5 = await Deno.connect({ path: "/foo/bar.sock", transport: "unix" });
|
||||
* const conn6 = await Deno.connect({ cid: -1, port: 80, transport: "vsock" });
|
||||
* ```
|
||||
*
|
||||
* Requires `allow-net` permission for "tcp" and "vsock", and `allow-read` for "unix".
|
||||
*
|
||||
* @tags allow-net, allow-read
|
||||
* @category Network
|
||||
*/
|
||||
// deno-lint-ignore adjacent-overload-signatures
|
||||
export function connect(options: VsockConnectOptions): Promise<VsockConn>;
|
||||
|
||||
/** @category Network */
|
||||
export interface ConnectTlsOptions {
|
||||
/** The port to connect to. */
|
||||
|
|
|
@ -46,6 +46,7 @@ const {
|
|||
TypedArrayPrototypeGetSymbolToStringTag,
|
||||
Uint8Array,
|
||||
Promise,
|
||||
Number,
|
||||
} = primordials;
|
||||
|
||||
import { InnerBody } from "ext:deno_fetch/22_body.js";
|
||||
|
@ -348,6 +349,13 @@ class InnerRequest {
|
|||
}
|
||||
this.#methodAndUri = op_http_get_request_method_and_url(this.#external);
|
||||
}
|
||||
if (transport === "vsock") {
|
||||
return {
|
||||
transport,
|
||||
cid: Number(this.#methodAndUri[3]),
|
||||
port: this.#methodAndUri[4],
|
||||
};
|
||||
}
|
||||
return {
|
||||
transport: "tcp",
|
||||
hostname: this.#methodAndUri[3],
|
||||
|
@ -769,6 +777,7 @@ function serve(arg1, arg2) {
|
|||
|
||||
const wantsHttps = hasTlsKeyPairOptions(options);
|
||||
const wantsUnix = ObjectHasOwn(options, "path");
|
||||
const wantsVsock = ObjectHasOwn(options, "cid");
|
||||
const signal = options.signal;
|
||||
const onError = options.onError ??
|
||||
function (error) {
|
||||
|
@ -792,6 +801,23 @@ function serve(arg1, arg2) {
|
|||
});
|
||||
}
|
||||
|
||||
if (wantsVsock) {
|
||||
const listener = listen({
|
||||
transport: "vsock",
|
||||
cid: options.cid,
|
||||
port: options.port,
|
||||
[listenOptionApiName]: "Deno.serve",
|
||||
});
|
||||
const { cid, port } = listener.addr;
|
||||
return serveHttpOnListener(listener, signal, handler, onError, () => {
|
||||
if (options.onListen) {
|
||||
options.onListen(listener.addr);
|
||||
} else {
|
||||
import.meta.log("info", `Listening on vsock:${cid}:${port}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const listenOpts = {
|
||||
hostname: options.hostname ?? "0.0.0.0",
|
||||
port: options.port ?? 8000,
|
||||
|
|
|
@ -385,7 +385,7 @@ where
|
|||
.into();
|
||||
|
||||
let port: v8::Local<v8::Value> = match request_info.peer_port {
|
||||
Some(port) => v8::Integer::new(scope, port.into()).into(),
|
||||
Some(port) => v8::Number::new(scope, port.into()).into(),
|
||||
None => v8::undefined(scope).into(),
|
||||
};
|
||||
|
||||
|
@ -1025,6 +1025,10 @@ where
|
|||
NetworkStream::Unix(conn) => {
|
||||
serve_http(conn, connection_properties, lifetime, tx, options)
|
||||
}
|
||||
#[cfg(unix)]
|
||||
NetworkStream::Vsock(conn) => {
|
||||
serve_http(conn, connection_properties, lifetime, tx, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,15 +23,15 @@ use hyper::Uri;
|
|||
pub struct HttpListenProperties {
|
||||
pub scheme: &'static str,
|
||||
pub fallback_host: String,
|
||||
pub local_port: Option<u16>,
|
||||
pub local_port: Option<u32>,
|
||||
pub stream_type: NetworkStreamType,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HttpConnectionProperties {
|
||||
pub peer_address: Rc<str>,
|
||||
pub peer_port: Option<u16>,
|
||||
pub local_port: Option<u16>,
|
||||
pub peer_port: Option<u32>,
|
||||
pub local_port: Option<u32>,
|
||||
pub stream_type: NetworkStreamType,
|
||||
}
|
||||
|
||||
|
@ -161,15 +161,19 @@ impl HttpPropertyExtractor for DefaultHttpPropertyExtractor {
|
|||
0,
|
||||
)))
|
||||
});
|
||||
let peer_port: Option<u16> = match peer_address {
|
||||
NetworkStreamAddress::Ip(ip) => Some(ip.port()),
|
||||
let peer_port: Option<u32> = match peer_address {
|
||||
NetworkStreamAddress::Ip(ip) => Some(ip.port() as _),
|
||||
#[cfg(unix)]
|
||||
NetworkStreamAddress::Unix(_) => None,
|
||||
#[cfg(unix)]
|
||||
NetworkStreamAddress::Vsock(vsock) => Some(vsock.port()),
|
||||
};
|
||||
let peer_address = match peer_address {
|
||||
NetworkStreamAddress::Ip(addr) => Rc::from(addr.ip().to_string()),
|
||||
#[cfg(unix)]
|
||||
NetworkStreamAddress::Unix(_) => Rc::from("unix"),
|
||||
#[cfg(unix)]
|
||||
NetworkStreamAddress::Vsock(addr) => Rc::from(addr.cid().to_string()),
|
||||
};
|
||||
let local_port = listen_properties.local_port;
|
||||
let stream_type = listen_properties.stream_type;
|
||||
|
@ -204,10 +208,12 @@ fn listener_properties(
|
|||
) -> Result<HttpListenProperties, std::io::Error> {
|
||||
let scheme = req_scheme_from_stream_type(stream_type);
|
||||
let fallback_host = req_host_from_addr(stream_type, &local_address);
|
||||
let local_port: Option<u16> = match local_address {
|
||||
NetworkStreamAddress::Ip(ip) => Some(ip.port()),
|
||||
let local_port: Option<u32> = match local_address {
|
||||
NetworkStreamAddress::Ip(ip) => Some(ip.port() as _),
|
||||
#[cfg(unix)]
|
||||
NetworkStreamAddress::Unix(_) => None,
|
||||
#[cfg(unix)]
|
||||
NetworkStreamAddress::Vsock(vsock) => Some(vsock.port()),
|
||||
};
|
||||
Ok(HttpListenProperties {
|
||||
scheme,
|
||||
|
@ -252,6 +258,10 @@ fn req_host_from_addr(
|
|||
percent_encoding::NON_ALPHANUMERIC,
|
||||
)
|
||||
.to_string(),
|
||||
#[cfg(unix)]
|
||||
NetworkStreamAddress::Vsock(vsock) => {
|
||||
format!("{}:{}", vsock.cid(), vsock.port())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,6 +271,8 @@ fn req_scheme_from_stream_type(stream_type: NetworkStreamType) -> &'static str {
|
|||
NetworkStreamType::Tls => "https://",
|
||||
#[cfg(unix)]
|
||||
NetworkStreamType::Unix => "http+unix://",
|
||||
#[cfg(unix)]
|
||||
NetworkStreamType::Vsock => "http+vsock://",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,11 +280,13 @@ fn req_host<'a>(
|
|||
uri: &'a Uri,
|
||||
headers: &'a HeaderMap,
|
||||
addr_type: NetworkStreamType,
|
||||
port: u16,
|
||||
port: u32,
|
||||
) -> Option<Cow<'a, str>> {
|
||||
// Unix sockets always use the socket address
|
||||
#[cfg(unix)]
|
||||
if addr_type == NetworkStreamType::Unix {
|
||||
if addr_type == NetworkStreamType::Unix
|
||||
|| addr_type == NetworkStreamType::Vsock
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -291,6 +305,8 @@ fn req_host<'a>(
|
|||
}
|
||||
#[cfg(unix)]
|
||||
NetworkStreamType::Unix => {}
|
||||
#[cfg(unix)]
|
||||
NetworkStreamType::Vsock => {}
|
||||
}
|
||||
return Some(Cow::Borrowed(auth.as_str()));
|
||||
}
|
||||
|
|
|
@ -12,14 +12,17 @@ import {
|
|||
op_dns_resolve,
|
||||
op_net_accept_tcp,
|
||||
op_net_accept_unix,
|
||||
op_net_accept_vsock,
|
||||
op_net_connect_tcp,
|
||||
op_net_connect_unix,
|
||||
op_net_connect_vsock,
|
||||
op_net_join_multi_v4_udp,
|
||||
op_net_join_multi_v6_udp,
|
||||
op_net_leave_multi_v4_udp,
|
||||
op_net_leave_multi_v6_udp,
|
||||
op_net_listen_tcp,
|
||||
op_net_listen_unix,
|
||||
op_net_listen_vsock,
|
||||
op_net_recv_udp,
|
||||
op_net_recv_unixpacket,
|
||||
op_net_send_udp,
|
||||
|
@ -249,6 +252,20 @@ class UnixConn extends Conn {
|
|||
}
|
||||
}
|
||||
|
||||
class VsockConn extends Conn {
|
||||
#rid = 0;
|
||||
|
||||
constructor(rid, remoteAddr, localAddr) {
|
||||
super(rid, remoteAddr, localAddr);
|
||||
ObjectDefineProperty(this, internalRidSymbol, {
|
||||
__proto__: null,
|
||||
enumerable: false,
|
||||
value: rid,
|
||||
});
|
||||
this.#rid = rid;
|
||||
}
|
||||
}
|
||||
|
||||
class Listener {
|
||||
#rid = 0;
|
||||
#addr = null;
|
||||
|
@ -278,6 +295,9 @@ class Listener {
|
|||
case "unix":
|
||||
promise = op_net_accept_unix(this.#rid);
|
||||
break;
|
||||
case "vsock":
|
||||
promise = op_net_accept_vsock(this.#rid);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported transport: ${this.addr.transport}`);
|
||||
}
|
||||
|
@ -285,18 +305,25 @@ class Listener {
|
|||
if (this.#unref) core.unrefOpPromise(promise);
|
||||
const { 0: rid, 1: localAddr, 2: remoteAddr, 3: fd } = await promise;
|
||||
this.#promise = null;
|
||||
if (this.addr.transport == "tcp") {
|
||||
localAddr.transport = "tcp";
|
||||
remoteAddr.transport = "tcp";
|
||||
return new TcpConn(rid, remoteAddr, localAddr, fd);
|
||||
} else if (this.addr.transport == "unix") {
|
||||
return new UnixConn(
|
||||
rid,
|
||||
{ transport: "unix", path: remoteAddr },
|
||||
{ transport: "unix", path: localAddr },
|
||||
);
|
||||
} else {
|
||||
throw new Error("unreachable");
|
||||
switch (this.addr.transport) {
|
||||
case "tcp":
|
||||
localAddr.transport = "tcp";
|
||||
remoteAddr.transport = "tcp";
|
||||
return new TcpConn(rid, remoteAddr, localAddr, fd);
|
||||
case "unix":
|
||||
return new UnixConn(
|
||||
rid,
|
||||
{ transport: "unix", path: remoteAddr },
|
||||
{ transport: "unix", path: localAddr },
|
||||
);
|
||||
case "vsock":
|
||||
return new VsockConn(
|
||||
rid,
|
||||
{ transport: "vsock", cid: remoteAddr[0], port: remoteAddr[1] },
|
||||
{ transport: "vsock", cid: localAddr[0], port: localAddr[1] },
|
||||
);
|
||||
default:
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -528,6 +555,18 @@ function listen(args) {
|
|||
};
|
||||
return new Listener(rid, addr);
|
||||
}
|
||||
case "vsock": {
|
||||
const { 0: rid, 1: cid, 2: port } = op_net_listen_vsock(
|
||||
args.cid,
|
||||
args.port,
|
||||
);
|
||||
const addr = {
|
||||
transport: "vsock",
|
||||
cid,
|
||||
port,
|
||||
};
|
||||
return new Listener(rid, addr);
|
||||
}
|
||||
default:
|
||||
throw new TypeError(`Unsupported transport: '${transport}'`);
|
||||
}
|
||||
|
@ -605,6 +644,15 @@ async function connect(args) {
|
|||
{ transport: "unix", path: localAddr },
|
||||
);
|
||||
}
|
||||
case "vsock": {
|
||||
const { 0: rid, 1: localAddr, 2: remoteAddr } =
|
||||
await op_net_connect_vsock(args.cid, args.port);
|
||||
return new VsockConn(
|
||||
rid,
|
||||
{ transport: "vsock", cid: remoteAddr[0], port: remoteAddr[1] },
|
||||
{ transport: "vsock", cid: localAddr[0], port: localAddr[1] },
|
||||
);
|
||||
}
|
||||
default:
|
||||
throw new TypeError(`Unsupported transport: '${transport}'`);
|
||||
}
|
||||
|
@ -622,4 +670,5 @@ export {
|
|||
UnixConn,
|
||||
UpgradedConn,
|
||||
validatePort,
|
||||
VsockConn,
|
||||
};
|
||||
|
|
|
@ -30,3 +30,6 @@ thiserror.workspace = true
|
|||
tokio.workspace = true
|
||||
url.workspace = true
|
||||
web-transport-proto.workspace = true
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
tokio-vsock.workspace = true
|
||||
|
|
|
@ -189,3 +189,44 @@ impl Resource for UnixStreamResource {
|
|||
self.cancel_read_ops();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub type VsockStreamResource =
|
||||
FullDuplexResource<tokio_vsock::OwnedReadHalf, tokio_vsock::OwnedWriteHalf>;
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub struct VsockStreamResource;
|
||||
|
||||
#[cfg(not(unix))]
|
||||
impl VsockStreamResource {
|
||||
fn read(self: Rc<Self>, _data: &mut [u8]) -> AsyncResult<usize> {
|
||||
unreachable!()
|
||||
}
|
||||
fn write(self: Rc<Self>, _data: &[u8]) -> AsyncResult<usize> {
|
||||
unreachable!()
|
||||
}
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn shutdown(self: Rc<Self>) -> Result<(), JsErrorBox> {
|
||||
unreachable!()
|
||||
}
|
||||
pub fn cancel_read_ops(&self) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Resource for VsockStreamResource {
|
||||
deno_core::impl_readable_byob!();
|
||||
deno_core::impl_writable!();
|
||||
|
||||
fn name(&self) -> Cow<str> {
|
||||
"vsockStream".into()
|
||||
}
|
||||
|
||||
fn shutdown(self: Rc<Self>) -> AsyncResult<()> {
|
||||
Box::pin(self.shutdown().map_err(JsErrorBox::from_err))
|
||||
}
|
||||
|
||||
fn close(self: Rc<Self>) {
|
||||
self.cancel_read_ops();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,13 @@ pub trait NetPermissions {
|
|||
p: &'a Path,
|
||||
api_name: &str,
|
||||
) -> Result<Cow<'a, Path>, PermissionCheckError>;
|
||||
#[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"]
|
||||
fn check_vsock(
|
||||
&mut self,
|
||||
cid: u32,
|
||||
port: u32,
|
||||
api_name: &str,
|
||||
) -> Result<(), PermissionCheckError>;
|
||||
}
|
||||
|
||||
impl NetPermissions for deno_permissions::PermissionsContainer {
|
||||
|
@ -87,6 +94,18 @@ impl NetPermissions for deno_permissions::PermissionsContainer {
|
|||
self, path, api_name,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn check_vsock(
|
||||
&mut self,
|
||||
cid: u32,
|
||||
port: u32,
|
||||
api_name: &str,
|
||||
) -> Result<(), PermissionCheckError> {
|
||||
deno_permissions::PermissionsContainer::check_net_vsock(
|
||||
self, cid, port, api_name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for checking unstable features. Used for sync ops.
|
||||
|
@ -139,6 +158,9 @@ deno_core::extension!(deno_net,
|
|||
ops::op_dns_resolve<P>,
|
||||
ops::op_set_nodelay,
|
||||
ops::op_set_keepalive,
|
||||
ops::op_net_listen_vsock<P>,
|
||||
ops::op_net_accept_vsock,
|
||||
ops::op_net_connect_vsock<P>,
|
||||
|
||||
ops_tls::op_tls_key_null,
|
||||
ops_tls::op_tls_key_static,
|
||||
|
|
151
ext/net/ops.rs
151
ext/net/ops.rs
|
@ -152,6 +152,9 @@ pub enum NetError {
|
|||
#[class(generic)]
|
||||
#[error("{0}")]
|
||||
Reunite(tokio::net::tcp::ReuniteError),
|
||||
#[class(generic)]
|
||||
#[error("VSOCK is not supported on this platform")]
|
||||
VsockUnsupported,
|
||||
}
|
||||
|
||||
pub(crate) fn accept_err(e: std::io::Error) -> NetError {
|
||||
|
@ -608,6 +611,145 @@ where
|
|||
net_listen_udp::<NP>(state, addr, reuse_address, loopback)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[op2(async, stack_trace)]
|
||||
#[serde]
|
||||
pub async fn op_net_connect_vsock<NP>(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
#[smi] cid: u32,
|
||||
#[smi] port: u32,
|
||||
) -> Result<(ResourceId, (u32, u32), (u32, u32)), NetError>
|
||||
where
|
||||
NP: NetPermissions + 'static,
|
||||
{
|
||||
use tokio_vsock::VsockAddr;
|
||||
use tokio_vsock::VsockStream;
|
||||
|
||||
super::check_unstable(
|
||||
&state.borrow(),
|
||||
r#"Deno.connect({ transport: "vsock" })"#,
|
||||
);
|
||||
|
||||
state.borrow_mut().borrow_mut::<NP>().check_vsock(
|
||||
cid,
|
||||
port,
|
||||
"Deno.connect()",
|
||||
)?;
|
||||
|
||||
let addr = VsockAddr::new(cid, port);
|
||||
let vsock_stream = VsockStream::connect(addr).await?;
|
||||
let local_addr = vsock_stream.local_addr()?;
|
||||
let remote_addr = vsock_stream.peer_addr()?;
|
||||
|
||||
let rid =
|
||||
state
|
||||
.borrow_mut()
|
||||
.resource_table
|
||||
.add(crate::io::VsockStreamResource::new(
|
||||
vsock_stream.into_split(),
|
||||
));
|
||||
|
||||
Ok((
|
||||
rid,
|
||||
(local_addr.cid(), local_addr.port()),
|
||||
(remote_addr.cid(), remote_addr.port()),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub fn op_net_connect_vsock<NP>() -> Result<(), NetError>
|
||||
where
|
||||
NP: NetPermissions + 'static,
|
||||
{
|
||||
Err(NetError::VsockUnsupported)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[op2(stack_trace)]
|
||||
#[serde]
|
||||
pub fn op_net_listen_vsock<NP>(
|
||||
state: &mut OpState,
|
||||
#[smi] cid: u32,
|
||||
#[smi] port: u32,
|
||||
) -> Result<(ResourceId, u32, u32), NetError>
|
||||
where
|
||||
NP: NetPermissions + 'static,
|
||||
{
|
||||
use tokio_vsock::VsockAddr;
|
||||
use tokio_vsock::VsockListener;
|
||||
|
||||
super::check_unstable(state, r#"Deno.listen({ transport: "vsock" })"#);
|
||||
|
||||
state
|
||||
.borrow_mut::<NP>()
|
||||
.check_vsock(cid, port, "Deno.listen()")?;
|
||||
|
||||
let addr = VsockAddr::new(cid, port);
|
||||
let listener = VsockListener::bind(addr)?;
|
||||
let local_addr = listener.local_addr()?;
|
||||
let listener_resource = NetworkListenerResource::new(listener);
|
||||
let rid = state.resource_table.add(listener_resource);
|
||||
Ok((rid, local_addr.cid(), local_addr.port()))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub fn op_net_listen_vsock<NP>() -> Result<(), NetError>
|
||||
where
|
||||
NP: NetPermissions + 'static,
|
||||
{
|
||||
Err(NetError::VsockUnsupported)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[op2(async)]
|
||||
#[serde]
|
||||
pub async fn op_net_accept_vsock(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
#[smi] rid: ResourceId,
|
||||
) -> Result<(ResourceId, (u32, u32), (u32, u32)), NetError> {
|
||||
use tokio_vsock::VsockListener;
|
||||
|
||||
let resource = state
|
||||
.borrow()
|
||||
.resource_table
|
||||
.get::<NetworkListenerResource<VsockListener>>(rid)
|
||||
.map_err(|_| NetError::ListenerClosed)?;
|
||||
let listener = RcRef::map(&resource, |r| &r.listener)
|
||||
.try_borrow_mut()
|
||||
.ok_or_else(|| NetError::AcceptTaskOngoing)?;
|
||||
let cancel = RcRef::map(resource, |r| &r.cancel);
|
||||
let (vsock_stream, _socket_addr) = listener
|
||||
.accept()
|
||||
.try_or_cancel(cancel)
|
||||
.await
|
||||
.map_err(accept_err)?;
|
||||
let local_addr = vsock_stream.local_addr()?;
|
||||
let remote_addr = vsock_stream.peer_addr()?;
|
||||
|
||||
let mut state = state.borrow_mut();
|
||||
let rid = state
|
||||
.resource_table
|
||||
.add(crate::io::VsockStreamResource::new(
|
||||
vsock_stream.into_split(),
|
||||
));
|
||||
Ok((
|
||||
rid,
|
||||
(local_addr.cid(), local_addr.port()),
|
||||
(remote_addr.cid(), remote_addr.port()),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub fn op_net_accept_vsock() -> Result<(), NetError> {
|
||||
Err(NetError::VsockUnsupported)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Eq, PartialEq, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum DnsReturnRecord {
|
||||
|
@ -1148,6 +1290,15 @@ mod tests {
|
|||
) -> Result<Cow<'a, Path>, PermissionCheckError> {
|
||||
Ok(Cow::Borrowed(p))
|
||||
}
|
||||
|
||||
fn check_vsock(
|
||||
&mut self,
|
||||
_cid: u32,
|
||||
_port: u32,
|
||||
_api_name: &str,
|
||||
) -> Result<(), PermissionCheckError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
|
|
|
@ -280,6 +280,14 @@ network_stream!(
|
|||
tokio::net::UnixListener,
|
||||
tokio::net::unix::SocketAddr,
|
||||
crate::io::UnixStreamResource
|
||||
],
|
||||
[
|
||||
Vsock,
|
||||
vsock,
|
||||
tokio_vsock::VsockStream,
|
||||
tokio_vsock::VsockListener,
|
||||
tokio_vsock::VsockAddr,
|
||||
crate::io::VsockStreamResource
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -307,6 +315,8 @@ pub enum NetworkStreamAddress {
|
|||
Ip(std::net::SocketAddr),
|
||||
#[cfg(unix)]
|
||||
Unix(tokio::net::unix::SocketAddr),
|
||||
#[cfg(unix)]
|
||||
Vsock(tokio_vsock::VsockAddr),
|
||||
}
|
||||
|
||||
impl From<std::net::SocketAddr> for NetworkStreamAddress {
|
||||
|
@ -322,6 +332,13 @@ impl From<tokio::net::unix::SocketAddr> for NetworkStreamAddress {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl From<tokio_vsock::VsockAddr> for NetworkStreamAddress {
|
||||
fn from(value: tokio_vsock::VsockAddr) -> Self {
|
||||
NetworkStreamAddress::Vsock(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
||||
pub enum TakeNetworkStreamError {
|
||||
#[class("Busy")]
|
||||
|
@ -334,6 +351,10 @@ pub enum TakeNetworkStreamError {
|
|||
#[class("Busy")]
|
||||
#[error("Unix socket is currently in use")]
|
||||
UnixBusy,
|
||||
#[cfg(unix)]
|
||||
#[class("Busy")]
|
||||
#[error("Vsock socket is currently in use")]
|
||||
VsockBusy,
|
||||
#[class(generic)]
|
||||
#[error(transparent)]
|
||||
ReuniteTcp(#[from] tokio::net::tcp::ReuniteError),
|
||||
|
@ -341,6 +362,10 @@ pub enum TakeNetworkStreamError {
|
|||
#[class(generic)]
|
||||
#[error(transparent)]
|
||||
ReuniteUnix(#[from] tokio::net::unix::ReuniteError),
|
||||
#[cfg(unix)]
|
||||
#[class(generic)]
|
||||
#[error("Cannot reunite halves from different streams")]
|
||||
ReuniteVsock,
|
||||
#[class(inherit)]
|
||||
#[error(transparent)]
|
||||
Resource(deno_core::error::ResourceError),
|
||||
|
@ -388,6 +413,21 @@ pub fn take_network_stream_resource(
|
|||
return Ok(NetworkStream::Unix(unix_stream));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
if let Ok(resource_rc) =
|
||||
resource_table.take::<crate::io::VsockStreamResource>(stream_rid)
|
||||
{
|
||||
// This Vsock socket might be used somewhere else.
|
||||
let resource = Rc::try_unwrap(resource_rc)
|
||||
.map_err(|_| TakeNetworkStreamError::VsockBusy)?;
|
||||
let (read_half, write_half) = resource.into_inner();
|
||||
if !read_half.is_pair_of(&write_half) {
|
||||
return Err(TakeNetworkStreamError::ReuniteVsock);
|
||||
}
|
||||
let vsock_stream = read_half.unsplit(write_half);
|
||||
return Ok(NetworkStream::Vsock(vsock_stream));
|
||||
}
|
||||
|
||||
Err(TakeNetworkStreamError::Resource(
|
||||
ResourceError::BadResourceId,
|
||||
))
|
||||
|
|
|
@ -817,6 +817,7 @@ pub struct WriteDescriptor(pub PathBuf);
|
|||
pub enum Host {
|
||||
Fqdn(FQDN),
|
||||
Ip(IpAddr),
|
||||
Vsock(u32),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
||||
|
@ -881,7 +882,7 @@ impl Host {
|
|||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
|
||||
pub struct NetDescriptor(pub Host, pub Option<u16>);
|
||||
pub struct NetDescriptor(pub Host, pub Option<u32>);
|
||||
|
||||
impl QueryDescriptor for NetDescriptor {
|
||||
type AllowDesc = NetDescriptor;
|
||||
|
@ -953,6 +954,8 @@ pub enum NetDescriptorParseError {
|
|||
Ipv6MissingSquareBrackets(String),
|
||||
#[error("{0}")]
|
||||
Host(#[from] HostParseError),
|
||||
#[error("invalid vsock: '{0}'")]
|
||||
InvalidVsock(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
||||
|
@ -967,6 +970,24 @@ pub enum NetDescriptorFromUrlParseError {
|
|||
|
||||
impl NetDescriptor {
|
||||
pub fn parse(hostname: &str) -> Result<Self, NetDescriptorParseError> {
|
||||
#[cfg(unix)]
|
||||
if let Some(vsock) = hostname.strip_prefix("vsock:") {
|
||||
let mut split = vsock.split(':');
|
||||
let Some(cid) = split.next().and_then(|c| {
|
||||
if c == "-1" {
|
||||
Some(u32::MAX)
|
||||
} else {
|
||||
c.parse().ok()
|
||||
}
|
||||
}) else {
|
||||
return Err(NetDescriptorParseError::InvalidVsock(hostname.into()));
|
||||
};
|
||||
let Some(port) = split.next().and_then(|p| p.parse().ok()) else {
|
||||
return Err(NetDescriptorParseError::InvalidVsock(hostname.into()));
|
||||
};
|
||||
return Ok(NetDescriptor(Host::Vsock(cid), Some(port)));
|
||||
}
|
||||
|
||||
if hostname.starts_with("http://") || hostname.starts_with("https://") {
|
||||
return Err(NetDescriptorParseError::Url(hostname.to_string()));
|
||||
}
|
||||
|
@ -995,7 +1016,10 @@ impl NetDescriptor {
|
|||
hostname.to_string(),
|
||||
));
|
||||
};
|
||||
return Ok(NetDescriptor(Host::Ip(IpAddr::V6(ip)), port));
|
||||
return Ok(NetDescriptor(
|
||||
Host::Ip(IpAddr::V6(ip)),
|
||||
port.map(Into::into),
|
||||
));
|
||||
} else {
|
||||
return Err(NetDescriptorParseError::InvalidHost(hostname.to_string()));
|
||||
}
|
||||
|
@ -1032,7 +1056,7 @@ impl NetDescriptor {
|
|||
Some(port)
|
||||
};
|
||||
|
||||
Ok(NetDescriptor(host, port))
|
||||
Ok(NetDescriptor(host, port.map(Into::into)))
|
||||
}
|
||||
|
||||
pub fn from_url(url: &Url) -> Result<Self, NetDescriptorFromUrlParseError> {
|
||||
|
@ -1041,7 +1065,14 @@ impl NetDescriptor {
|
|||
})?;
|
||||
let host = Host::parse(host)?;
|
||||
let port = url.port_or_known_default();
|
||||
Ok(NetDescriptor(host, port))
|
||||
Ok(NetDescriptor(host, port.map(Into::into)))
|
||||
}
|
||||
|
||||
pub fn from_vsock(
|
||||
cid: u32,
|
||||
port: u32,
|
||||
) -> Result<Self, NetDescriptorParseError> {
|
||||
Ok(NetDescriptor(Host::Vsock(cid), Some(port)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1051,6 +1082,7 @@ impl fmt::Display for NetDescriptor {
|
|||
Host::Fqdn(fqdn) => write!(f, "{fqdn}"),
|
||||
Host::Ip(IpAddr::V4(ip)) => write!(f, "{ip}"),
|
||||
Host::Ip(IpAddr::V6(ip)) => write!(f, "[{ip}]"),
|
||||
Host::Vsock(cid) => write!(f, "vsock:{cid}"),
|
||||
}?;
|
||||
if let Some(port) = self.1 {
|
||||
write!(f, ":{}", port)?;
|
||||
|
@ -2933,11 +2965,27 @@ impl PermissionsContainer {
|
|||
let inner = &mut inner.net;
|
||||
skip_check_if_is_permission_fully_granted!(inner);
|
||||
let hostname = Host::parse(host.0.as_ref())?;
|
||||
let descriptor = NetDescriptor(hostname, host.1);
|
||||
let descriptor = NetDescriptor(hostname, host.1.map(Into::into));
|
||||
inner.check(&descriptor, Some(api_name))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn check_net_vsock(
|
||||
&mut self,
|
||||
cid: u32,
|
||||
port: u32,
|
||||
api_name: &str,
|
||||
) -> Result<(), PermissionCheckError> {
|
||||
let mut inner = self.inner.lock();
|
||||
if inner.net.is_allow_all() {
|
||||
return Ok(());
|
||||
}
|
||||
let desc = NetDescriptor(Host::Vsock(cid), Some(port));
|
||||
inner.net.check(&desc, Some(api_name))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn check_ffi(
|
||||
&mut self,
|
||||
|
|
|
@ -150,6 +150,15 @@ impl deno_net::NetPermissions for Permissions {
|
|||
) -> Result<Cow<'a, Path>, PermissionCheckError> {
|
||||
unreachable!("snapshotting!")
|
||||
}
|
||||
|
||||
fn check_vsock(
|
||||
&mut self,
|
||||
_cid: u32,
|
||||
_port: u32,
|
||||
_api_name: &str,
|
||||
) -> Result<(), PermissionCheckError> {
|
||||
unreachable!("snapshotting!")
|
||||
}
|
||||
}
|
||||
|
||||
impl deno_fs::FsPermissions for Permissions {
|
||||
|
|
|
@ -4045,6 +4045,58 @@ Deno.test(
|
|||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
{
|
||||
ignore: Deno.build.os !== "linux",
|
||||
permissions: { run: true, net: true },
|
||||
},
|
||||
async function httpServerVsockSocket() {
|
||||
const { promise, resolve } = Promise.withResolvers<Deno.VsockAddr>();
|
||||
const ac = new AbortController();
|
||||
await using server = Deno.serve(
|
||||
{
|
||||
signal: ac.signal,
|
||||
cid: -1,
|
||||
port: 8000,
|
||||
onListen(info) {
|
||||
resolve(info);
|
||||
},
|
||||
onError: createOnErrorCb(ac),
|
||||
},
|
||||
(_req, { remoteAddr }) => {
|
||||
assertEquals(remoteAddr.transport, "vsock");
|
||||
assertEquals(remoteAddr.cid, 1);
|
||||
assertEquals(remoteAddr.port, conn.localAddr.port);
|
||||
return new Response("hello world!");
|
||||
},
|
||||
);
|
||||
|
||||
assertEquals((await promise).cid, 4294967295);
|
||||
assertEquals((await promise).port, 8000);
|
||||
|
||||
const conn = await Deno.connect({
|
||||
transport: "vsock",
|
||||
cid: 1,
|
||||
port: 8000,
|
||||
});
|
||||
await conn.write(
|
||||
new TextEncoder().encode("GET / HTTP/1.1\r\nhost: example.com\r\n\r\n"),
|
||||
);
|
||||
const data = new Uint8Array(512);
|
||||
const n = await conn.read(data);
|
||||
const body = new TextDecoder().decode(data.subarray(0, n!));
|
||||
|
||||
assertEquals(
|
||||
"hello world!",
|
||||
body.split("\r\n").at(-1),
|
||||
);
|
||||
|
||||
await conn.close();
|
||||
ac.abort();
|
||||
await server.finished;
|
||||
},
|
||||
);
|
||||
|
||||
// serve Handler must return Response class or promise that resolves Response class
|
||||
Deno.test(
|
||||
{ permissions: { net: true, run: true } },
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue