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_core",
"deno_error", "deno_error",
"deno_fs", "deno_fs",
"deno_net",
"deno_path_util", "deno_path_util",
"deno_permissions", "deno_permissions",
"deno_tls", "deno_tls",

View file

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

View file

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

View file

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

View file

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

View file

@ -128,6 +128,7 @@ pub(crate) enum Target {
cid: u32, cid: u32,
port: u32, port: u32,
}, },
Tunnel,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -242,6 +243,9 @@ impl Intercept {
Target::Vsock { .. } => { Target::Vsock { .. } => {
// Auth not supported for Vsock sockets // Auth not supported for Vsock sockets
} }
Target::Tunnel => {
// Auth not supported for Vsock sockets
}
} }
} }
} }
@ -339,20 +343,6 @@ impl Target {
Some(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)] #[derive(Debug)]
@ -560,6 +550,8 @@ pub enum Proxied<T> {
/// Forwarded via Vsock socket /// Forwarded via Vsock socket
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))]
Vsock(TokioIo<VsockStream>), Vsock(TokioIo<VsockStream>),
/// Forwarded through tunnel
Tunnel(TokioIo<deno_net::tunnel::TunnelStream>),
} }
impl<C> Service<Uri> for ProxyConnector<C> impl<C> Service<Uri> for ProxyConnector<C>
@ -691,6 +683,15 @@ where
let io = VsockStream::connect(addr).await?; let io = VsockStream::connect(addr).await?;
Ok(Proxied::Vsock(TokioIo::new(io))) 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" target_os = "macos"
))] ))]
Proxied::Vsock(ref mut p) => Pin::new(p).poll_read(cx, buf), 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" target_os = "macos"
))] ))]
Proxied::Vsock(ref mut p) => Pin::new(p).poll_write(cx, buf), 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" target_os = "macos"
))] ))]
Proxied::Vsock(ref mut p) => Pin::new(p).poll_flush(cx), 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" target_os = "macos"
))] ))]
Proxied::Vsock(ref mut p) => Pin::new(p).poll_shutdown(cx), 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" target_os = "macos"
))] ))]
Proxied::Vsock(ref p) => p.is_write_vectored(), Proxied::Vsock(ref p) => p.is_write_vectored(),
Proxied::Tunnel(ref p) => p.is_write_vectored(),
} }
} }
@ -921,6 +927,7 @@ where
target_os = "macos" target_os = "macos"
))] ))]
Proxied::Vsock(ref mut p) => Pin::new(p).poll_write_vectored(cx, bufs), 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" target_os = "macos"
))] ))]
Proxied::Vsock(_) => Connected::new().proxy(true), 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)] #[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase", tag = "transport")] #[serde(rename_all = "camelCase", tag = "transport")]
pub enum Proxy { pub enum Proxy {
@ -243,6 +249,9 @@ pub enum Proxy {
cid: u32, cid: u32,
port: u32, port: u32,
}, },
Tunnel {
kind: TunnelKind,
},
} }
#[derive(Deserialize, Default, Debug, Clone)] #[derive(Deserialize, Default, Debug, Clone)]

View file

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

View file

@ -1,4 +1,12 @@
You are connected to https://localhost:[WILDLINE] 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 HTTP/1.1 200 OK
content-type: application/json content-type: application/json
vary: Accept-Encoding vary: Accept-Encoding

View file

@ -43,10 +43,10 @@ for await (const incoming of listener) {
async function handleConnection(incoming: Deno.QuicIncoming) { async function handleConnection(incoming: Deno.QuicIncoming) {
const conn = await incoming.accept(); const conn = await incoming.accept();
const incomingBidi = conn.incomingBidirectionalStreams.getReader();
{ {
const { value: bi } = await conn.incomingBidirectionalStreams const { value: bi } = await incomingBidi.read();
.getReader()
.read();
const reader = bi.readable.getReader({ mode: "byob" }); const reader = bi.readable.getReader({ mode: "byob" });
const version = await readUint32LE(reader); 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`), 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 reader = stream.readable.getReader({ mode: "byob" });
const { value } = await reader.read(new Uint8Array(1024)); const { value } = await reader.read(new Uint8Array(1024));
console.log(new TextDecoder().decode(value)); console.log(new TextDecoder().decode(value));