feat(unstable): fetch tunnel proxy

This commit is contained in:
snek 2025-09-18 17:05:05 +02:00
parent ebcb2fa294
commit 2e9daab3ea
No known key found for this signature in database
10 changed files with 81 additions and 25 deletions

1
Cargo.lock generated
View file

@ -2106,6 +2106,7 @@ dependencies = [
"deno_core",
"deno_error",
"deno_fs",
"deno_net",
"deno_path_util",
"deno_permissions",
"deno_tls",

View file

@ -6387,6 +6387,9 @@ declare namespace Deno {
cid: number;
/** The port of the vsock to connect to. */
port: number;
} | {
transport: "tunnel";
kind: "agent";
};
/**

View file

@ -77,7 +77,8 @@ function createHttpClient(options) {
}
case "tcp":
case "unix":
case "vsock": {
case "vsock":
case "tunnel": {
break;
}
default: {

View file

@ -20,6 +20,7 @@ data-url.workspace = true
deno_core.workspace = true
deno_error.workspace = true
deno_fs.workspace = true
deno_net.workspace = true
deno_path_util.workspace = true
deno_permissions.workspace = true
deno_tls.workspace = true

View file

@ -972,6 +972,7 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check_net_vsock(*cid, *port, "Deno.createHttpClient()")?;
}
Proxy::Tunnel { .. } => {}
}
}
@ -1144,16 +1145,15 @@ pub fn create_http_client(
}
intercept
}
Proxy::Tcp {
hostname: host,
port,
} => {
let target = proxy::Target::new_tcp(host, port);
Proxy::Tcp { hostname, port } => {
let target = proxy::Target::Tcp { hostname, port };
proxy::Intercept::all(target)
}
#[cfg(not(windows))]
Proxy::Unix { path } => {
let target = proxy::Target::new_unix(PathBuf::from(path));
let target = proxy::Target::Unix {
path: PathBuf::from(path),
};
proxy::Intercept::all(target)
}
#[cfg(windows)]
@ -1166,7 +1166,7 @@ pub fn create_http_client(
target_os = "macos"
))]
Proxy::Vsock { cid, port } => {
let target = proxy::Target::new_vsock(cid, port);
let target = proxy::Target::Vsock { cid, port };
proxy::Intercept::all(target)
}
#[cfg(not(any(
@ -1177,6 +1177,7 @@ pub fn create_http_client(
Proxy::Vsock { .. } => {
return Err(HttpClientCreateError::VsockProxyNotSupported);
}
Proxy::Tunnel { .. } => proxy::Intercept::all(proxy::Target::Tunnel),
};
proxies.prepend(intercept);
}

View file

@ -128,6 +128,7 @@ pub(crate) enum Target {
cid: u32,
port: u32,
},
Tunnel,
}
#[derive(Debug, Clone, Copy)]
@ -242,6 +243,9 @@ impl Intercept {
Target::Vsock { .. } => {
// Auth not supported for Vsock sockets
}
Target::Tunnel => {
// Auth not supported for Vsock sockets
}
}
}
}
@ -339,20 +343,6 @@ impl Target {
Some(target)
}
pub(crate) fn new_tcp(hostname: String, port: u16) -> Self {
Target::Tcp { hostname, port }
}
#[cfg(not(windows))]
pub(crate) fn new_unix(path: PathBuf) -> Self {
Target::Unix { path }
}
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))]
pub(crate) fn new_vsock(cid: u32, port: u32) -> Self {
Target::Vsock { cid, port }
}
}
#[derive(Debug)]
@ -560,6 +550,8 @@ pub enum Proxied<T> {
/// Forwarded via Vsock socket
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))]
Vsock(TokioIo<VsockStream>),
/// Forwarded through tunnel
Tunnel(TokioIo<deno_net::tunnel::TunnelStream>),
}
impl<C> Service<Uri> for ProxyConnector<C>
@ -691,6 +683,15 @@ where
let io = VsockStream::connect(addr).await?;
Ok(Proxied::Vsock(TokioIo::new(io)))
}),
Target::Tunnel => Box::pin(async move {
let Some(tunnel) = deno_net::tunnel::get_tunnel() else {
return Err("tunnel is not connected".into());
};
let stream = tunnel.create_agent_stream().await?;
Ok(Proxied::Tunnel(TokioIo::new(stream)))
}),
};
}
@ -808,6 +809,7 @@ where
target_os = "macos"
))]
Proxied::Vsock(ref mut p) => Pin::new(p).poll_read(cx, buf),
Proxied::Tunnel(ref mut p) => Pin::new(p).poll_read(cx, buf),
}
}
}
@ -835,6 +837,7 @@ where
target_os = "macos"
))]
Proxied::Vsock(ref mut p) => Pin::new(p).poll_write(cx, buf),
Proxied::Tunnel(ref mut p) => Pin::new(p).poll_write(cx, buf),
}
}
@ -856,6 +859,7 @@ where
target_os = "macos"
))]
Proxied::Vsock(ref mut p) => Pin::new(p).poll_flush(cx),
Proxied::Tunnel(ref mut p) => Pin::new(p).poll_flush(cx),
}
}
@ -877,6 +881,7 @@ where
target_os = "macos"
))]
Proxied::Vsock(ref mut p) => Pin::new(p).poll_shutdown(cx),
Proxied::Tunnel(ref mut p) => Pin::new(p).poll_shutdown(cx),
}
}
@ -895,6 +900,7 @@ where
target_os = "macos"
))]
Proxied::Vsock(ref p) => p.is_write_vectored(),
Proxied::Tunnel(ref p) => p.is_write_vectored(),
}
}
@ -921,6 +927,7 @@ where
target_os = "macos"
))]
Proxied::Vsock(ref mut p) => Pin::new(p).poll_write_vectored(cx, bufs),
Proxied::Tunnel(ref mut p) => Pin::new(p).poll_write_vectored(cx, bufs),
}
}
}
@ -958,6 +965,7 @@ where
target_os = "macos"
))]
Proxied::Vsock(_) => Connected::new().proxy(true),
Proxied::Tunnel(_) => Connected::new().proxy(true),
}
}
}

View file

@ -224,6 +224,12 @@ impl ServerCertVerifier for NoServerNameVerification {
}
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub enum TunnelKind {
Agent,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase", tag = "transport")]
pub enum Proxy {
@ -243,6 +249,9 @@ pub enum Proxy {
cid: u32,
port: u32,
},
Tunnel {
kind: TunnelKind,
},
}
#[derive(Deserialize, Default, Debug, Clone)]

View file

@ -1,11 +1,18 @@
let serveAddr;
const client = Deno.createHttpClient({
proxy: { transport: "tunnel", kind: "agent" },
});
Deno.serve({
onListen(addr) {
serveAddr = addr;
},
}, async (req, info) => {
const headers = Object.fromEntries(req.headers);
await fetch("http://meow.com", { client });
return Response.json({
method: req.method,
url: req.url,

View file

@ -1,4 +1,12 @@
You are connected to https://localhost:[WILDLINE]
GET http://meow.com/ HTTP/1.1
accept: */*
accept-language: *
user-agent: Deno/[WILDCARD]
accept-encoding: gzip,br
host: meow.com
HTTP/1.1 200 OK
content-type: application/json
vary: Accept-Encoding

View file

@ -43,10 +43,10 @@ for await (const incoming of listener) {
async function handleConnection(incoming: Deno.QuicIncoming) {
const conn = await incoming.accept();
const incomingBidi = conn.incomingBidirectionalStreams.getReader();
{
const { value: bi } = await conn.incomingBidirectionalStreams
.getReader()
.read();
const { value: bi } = await incomingBidi.read();
const reader = bi.readable.getReader({ mode: "byob" });
const version = await readUint32LE(reader);
@ -105,6 +105,23 @@ async function handleConnection(incoming: Deno.QuicIncoming) {
new TextEncoder().encode(`GET / HTTP/1.1\r\nHost: localhost\r\n\r\n`),
);
{
const { value: agentStream } = await incomingBidi.read();
const reader = agentStream.readable.getReader({ mode: "byob" });
const agentHeader = await readStreamHeader(reader);
if (agentHeader.headerType !== "Agent") {
conn.close({ closeCode: 1, reason: "expected Agent" });
return;
}
const { value } = await reader.read(new Uint8Array(1024));
console.log(new TextDecoder().decode(value));
await agentStream.writable.getWriter().write(
new TextEncoder().encode(
`HTTP/1.1 201 No Content\r\nConnection: close\r\n\r\n`,
),
);
}
const reader = stream.readable.getReader({ mode: "byob" });
const { value } = await reader.read(new Uint8Array(1024));
console.log(new TextDecoder().decode(value));