1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 00:29:09 -05:00

feat(ext/net): reusePort for TCP on Linux (#16398)

This commit is contained in:
Luca Casonato 2022-10-26 21:04:27 +02:00 committed by GitHub
parent de580cedd2
commit f4f1f4f0b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 154 additions and 4 deletions

View file

@ -1158,6 +1158,22 @@ declare namespace Deno {
[Symbol.asyncIterator](): AsyncIterableIterator<[Uint8Array, Addr]>; [Symbol.asyncIterator](): AsyncIterableIterator<[Uint8Array, Addr]>;
} }
/**
* @category Network
*/
export interface TcpListenOptions extends ListenOptions {
/** When `true` the SO_REUSEPORT flag will be set on the listener. This
* allows multiple processes to listen on the same address and port.
*
* On Linux this will cause the kernel to distribute incoming connections
* across the different processes that are listening on the same address and
* port.
*
* This flag is only supported on Linux. It is silently ignored on other
* platforms. Defaults to `false`. */
reusePort?: boolean;
}
/** **UNSTABLE**: New API, yet to be vetted. /** **UNSTABLE**: New API, yet to be vetted.
* *
* Unstable options which can be set when opening a Unix listener via * Unstable options which can be set when opening a Unix listener via

View file

@ -1005,3 +1005,48 @@ Deno.test(
} }
}, },
); );
Deno.test({
ignore: Deno.build.os !== "linux",
permissions: { net: true },
}, async function netTcpListenReusePort() {
const port = 4003;
const listener1 = Deno.listen({ port, reusePort: true });
const listener2 = Deno.listen({ port, reusePort: true });
let p1;
let p2;
let listener1Recv = false;
let listener2Recv = false;
while (!listener1Recv || !listener2Recv) {
if (!p1) {
p1 = listener1.accept().then((conn) => {
conn.close();
listener1Recv = true;
p1 = undefined;
}).catch(() => {});
}
if (!p2) {
p2 = listener2.accept().then((conn) => {
conn.close();
listener2Recv = true;
p2 = undefined;
}).catch(() => {});
}
const conn = await Deno.connect({ port });
conn.close();
await Promise.race([p1, p2]);
}
listener1.close();
listener2.close();
});
Deno.test({
ignore: Deno.build.os === "linux",
permissions: { net: true },
}, function netTcpListenReusePortDoesNothing() {
const listener1 = Deno.listen({ port: 4003, reusePort: true });
assertThrows(() => {
Deno.listen({ port: 4003, reusePort: true });
}, Deno.errors.AddrInUse);
listener1.close();
});

View file

@ -1410,3 +1410,69 @@ Deno.test(
listener2.close(); listener2.close();
}, },
); );
Deno.test({
ignore: Deno.build.os !== "linux",
permissions: { net: true },
}, async function listenTlsReusePort() {
const hostname = "localhost";
const port = 4003;
const listener1 = Deno.listenTls({
hostname,
port,
cert,
key,
reusePort: true,
});
const listener2 = Deno.listenTls({
hostname,
port,
cert,
key,
reusePort: true,
});
let p1;
let p2;
let listener1Recv = false;
let listener2Recv = false;
while (!listener1Recv || !listener2Recv) {
if (!p1) {
p1 = listener1.accept().then((conn) => {
conn.close();
listener1Recv = true;
p1 = undefined;
}).catch(() => {});
}
if (!p2) {
p2 = listener2.accept().then((conn) => {
conn.close();
listener2Recv = true;
p2 = undefined;
}).catch(() => {});
}
const conn = await Deno.connectTls({ hostname, port, caCerts });
conn.close();
await Promise.race([p1, p2]);
}
listener1.close();
listener2.close();
});
Deno.test({
ignore: Deno.build.os === "linux",
permissions: { net: true },
}, function listenTlsReusePortDoesNothing() {
const hostname = "localhost";
const port = 4003;
const listener1 = Deno.listenTls({
hostname,
port,
cert,
key,
reusePort: true,
});
assertThrows(() => {
Deno.listenTls({ hostname, port, cert, key, reusePort: true });
}, Deno.errors.AddrInUse);
listener1.close();
});

View file

@ -302,7 +302,7 @@
const [rid, addr] = ops.op_net_listen_tcp({ const [rid, addr] = ops.op_net_listen_tcp({
hostname: args.hostname ?? "0.0.0.0", hostname: args.hostname ?? "0.0.0.0",
port: args.port, port: args.port,
}); }, args.reusePort);
addr.transport = "tcp"; addr.transport = "tcp";
return new Listener(rid, addr); return new Listener(rid, addr);
} }

View file

@ -65,13 +65,14 @@
hostname = "0.0.0.0", hostname = "0.0.0.0",
transport = "tcp", transport = "tcp",
alpnProtocols = undefined, alpnProtocols = undefined,
reusePort = false,
}) { }) {
if (transport !== "tcp") { if (transport !== "tcp") {
throw new TypeError(`Unsupported transport: '${transport}'`); throw new TypeError(`Unsupported transport: '${transport}'`);
} }
const [rid, localAddr] = ops.op_net_listen_tls( const [rid, localAddr] = ops.op_net_listen_tls(
{ hostname, port }, { hostname, port },
{ cert, certFile, key, keyFile, alpnProtocols }, { cert, certFile, key, keyFile, alpnProtocols, reusePort },
); );
return new TlsListener(rid, localAddr); return new TlsListener(rid, localAddr);
} }

View file

@ -91,6 +91,11 @@ declare namespace Deno {
hostname?: string; hostname?: string;
} }
/** @category Network */
// deno-lint-ignore no-empty-interface
export interface TcpListenOptions extends ListenOptions {
}
/** Listen announces on the local transport address. /** Listen announces on the local transport address.
* *
* ```ts * ```ts
@ -106,11 +111,11 @@ declare namespace Deno {
* @category Network * @category Network
*/ */
export function listen( export function listen(
options: ListenOptions & { transport?: "tcp" }, options: TcpListenOptions & { transport?: "tcp" },
): Listener; ): Listener;
/** @category Network */ /** @category Network */
export interface ListenTlsOptions extends ListenOptions { export interface ListenTlsOptions extends TcpListenOptions {
/** Server private key in PEM format */ /** Server private key in PEM format */
key?: string; key?: string;
/** Cert chain in PEM format */ /** Cert chain in PEM format */

View file

@ -246,10 +246,14 @@ impl Resource for UdpSocketResource {
fn op_net_listen_tcp<NP>( fn op_net_listen_tcp<NP>(
state: &mut OpState, state: &mut OpState,
addr: IpAddr, addr: IpAddr,
reuse_port: bool,
) -> Result<(ResourceId, IpAddr), AnyError> ) -> Result<(ResourceId, IpAddr), AnyError>
where where
NP: NetPermissions + 'static, NP: NetPermissions + 'static,
{ {
if reuse_port {
super::check_unstable(state, "Deno.listen({ reusePort: true })");
}
state state
.borrow_mut::<NP>() .borrow_mut::<NP>()
.check_net(&(&addr.hostname, Some(addr.port)), "Deno.listen()")?; .check_net(&(&addr.hostname, Some(addr.port)), "Deno.listen()")?;
@ -264,6 +268,10 @@ where
let socket = Socket::new(domain, Type::STREAM, None)?; let socket = Socket::new(domain, Type::STREAM, None)?;
#[cfg(not(windows))] #[cfg(not(windows))]
socket.set_reuse_address(true)?; socket.set_reuse_address(true)?;
if reuse_port {
#[cfg(target_os = "linux")]
socket.set_reuse_port(true)?;
}
let socket_addr = socket2::SockAddr::from(addr); let socket_addr = socket2::SockAddr::from(addr);
socket.bind(&socket_addr)?; socket.bind(&socket_addr)?;
socket.listen(128)?; socket.listen(128)?;

View file

@ -990,6 +990,7 @@ pub struct ListenTlsArgs {
// TODO(kt3k): Remove this option at v2.0. // TODO(kt3k): Remove this option at v2.0.
key_file: Option<String>, key_file: Option<String>,
alpn_protocols: Option<Vec<String>>, alpn_protocols: Option<Vec<String>>,
reuse_port: bool,
} }
#[op] #[op]
@ -1001,6 +1002,10 @@ pub fn op_net_listen_tls<NP>(
where where
NP: NetPermissions + 'static, NP: NetPermissions + 'static,
{ {
if args.reuse_port {
super::check_unstable(state, "Deno.listenTls({ reusePort: true })");
}
let cert_file = args.cert_file.as_deref(); let cert_file = args.cert_file.as_deref();
let key_file = args.key_file.as_deref(); let key_file = args.key_file.as_deref();
let cert = args.cert.as_deref(); let cert = args.cert.as_deref();
@ -1061,6 +1066,10 @@ where
let socket = Socket::new(domain, Type::STREAM, None)?; let socket = Socket::new(domain, Type::STREAM, None)?;
#[cfg(not(windows))] #[cfg(not(windows))]
socket.set_reuse_address(true)?; socket.set_reuse_address(true)?;
if args.reuse_port {
#[cfg(target_os = "linux")]
socket.set_reuse_port(true)?;
}
let socket_addr = socket2::SockAddr::from(bind_addr); let socket_addr = socket2::SockAddr::from(bind_addr);
socket.bind(&socket_addr)?; socket.bind(&socket_addr)?;
socket.listen(128)?; socket.listen(128)?;