summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/dts/lib.deno.unstable.d.ts16
-rw-r--r--cli/tests/unit/net_test.ts45
-rw-r--r--cli/tests/unit/tls_test.ts66
-rw-r--r--ext/net/01_net.js2
-rw-r--r--ext/net/02_tls.js3
-rw-r--r--ext/net/lib.deno_net.d.ts9
-rw-r--r--ext/net/ops.rs8
-rw-r--r--ext/net/ops_tls.rs9
8 files changed, 154 insertions, 4 deletions
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index cde76d4d8..2bcba88da 100644
--- a/cli/dts/lib.deno.unstable.d.ts
+++ b/cli/dts/lib.deno.unstable.d.ts
@@ -1158,6 +1158,22 @@ declare namespace Deno {
[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 options which can be set when opening a Unix listener via
diff --git a/cli/tests/unit/net_test.ts b/cli/tests/unit/net_test.ts
index d2beb5566..8d004f424 100644
--- a/cli/tests/unit/net_test.ts
+++ b/cli/tests/unit/net_test.ts
@@ -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();
+});
diff --git a/cli/tests/unit/tls_test.ts b/cli/tests/unit/tls_test.ts
index ee7636954..d3ef4d0bf 100644
--- a/cli/tests/unit/tls_test.ts
+++ b/cli/tests/unit/tls_test.ts
@@ -1410,3 +1410,69 @@ Deno.test(
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();
+});
diff --git a/ext/net/01_net.js b/ext/net/01_net.js
index acd8ee179..765b94035 100644
--- a/ext/net/01_net.js
+++ b/ext/net/01_net.js
@@ -302,7 +302,7 @@
const [rid, addr] = ops.op_net_listen_tcp({
hostname: args.hostname ?? "0.0.0.0",
port: args.port,
- });
+ }, args.reusePort);
addr.transport = "tcp";
return new Listener(rid, addr);
}
diff --git a/ext/net/02_tls.js b/ext/net/02_tls.js
index d3f906dbd..89913d1af 100644
--- a/ext/net/02_tls.js
+++ b/ext/net/02_tls.js
@@ -65,13 +65,14 @@
hostname = "0.0.0.0",
transport = "tcp",
alpnProtocols = undefined,
+ reusePort = false,
}) {
if (transport !== "tcp") {
throw new TypeError(`Unsupported transport: '${transport}'`);
}
const [rid, localAddr] = ops.op_net_listen_tls(
{ hostname, port },
- { cert, certFile, key, keyFile, alpnProtocols },
+ { cert, certFile, key, keyFile, alpnProtocols, reusePort },
);
return new TlsListener(rid, localAddr);
}
diff --git a/ext/net/lib.deno_net.d.ts b/ext/net/lib.deno_net.d.ts
index df569f93a..1941c0426 100644
--- a/ext/net/lib.deno_net.d.ts
+++ b/ext/net/lib.deno_net.d.ts
@@ -91,6 +91,11 @@ declare namespace Deno {
hostname?: string;
}
+ /** @category Network */
+ // deno-lint-ignore no-empty-interface
+ export interface TcpListenOptions extends ListenOptions {
+ }
+
/** Listen announces on the local transport address.
*
* ```ts
@@ -106,11 +111,11 @@ declare namespace Deno {
* @category Network
*/
export function listen(
- options: ListenOptions & { transport?: "tcp" },
+ options: TcpListenOptions & { transport?: "tcp" },
): Listener;
/** @category Network */
- export interface ListenTlsOptions extends ListenOptions {
+ export interface ListenTlsOptions extends TcpListenOptions {
/** Server private key in PEM format */
key?: string;
/** Cert chain in PEM format */
diff --git a/ext/net/ops.rs b/ext/net/ops.rs
index 9a6d95586..e6420bf9e 100644
--- a/ext/net/ops.rs
+++ b/ext/net/ops.rs
@@ -246,10 +246,14 @@ impl Resource for UdpSocketResource {
fn op_net_listen_tcp<NP>(
state: &mut OpState,
addr: IpAddr,
+ reuse_port: bool,
) -> Result<(ResourceId, IpAddr), AnyError>
where
NP: NetPermissions + 'static,
{
+ if reuse_port {
+ super::check_unstable(state, "Deno.listen({ reusePort: true })");
+ }
state
.borrow_mut::<NP>()
.check_net(&(&addr.hostname, Some(addr.port)), "Deno.listen()")?;
@@ -264,6 +268,10 @@ where
let socket = Socket::new(domain, Type::STREAM, None)?;
#[cfg(not(windows))]
socket.set_reuse_address(true)?;
+ if reuse_port {
+ #[cfg(target_os = "linux")]
+ socket.set_reuse_port(true)?;
+ }
let socket_addr = socket2::SockAddr::from(addr);
socket.bind(&socket_addr)?;
socket.listen(128)?;
diff --git a/ext/net/ops_tls.rs b/ext/net/ops_tls.rs
index b27426894..1e2b54533 100644
--- a/ext/net/ops_tls.rs
+++ b/ext/net/ops_tls.rs
@@ -990,6 +990,7 @@ pub struct ListenTlsArgs {
// TODO(kt3k): Remove this option at v2.0.
key_file: Option<String>,
alpn_protocols: Option<Vec<String>>,
+ reuse_port: bool,
}
#[op]
@@ -1001,6 +1002,10 @@ pub fn op_net_listen_tls<NP>(
where
NP: NetPermissions + 'static,
{
+ if args.reuse_port {
+ super::check_unstable(state, "Deno.listenTls({ reusePort: true })");
+ }
+
let cert_file = args.cert_file.as_deref();
let key_file = args.key_file.as_deref();
let cert = args.cert.as_deref();
@@ -1061,6 +1066,10 @@ where
let socket = Socket::new(domain, Type::STREAM, None)?;
#[cfg(not(windows))]
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);
socket.bind(&socket_addr)?;
socket.listen(128)?;