/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
tests/unit/net_test.ts
1 882 строки
52 KB
Nathan Whitaker
fix(net): clean up cancellation resources on early errors (#36229)
23 июл 2026, 04:09
Не верифицирован
23 июл 2026, 04:09
18f5fe1
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. import { assert, assertEquals, assertGreaterOrEqual, assertRejects, assertStringIncludes, assertThrows, delay, execCode, execCode2, execCode3, tmpUnixSocketPath, } from "./test_util.ts"; // Since these tests may run in parallel, ensure this port is unique to this file const listenPort = 4503; const listenPort2 = 4504; let isCI: boolean; try { isCI = Deno.env.get("CI") !== undefined; } catch { isCI = true; } Deno.test({ permissions: { net: true } }, function netTcpListenClose() { const listener = Deno.listen({ hostname: "127.0.0.1", port: listenPort }); assert(listener.addr.transport === "tcp"); assertEquals(listener.addr.hostname, "127.0.0.1"); assertEquals(listener.addr.port, listenPort); listener.close(); }); Deno.test( { permissions: { net: true }, }, function netTcpListenEphemeralClose() { const listener = Deno.listen({ hostname: "127.0.0.1" }); assert(listener.addr.transport === "tcp"); assertEquals(listener.addr.hostname, "127.0.0.1"); // Ephemeral port range starts at 49152 according to RFC 6335 / IANA, but // many OSes use the lower number below (old OSes also started at 1024) assertGreaterOrEqual(listener.addr.port, 32768); listener.close(); }, ); Deno.test( { permissions: { net: true }, }, function netUdpListenClose() { const socket = Deno.listenDatagram({ hostname: "127.0.0.1", port: listenPort, transport: "udp", }); assert(socket.addr.transport === "udp"); assertEquals(socket.addr.hostname, "127.0.0.1"); assertEquals(socket.addr.port, listenPort); socket.close(); }, ); Deno.test( { permissions: { net: true }, }, function netUdpListenEphemeralClose() { const socket = Deno.listenDatagram({ hostname: "127.0.0.1", transport: "udp", }); assert(socket.addr.transport === "udp"); assertEquals(socket.addr.hostname, "127.0.0.1"); // Ephemeral port range starts at 49152 according to RFC 6335 / IANA, but // many OSes use the lower number below (old OSes also started at 1024) assertGreaterOrEqual(socket.addr.port, 32768); socket.close(); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, net: true }, }, function netUnixListenClose() { const filePath = tmpUnixSocketPath(); const socket = Deno.listen({ path: filePath, transport: "unix", }); assert(socket.addr.transport === "unix"); assertEquals(socket.addr.path, filePath); socket.close(); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, net: true }, }, function netUnixPacketListenClose() { const filePath = tmpUnixSocketPath(); const socket = Deno.listenDatagram({ path: filePath, transport: "unixpacket", }); assert(socket.addr.transport === "unixpacket"); assertEquals(socket.addr.path, filePath); socket.close(); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true }, }, function netUnixPacketAnonListenClose() { const socket = Deno.listenDatagram({ transport: "unixpacket", }); assert(socket.addr.transport === "unixpacket"); assertEquals(socket.addr.path, null); socket.close(); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: false }, }, function netUnixListenWritePermission() { assertThrows(() => { const filePath = tmpUnixSocketPath(); const socket = Deno.listen({ path: filePath, transport: "unix", }); assert(socket.addr.transport === "unix"); assertEquals(socket.addr.path, filePath); socket.close(); }, Deno.errors.NotCapable); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: false }, }, function netUnixPacketListenWritePermission() { assertThrows(() => { const filePath = tmpUnixSocketPath(); const socket = Deno.listenDatagram({ path: filePath, transport: "unixpacket", }); assert(socket.addr.transport === "unixpacket"); assertEquals(socket.addr.path, filePath); socket.close(); }, Deno.errors.NotCapable); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: false }, }, function netUnixPacketAnonListenWritePermission() { const socket = Deno.listenDatagram({ transport: "unixpacket", }); assert(socket.addr.transport === "unixpacket"); assertEquals(socket.addr.path, null); socket.close(); }, ); Deno.test( { permissions: { net: true }, }, async function netTcpCloseWhileAccept() { const listener = Deno.listen({ port: listenPort }); const p = listener.accept(); listener.close(); await assertRejects( () => p, Deno.errors.BadResource, "Listener has been closed", ); }, ); Deno.test( { permissions: { net: true }, }, async function netTcpListenAfterCloseWhileAcceptSettles() { const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 }); const { hostname, port } = listener.addr as Deno.NetAddr; const acceptPromise = listener.accept(); listener.close(); await assertRejects( () => acceptPromise, Deno.errors.BadResource, "Listener has been closed", ); const listener2 = Deno.listen({ hostname, port }); listener2.close(); }, ); Deno.test( { permissions: { net: true }, }, async function netTcpListenAfterCloseWhileAsyncIteratorSettles() { const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 }); const { hostname, port } = listener.addr as Deno.NetAddr; const nextPromise = listener[Symbol.asyncIterator]().next(); listener.close(); assertEquals(await nextPromise, { value: undefined, done: true }); const listener2 = Deno.listen({ hostname, port }); listener2.close(); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, net: true }, }, async function netUnixCloseWhileAccept() { const filePath = tmpUnixSocketPath(); const listener = Deno.listen({ path: filePath, transport: "unix", }); const p = listener.accept(); listener.close(); await assertRejects( () => p, Deno.errors.BadResource, "Listener has been closed", ); }, ); Deno.test( { permissions: { net: true } }, async function netTcpConcurrentAccept() { const listener = Deno.listen({ port: 4510 }); let acceptErrCount = 0; const checkErr = (e: Error) => { if (e.message === "Listener has been closed") { assertEquals(acceptErrCount, 1); } else if (e.message === "Another accept task is ongoing") { acceptErrCount++; } else { throw e; } }; const p = listener.accept().catch(checkErr); const p1 = listener.accept().catch(checkErr); await Promise.race([p, p1]); listener.close(); await Promise.all([p, p1]); assertEquals(acceptErrCount, 1); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, net: true }, }, async function netUnixConcurrentAccept() { const filePath = tmpUnixSocketPath(); const listener = Deno.listen({ transport: "unix", path: filePath }); let acceptErrCount = 0; const checkErr = (e: Error) => { if (e.message === "Listener has been closed") { assertEquals(acceptErrCount, 1); } else if (e instanceof Deno.errors.Busy) { // "Listener already in use" acceptErrCount++; } else { throw e; } }; const p = listener.accept().catch(checkErr); const p1 = listener.accept().catch(checkErr); await Promise.race([p, p1]); listener.close(); await Promise.all([p, p1]); assertEquals(acceptErrCount, 1); }, ); Deno.test({ permissions: { net: true } }, async function netTcpDialListen() { const listener = Deno.listen({ port: listenPort }); listener.accept().then( async (conn) => { assert(conn.remoteAddr != null); assert(conn.localAddr.transport === "tcp"); assertEquals(conn.localAddr.hostname, "127.0.0.1"); assertEquals(conn.localAddr.port, listenPort); await conn.write(new Uint8Array([1, 2, 3])); conn.close(); }, ); const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); assert(conn.remoteAddr.transport === "tcp"); assertEquals(conn.remoteAddr.hostname, "127.0.0.1"); assertEquals(conn.remoteAddr.port, listenPort); assert(conn.localAddr != null); const buf = new Uint8Array(1024); const readResult = await conn.read(buf); assertEquals(3, readResult); assertEquals(1, buf[0]); assertEquals(2, buf[1]); assertEquals(3, buf[2]); assert(readResult !== null); const readResult2 = await conn.read(buf); assertEquals(readResult2, null); listener.close(); conn.close(); }); Deno.test({ permissions: { net: true } }, async function netTcpSetNoDelay() { const listener = Deno.listen({ port: listenPort }); listener.accept().then( async (conn) => { assert(conn.remoteAddr != null); assert(conn.localAddr.transport === "tcp"); assertEquals(conn.localAddr.hostname, "127.0.0.1"); assertEquals(conn.localAddr.port, listenPort); await conn.write(new Uint8Array([1, 2, 3])); conn.close(); }, ); const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); conn.setNoDelay(true); assert(conn.remoteAddr.transport === "tcp"); assertEquals(conn.remoteAddr.hostname, "127.0.0.1"); assertEquals(conn.remoteAddr.port, listenPort); assert(conn.localAddr != null); const buf = new Uint8Array(1024); const readResult = await conn.read(buf); assertEquals(3, readResult); assertEquals(1, buf[0]); assertEquals(2, buf[1]); assertEquals(3, buf[2]); assert(readResult !== null); const readResult2 = await conn.read(buf); assertEquals(readResult2, null); listener.close(); conn.close(); }); Deno.test({ permissions: { net: true } }, async function netTcpSetKeepAlive() { const listener = Deno.listen({ port: listenPort }); listener.accept().then( async (conn) => { assert(conn.remoteAddr != null); assert(conn.localAddr.transport === "tcp"); assertEquals(conn.localAddr.hostname, "127.0.0.1"); assertEquals(conn.localAddr.port, listenPort); await conn.write(new Uint8Array([1, 2, 3])); conn.close(); }, ); const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); conn.setKeepAlive(true); assert(conn.remoteAddr.transport === "tcp"); assertEquals(conn.remoteAddr.hostname, "127.0.0.1"); assertEquals(conn.remoteAddr.port, listenPort); assert(conn.localAddr != null); const buf = new Uint8Array(1024); const readResult = await conn.read(buf); assertEquals(3, readResult); assertEquals(1, buf[0]); assertEquals(2, buf[1]); assertEquals(3, buf[2]); assert(readResult !== null); const readResult2 = await conn.read(buf); assertEquals(readResult2, null); listener.close(); conn.close(); }); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, net: true }, }, async function netUnixDialListen() { const filePath = tmpUnixSocketPath(); const listener = Deno.listen({ path: filePath, transport: "unix" }); listener.accept().then( async (conn) => { assert(conn.remoteAddr != null); assert(conn.localAddr.transport === "unix"); assertEquals(conn.localAddr.path, filePath); await conn.write(new Uint8Array([1, 2, 3])); conn.close(); }, ); const conn = await Deno.connect({ path: filePath, transport: "unix" }); assert(conn.remoteAddr.transport === "unix"); assertEquals(conn.remoteAddr.path, filePath); assert(conn.remoteAddr != null); const buf = new Uint8Array(1024); const readResult = await conn.read(buf); assertEquals(3, readResult); assertEquals(1, buf[0]); assertEquals(2, buf[1]); assertEquals(3, buf[2]); assert(readResult !== null); const readResult2 = await conn.read(buf); assertEquals(readResult2, null); listener.close(); conn.close(); }, ); Deno.test( { permissions: { net: true } }, async function netUdpSendReceive() { const alice = Deno.listenDatagram({ port: listenPort, transport: "udp", hostname: "127.0.0.1", }); assert(alice.addr.transport === "udp"); assertEquals(alice.addr.port, listenPort); assertEquals(alice.addr.hostname, "127.0.0.1"); const bob = Deno.listenDatagram({ port: listenPort2, transport: "udp", hostname: "127.0.0.1", }); assert(bob.addr.transport === "udp"); assertEquals(bob.addr.port, listenPort2); assertEquals(bob.addr.hostname, "127.0.0.1"); const sent = new Uint8Array([1, 2, 3]); const byteLength = await alice.send(sent, bob.addr); assertEquals(byteLength, 3); const [recvd, remote] = await bob.receive(); assert(remote.transport === "udp"); assertEquals(remote.port, listenPort); assertEquals(recvd.length, 3); assertEquals(1, recvd[0]); assertEquals(2, recvd[1]); assertEquals(3, recvd[2]); alice.close(); bob.close(); }, ); // Regression test for https://github.com/denoland/deno/issues/25581 // `Deno.listenDatagram` with no `hostname` should bind to `0.0.0.0` (matching // the documented default and `Deno.listen`/`Deno.serve`), not `127.0.0.1`. // Binding to `127.0.0.1` made it impossible to send to a network broadcast // address such as `10.x.x.255` (the OS rejected the send with EINVAL/EFAULT). Deno.test( { permissions: { net: true } }, function netUdpDefaultHostnameIsAnyAddress() { const socket = Deno.listenDatagram({ port: 0, transport: "udp" }); assert(socket.addr.transport === "udp"); assertEquals(socket.addr.hostname, "0.0.0.0"); socket.close(); }, ); Deno.test( { permissions: { net: true } }, async function netUdpSendReceiveTestSizeLimits() { // Ensure payload being sent is within UDP limit, which seems to be 65507 // bytes const alice = Deno.listenDatagram({ port: listenPort, transport: "udp", hostname: "127.0.0.1", }); // wrap this in a try/catch so other tests can continue if we fail // if we don't close here then listening on future tests fails try { assert(alice.addr.transport === "udp"); assertEquals(alice.addr.port, listenPort); assertEquals(alice.addr.hostname, "127.0.0.1"); } catch (err) { alice.close(); throw err; } const bob = Deno.listenDatagram({ port: listenPort2, transport: "udp", hostname: "127.0.0.1", }); try { assert(bob.addr.transport === "udp"); assertEquals(bob.addr.port, listenPort2); assertEquals(bob.addr.hostname, "127.0.0.1"); } catch (err) { bob.close(); throw err; } const sizes = [0, 1, 2, 256, 1024, 4096, 16384, 65506, 65507, 65508, 65536]; const rx = /.+ \(os error \d+\)/; for (const size of sizes) { const tosend = new Uint8Array(size); let byteLength = 0; try { byteLength = await alice.send(tosend, bob.addr); } catch (err) { // Note: we have to do the test this way as different OS's have // different UDP size limits enabled, so we will just ensure if // an error is thrown it is the one we are expecting. assert((err as Error).message.match(rx)); alice.close(); bob.close(); return; } assertEquals(byteLength, size); const [recvd, remote] = await bob.receive(); assert(remote.transport === "udp"); assertEquals(remote.port, listenPort); assertEquals(recvd.length, size); } alice.close(); bob.close(); }, ); Deno.test( { permissions: { net: true }, ignore: true }, async function netUdpSendReceiveBroadcast() { // Must bind sender to an address that can send to the broadcast address on MacOS. // Macos will give us error 49 when sending the broadcast packet if we omit hostname here. const alice = Deno.listenDatagram({ port: listenPort, transport: "udp", hostname: "0.0.0.0", }); const bob = Deno.listenDatagram({ port: listenPort, transport: "udp", hostname: "0.0.0.0", }); assert(bob.addr.transport === "udp"); assertEquals(bob.addr.port, listenPort); assertEquals(bob.addr.hostname, "0.0.0.0"); const broadcastAddr = { ...bob.addr, hostname: "255.255.255.255" }; const sent = new Uint8Array([1, 2, 3]); const byteLength = await alice.send(sent, broadcastAddr); assertEquals(byteLength, 3); const [recvd, remote] = await bob.receive(); assert(remote.transport === "udp"); assertEquals(remote.port, listenPort); assertEquals(recvd.length, 3); assertEquals(1, recvd[0]); assertEquals(2, recvd[1]); assertEquals(3, recvd[2]); alice.close(); bob.close(); }, ); Deno.test( { permissions: { net: true }, ignore: true }, async function netUdpMulticastV4() { const listener = Deno.listenDatagram({ hostname: "0.0.0.0", port: 5353, transport: "udp", reuseAddress: true, }); const membership = await listener.joinMulticastV4( "224.0.0.251", "127.0.0.1", ); membership.setLoopback(true); membership.setLoopback(false); membership.setTTL(50); membership.leave(); listener.close(); }, ); Deno.test( { permissions: { net: true }, ignore: true }, async function netUdpMulticastV6() { const listener = Deno.listenDatagram({ hostname: "::", port: 5353, transport: "udp", reuseAddress: true, }); const membership = await listener.joinMulticastV6( "ff02::fb", 1, ); membership.setLoopback(true); membership.setLoopback(false); membership.leave(); listener.close(); }, ); Deno.test( { permissions: { net: true }, ignore: true }, async function netUdpSendReceiveMulticastv4() { const alice = Deno.listenDatagram({ hostname: "0.0.0.0", port: 5353, transport: "udp", reuseAddress: true, loopback: true, }); const bob = Deno.listenDatagram({ hostname: "0.0.0.0", port: 5353, transport: "udp", reuseAddress: true, }); const aliceMembership = await alice.joinMulticastV4( "224.0.0.1", "0.0.0.0", ); const bobMembership = await bob.joinMulticastV4("224.0.0.1", "0.0.0.0"); const sent = new Uint8Array([1, 2, 3]); await alice.send(sent, { hostname: "224.0.0.1", port: 5353, transport: "udp", }); const [recvd, remote] = await bob.receive(); assert(remote.transport === "udp"); assertEquals(remote.port, 5353); assertEquals(recvd.length, 3); assertEquals(1, recvd[0]); assertEquals(2, recvd[1]); assertEquals(3, recvd[2]); aliceMembership.leave(); bobMembership.leave(); alice.close(); bob.close(); }, ); Deno.test( { permissions: { net: true }, ignore: true }, async function netUdpMulticastLoopbackOption() { // Must bind sender to an address that can send to the broadcast address on MacOS. // Macos will give us error 49 when sending the broadcast packet if we omit hostname here. const listener = Deno.listenDatagram({ port: 5353, transport: "udp", hostname: "0.0.0.0", loopback: true, reuseAddress: true, }); const membership = await listener.joinMulticastV4( "224.0.0.1", "0.0.0.0", ); // await membership.setLoopback(true); const sent = new Uint8Array([1, 2, 3]); const byteLength = await listener.send(sent, { hostname: "224.0.0.1", port: 5353, transport: "udp", }); assertEquals(byteLength, 3); const [recvd, remote] = await listener.receive(); assert(remote.transport === "udp"); assertEquals(remote.port, 5353); assertEquals(recvd.length, 3); assertEquals(1, recvd[0]); assertEquals(2, recvd[1]); assertEquals(3, recvd[2]); membership.leave(); listener.close(); }, ); Deno.test( { permissions: { net: true } }, async function netUdpConcurrentSendReceive() { const socket = Deno.listenDatagram({ port: listenPort, transport: "udp", hostname: "127.0.0.1", }); assert(socket.addr.transport === "udp"); assertEquals(socket.addr.port, listenPort); assertEquals(socket.addr.hostname, "127.0.0.1"); const recvPromise = socket.receive(); const sendBuf = new Uint8Array([1, 2, 3]); const sendLen = await socket.send(sendBuf, socket.addr); assertEquals(sendLen, 3); const [recvBuf, _recvAddr] = await recvPromise; assertEquals(recvBuf.length, 3); assertEquals(1, recvBuf[0]); assertEquals(2, recvBuf[1]); assertEquals(3, recvBuf[2]); socket.close(); }, ); Deno.test( { permissions: { net: true } }, async function netUdpBorrowMutError() { const socket = Deno.listenDatagram({ port: listenPort, transport: "udp", hostname: "127.0.0.1", }); // Panic happened on second send: BorrowMutError const a = socket.send(new Uint8Array(), socket.addr); const b = socket.send(new Uint8Array(), socket.addr); await Promise.all([a, b]); socket.close(); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, net: true }, }, async function netUnixPacketSendReceive() { const aliceFilePath = tmpUnixSocketPath(); const alice = Deno.listenDatagram({ path: aliceFilePath, transport: "unixpacket", }); assert(alice.addr.transport === "unixpacket"); assertEquals(alice.addr.path, aliceFilePath); const bobFilePath = tmpUnixSocketPath(); const bob = Deno.listenDatagram({ path: bobFilePath, transport: "unixpacket", }); assert(bob.addr.transport === "unixpacket"); assertEquals(bob.addr.path, bobFilePath); const sent = new Uint8Array([1, 2, 3]); const byteLength = await alice.send(sent, bob.addr); assertEquals(byteLength, 3); const [recvd, remote] = await bob.receive(); assert(remote.transport === "unixpacket"); assertEquals(remote.path, aliceFilePath); assertEquals(recvd.length, 3); assertEquals(1, recvd[0]); assertEquals(2, recvd[1]); assertEquals(3, recvd[2]); alice.close(); bob.close(); }, ); // TODO(lucacasonato): support concurrent reads and writes on unixpacket sockets Deno.test( { ignore: true, permissions: { read: true, write: true, net: true } }, async function netUnixPacketConcurrentSendReceive() { const filePath = tmpUnixSocketPath(); const socket = Deno.listenDatagram({ path: filePath, transport: "unixpacket", }); assert(socket.addr.transport === "unixpacket"); assertEquals(socket.addr.path, filePath); const recvPromise = socket.receive(); const sendBuf = new Uint8Array([1, 2, 3]); const sendLen = await socket.send(sendBuf, socket.addr); assertEquals(sendLen, 3); const [recvBuf, _recvAddr] = await recvPromise; assertEquals(recvBuf.length, 3); assertEquals(1, recvBuf[0]); assertEquals(2, recvBuf[1]); assertEquals(3, recvBuf[2]); socket.close(); }, ); Deno.test( { permissions: { net: true } }, async function netTcpListenIteratorBreakClosesResource() { async function iterate(listener: Deno.Listener) { let i = 0; for await (const conn of listener) { conn.close(); i++; if (i > 1) { break; } } } const addr = { hostname: "127.0.0.1", port: 8888 }; const listener = Deno.listen(addr); const iteratePromise = iterate(listener); await delay(100); const conn1 = await Deno.connect(addr); conn1.close(); const conn2 = await Deno.connect(addr); conn2.close(); await iteratePromise; }, ); Deno.test( { permissions: { net: true } }, async function netTcpListenCloseWhileIterating() { const listener = Deno.listen({ port: 8001 }); const nextWhileClosing = listener[Symbol.asyncIterator]().next(); listener.close(); assertEquals(await nextWhileClosing, { value: undefined, done: true }); const nextAfterClosing = listener[Symbol.asyncIterator]().next(); assertEquals(await nextAfterClosing, { value: undefined, done: true }); }, ); Deno.test( { permissions: { net: true } }, async function netUdpListenCloseWhileIterating() { const socket = Deno.listenDatagram({ port: 8000, transport: "udp" }); const nextWhileClosing = socket[Symbol.asyncIterator]().next(); socket.close(); assertEquals(await nextWhileClosing, { value: undefined, done: true }); const nextAfterClosing = socket[Symbol.asyncIterator]().next(); assertEquals(await nextAfterClosing, { value: undefined, done: true }); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, net: true }, }, async function netUnixListenCloseWhileIterating() { const filePath = tmpUnixSocketPath(); const socket = Deno.listen({ path: filePath, transport: "unix" }); const nextWhileClosing = socket[Symbol.asyncIterator]().next(); socket.close(); assertEquals(await nextWhileClosing, { value: undefined, done: true }); const nextAfterClosing = socket[Symbol.asyncIterator]().next(); assertEquals(await nextAfterClosing, { value: undefined, done: true }); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, net: true }, }, async function netUnixPacketListenCloseWhileIterating() { const filePath = tmpUnixSocketPath(); const socket = Deno.listenDatagram({ path: filePath, transport: "unixpacket", }); const nextWhileClosing = socket[Symbol.asyncIterator]().next(); socket.close(); assertEquals(await nextWhileClosing, { value: undefined, done: true }); const nextAfterClosing = socket[Symbol.asyncIterator]().next(); assertEquals(await nextAfterClosing, { value: undefined, done: true }); }, ); Deno.test( { permissions: { net: true } }, async function netListenAsyncIterator() { const addr = { hostname: "127.0.0.1", port: listenPort }; const listener = Deno.listen(addr); const runAsyncIterator = async () => { for await (const conn of listener) { await conn.write(new Uint8Array([1, 2, 3])); conn.close(); } }; runAsyncIterator(); const conn = await Deno.connect(addr); const buf = new Uint8Array(1024); const readResult = await conn.read(buf); assertEquals(3, readResult); assertEquals(1, buf[0]); assertEquals(2, buf[1]); assertEquals(3, buf[2]); assert(readResult !== null); const readResult2 = await conn.read(buf); assertEquals(readResult2, null); listener.close(); conn.close(); }, ); Deno.test( { permissions: { net: true }, }, async function netCloseWriteSuccess() { const addr = { hostname: "127.0.0.1", port: listenPort }; const listener = Deno.listen(addr); const { promise: closePromise, resolve } = Promise.withResolvers<void>(); listener.accept().then(async (conn) => { await conn.write(new Uint8Array([1, 2, 3])); await closePromise; conn.close(); }); const conn = await Deno.connect(addr); conn.closeWrite(); // closing write const buf = new Uint8Array(1024); // Check read not impacted const readResult = await conn.read(buf); assertEquals(3, readResult); assertEquals(1, buf[0]); assertEquals(2, buf[1]); assertEquals(3, buf[2]); // Verify that the write end of the socket is closed. // TODO(piscisaureus): assert that thrown error is of a specific type. await assertRejects(async () => { await conn.write(new Uint8Array([1, 2, 3])); }); resolve(); listener.close(); conn.close(); }, ); Deno.test( { // https://github.com/denoland/deno/issues/11580 ignore: Deno.build.os === "darwin" && isCI, permissions: { net: true }, }, async function netHangsOnClose() { let acceptedConn: Deno.Conn; async function iteratorReq(listener: Deno.Listener) { const p = new Uint8Array(10); const conn = await listener.accept(); acceptedConn = conn; try { while (true) { const nread = await conn.read(p); if (nread === null) { break; } await conn.write(new Uint8Array([1, 2, 3])); } } catch (err) { assert(err); assert(err instanceof Deno.errors.Interrupted); } } const addr = { hostname: "127.0.0.1", port: listenPort }; const listener = Deno.listen(addr); const listenerPromise = iteratorReq(listener); const connectionPromise = (async () => { const conn = await Deno.connect(addr); await conn.write(new Uint8Array([1, 2, 3, 4])); const buf = new Uint8Array(10); await conn.read(buf); conn!.close(); acceptedConn!.close(); listener.close(); })(); await Promise.all([ listenerPromise, connectionPromise, ]); }, ); Deno.test( { permissions: { net: true }, }, function netExplicitUndefinedHostname() { const listener = Deno.listen({ hostname: undefined, port: 8080 }); assertEquals((listener.addr as Deno.NetAddr).hostname, "0.0.0.0"); listener.close(); }, ); Deno.test( { ignore: Deno.build.os !== "linux", permissions: { read: true, write: true, net: true }, }, async function netUnixAbstractPathAddr() { const path = `\0deno-net-test-${crypto.randomUUID()}`; const listener = Deno.listen({ path, transport: "unix", }); assertEquals(listener.addr.path, path); const acceptPromise = listener.accept(); const conn = await Deno.connect({ path, transport: "unix" }); assertEquals(conn.remoteAddr.path, path); const acceptedConn = await acceptPromise; assertEquals(acceptedConn.localAddr.path, path); conn.close(); acceptedConn.close(); listener.close(); }, ); Deno.test( { ignore: Deno.build.os !== "linux", permissions: { read: true, write: true, net: true }, }, async function netUnixPacketAbstractPathAddr() { const alicePath = `\0deno-net-test-${crypto.randomUUID()}`; const alice = Deno.listenDatagram({ path: alicePath, transport: "unixpacket", }); assert(alice.addr.transport === "unixpacket"); assertEquals(alice.addr.path, alicePath); const bobPath = `\0deno-net-test-${crypto.randomUUID()}`; const bob = Deno.listenDatagram({ path: bobPath, transport: "unixpacket", }); assert(bob.addr.transport === "unixpacket"); assertEquals(bob.addr.path, bobPath); const sent = new Uint8Array([1, 2, 3]); assertEquals(await alice.send(sent, bob.addr), sent.byteLength); const [received, remoteAddr] = await bob.receive(); assert(remoteAddr.transport === "unixpacket"); assertEquals(remoteAddr.path, alicePath); assertEquals(received, sent); alice.close(); bob.close(); }, ); Deno.test({ permissions: { net: true } }, async function whatwgStreams() { const server = (async () => { const listener = Deno.listen({ hostname: "127.0.0.1", port: listenPort }); const conn = await listener.accept(); await conn.readable.pipeTo(conn.writable); listener.close(); })(); const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); const reader = conn.readable.getReader(); const writer = conn.writable.getWriter(); const encoder = new TextEncoder(); const decoder = new TextDecoder(); const data = encoder.encode("Hello World"); await writer.write(data); const { value, done } = await reader.read(); assert(!done); assertEquals(decoder.decode(value), "Hello World"); await reader.cancel(); await server; }); Deno.test( { permissions: { read: true } }, async function readableStreamTextEncoderPipe() { const filename = "tests/testdata/assets/hello.txt"; const file = await Deno.open(filename); const readable = file.readable.pipeThrough(new TextDecoderStream()); const chunks = []; for await (const chunk of readable) { chunks.push(chunk); } assertEquals(chunks.length, 1); assertEquals(chunks[0].length, 12); }, ); Deno.test( { permissions: { read: true, write: true } }, async function writableStream() { const path = await Deno.makeTempFile(); const file = await Deno.open(path, { write: true }); assert(file.writable instanceof WritableStream); const readable = new ReadableStream({ start(controller) { controller.enqueue(new TextEncoder().encode("hello ")); controller.enqueue(new TextEncoder().encode("world!")); controller.close(); }, }); await readable.pipeTo(file.writable); const res = await Deno.readTextFile(path); assertEquals(res, "hello world!"); }, ); Deno.test( { permissions: { read: true, run: true } }, async function netListenUnref() { const [statusCode, _output] = await execCode(` async function main() { const listener = Deno.listen({ port: ${listenPort} }); listener.unref(); await listener.accept(); // This doesn't block the program from exiting } main(); `); assertEquals(statusCode, 0); }, ); Deno.test( { permissions: { read: true, run: true } }, async function netListenUnref2() { const [statusCode, _output] = await execCode(` async function main() { const listener = Deno.listen({ port: ${listenPort} }); await listener.accept(); listener.unref(); await listener.accept(); // The program exits here throw new Error(); // The program doesn't reach here } main(); const conn = await Deno.connect({ port: ${listenPort} }); conn.close(); `); assertEquals(statusCode, 0); }, ); Deno.test( { permissions: { read: true, run: true, net: true } }, async function netListenUnrefAndRef() { const p = execCode2(` async function main() { const listener = Deno.listen({ port: ${listenPort} }); listener.unref(); listener.ref(); // This restores 'ref' state of listener console.log("started"); await listener.accept(); console.log("accepted") } main(); `); await p.waitStdoutText("started"); const conn = await Deno.connect({ port: listenPort }); conn.close(); const [statusCode, output] = await p.finished(); assertEquals(statusCode, 0); assertEquals(output.trim(), "started\naccepted"); }, ); Deno.test( { permissions: { net: true } }, async function netListenUnrefConcurrentAccept() { const timer = setTimeout(() => {}, 1000); const listener = Deno.listen({ port: listenPort }); listener.accept().catch(() => {}); listener.unref(); // Unref'd listener still causes Busy error // on concurrent accept calls. await assertRejects(async () => { await listener.accept(); // The program exits here }, Deno.errors.Busy); listener.close(); clearTimeout(timer); }, ); Deno.test({ ignore: Deno.build.os === "windows", permissions: { read: true, write: true, net: true }, }, function netUnixListenAddrAlreadyInUse() { const filePath = tmpUnixSocketPath(); const listener = Deno.listen({ path: filePath, transport: "unix" }); assertThrows( () => { Deno.listen({ path: filePath, transport: "unix" }); }, Deno.errors.AddrInUse, ); listener.close(); }); Deno.test( { permissions: { net: true, read: true, run: true } }, async function netConnUnref() { const listener = Deno.listen({ port: listenPort }); const intervalId = setInterval(() => {}); // This keeps event loop alive. const program = execCode(` async function main() { const conn = await Deno.connect({ port: ${listenPort} }); conn.unref(); await conn.read(new Uint8Array(10)); // The program exits here throw new Error(); // The program doesn't reach here } main(); `); const conn = await listener.accept(); const [statusCode, _output] = await program; conn.close(); listener.close(); clearInterval(intervalId); assertEquals(statusCode, 0); }, ); Deno.test( { permissions: { net: true, read: true, run: true } }, async function netConnUnrefReadable() { const listener = Deno.listen({ port: listenPort }); const intervalId = setInterval(() => {}); // This keeps event loop alive. const program = execCode(` async function main() { const conn = await Deno.connect({ port: ${listenPort} }); conn.unref(); const reader = conn.readable.getReader(); await reader.read(); // The program exits here throw new Error(); // The program doesn't reach here } main(); `); const conn = await listener.accept(); const [statusCode, _output] = await program; conn.close(); listener.close(); clearInterval(intervalId); assertEquals(statusCode, 0); }, ); Deno.test({ permissions: { net: true } }, async function netTcpReuseAddr() { const listener1 = Deno.listen({ hostname: "127.0.0.1", port: listenPort, }); listener1.accept().then( (conn) => { conn.close(); }, ); const conn1 = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); const buf1 = new Uint8Array(1024); await conn1.read(buf1); listener1.close(); conn1.close(); const listener2 = Deno.listen({ hostname: "127.0.0.1", port: listenPort, }); listener2.accept().then( (conn) => { conn.close(); }, ); const conn2 = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); const buf2 = new Uint8Array(1024); await conn2.read(buf2); listener2.close(); conn2.close(); }); Deno.test( { permissions: { net: true } }, async function netUdpReuseAddr() { const sender = Deno.listenDatagram({ port: 4002, transport: "udp", hostname: "127.0.0.1", }); const listener1 = Deno.listenDatagram({ port: 4000, transport: "udp", reuseAddress: true, hostname: "127.0.0.1", }); const listener2 = Deno.listenDatagram({ port: 4000, transport: "udp", reuseAddress: true, hostname: "127.0.0.1", }); const sent = new Uint8Array([1, 2, 3]); await sender.send(sent, listener1.addr); await Promise.any([listener1.receive(), listener2.receive()]).then( ([recvd, remote]) => { assert(remote.transport === "udp"); assertEquals(recvd.length, 3); assertEquals(1, recvd[0]); assertEquals(2, recvd[1]); assertEquals(3, recvd[2]); }, ); sender.close(); listener1.close(); listener2.close(); }, ); Deno.test( { permissions: { net: true } }, function netUdpNoReuseAddr() { let listener1; try { listener1 = Deno.listenDatagram({ port: 4001, transport: "udp", reuseAddress: false, }); } catch (err) { assert(err); assert(err instanceof Deno.errors.AddrInUse); // AddrInUse from previous test } assertThrows(() => { Deno.listenDatagram({ port: 4001, transport: "udp", reuseAddress: false, }); }, Deno.errors.AddrInUse); if (typeof listener1 !== "undefined") { listener1.close(); } }, ); 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; listener1.close(); }).catch(() => {}); } if (!p2) { p2 = listener2.accept().then((conn) => { conn.close(); listener2Recv = true; p2 = undefined; listener2.close(); }).catch(() => {}); } const conn = await Deno.connect({ port }); conn.close(); await Promise.race([p1, p2]); } }); Deno.test( { permissions: { net: true }, ignore: true }, async function netTcpWithAbortSignal() { const controller = new AbortController(); setTimeout(() => controller.abort(), 1_000); const error = await assertRejects( async () => { await Deno.connect({ hostname: "deno.com", port: 50000, transport: "tcp", signal: controller.signal, }); }, ); assert(error instanceof DOMException); assertEquals(error.name, "AbortError"); }, ); Deno.test( { permissions: { net: true } }, async function netTcpInvalidPortWithAbortSignalDoesNotLeak() { const controller = new AbortController(); await assertRejects( () => Deno.connect({ port: -1, signal: controller.signal }), RangeError, ); }, ); Deno.test( { permissions: { net: false } }, async function netTcpPermissionErrorWithAbortSignalDoesNotLeak() { const controller = new AbortController(); await assertRejects( () => Deno.connect({ hostname: "127.0.0.1", port: 80, signal: controller.signal, }), Deno.errors.NotCapable, ); }, ); Deno.test( { permissions: { net: false } }, async function resolveDnsPermissionErrorWithAbortSignalDoesNotLeak() { const controller = new AbortController(); await assertRejects( () => Deno.resolveDns("example.com", "A", { nameServer: { ipAddr: "127.0.0.1" }, signal: controller.signal, }), Deno.errors.NotCapable, ); }, ); 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(); }); Deno.test({ permissions: { net: true }, }, function netTcpListenDoesNotThrowOnStringPort() { // @ts-ignore String port is not allowed by typing, but it shouldn't throw // for backwards compatibility. const listener = Deno.listen({ hostname: "localhost", port: "0" }); listener.close(); }); Deno.test( { permissions: { net: true } }, async function listenerExplicitResourceManagement() { let done: Promise<Deno.errors.BadResource>; { using listener = Deno.listen({ port: listenPort }); done = assertRejects( () => listener.accept(), Deno.errors.BadResource, ); } await done; }, ); Deno.test( { permissions: { net: true } }, async function listenerExplicitResourceManagementManualClose() { using listener = Deno.listen({ port: listenPort }); listener.close(); await assertRejects( // definitely closed () => listener.accept(), Deno.errors.BadResource, ); // calling [Symbol.dispose] after manual close is a no-op }, ); // Regression test for GHSA-rjhc-mq2r-fp9w: Unix socket connect/listen used // to check only filesystem permissions, letting scripts with // `--allow-read=/var/run/docker.sock` reach Docker without any `--allow-net` // grant. They now also require an `--allow-net=unix:<path>` rule (or // unscoped `--allow-net`). Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true }, }, function netUnixListenRequiresAllowNet() { const filePath = tmpUnixSocketPath(); assertThrows( () => Deno.listen({ path: filePath, transport: "unix" }), Deno.errors.NotCapable, ); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true }, }, async function netUnixConnectRequiresAllowNet() { const filePath = tmpUnixSocketPath(); await assertRejects( () => Deno.connect({ path: filePath, transport: "unix" }), Deno.errors.NotCapable, ); }, ); // Scoped form must match the exact path — a different unix path does not // grant access to this one. Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, net: ["unix:/some/other/path.sock"], }, }, function netUnixScopedAllowNetMatchesExactly() { const filePath = tmpUnixSocketPath(); assertThrows( () => Deno.listen({ path: filePath, transport: "unix" }), Deno.errors.NotCapable, ); }, ); // The datagram (unixpacket) half of the boundary must be guarded too: // `Deno.listenDatagram({ transport: "unixpacket" })` is denied without an // `--allow-net=unix:<path>` grant. Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true }, }, function netUnixPacketListenRequiresAllowNet() { const filePath = tmpUnixSocketPath(); assertThrows( () => Deno.listenDatagram({ path: filePath, transport: "unixpacket" }), Deno.errors.NotCapable, ); }, ); // `DatagramConn.send()` checks the destination path independently of the // listen path. Here the socket is listened with `--allow-net` scoped to its // own path, but sending to a different (ungranted) path is denied. Run in a // subprocess (`deno run`, not `deno eval` which has implicit full permissions) // so the dynamic socket paths can be passed in the allow-net rule. Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, run: true }, }, async function netUnixPacketSendRequiresAllowNet() { const alicePath = tmpUnixSocketPath(); const bobPath = tmpUnixSocketPath(); const scriptPath = Deno.makeTempFileSync({ suffix: ".js" }); Deno.writeTextFileSync( scriptPath, ` const alice = Deno.listenDatagram({ path: ${JSON.stringify(alicePath)}, transport: "unixpacket", }); try { await alice.send(new Uint8Array([1, 2, 3]), { path: ${JSON.stringify(bobPath)}, transport: "unixpacket", }); console.log("SENT"); } catch (e) { console.log( e instanceof Deno.errors.NotCapable ? "NOT_CAPABLE" : "OTHER:" + e.name, ); } finally { alice.close(); } `, ); const [status, output] = await execCode3(Deno.execPath(), [ "run", "--unstable-net", "--allow-read", "--allow-write", `--allow-net=unix:${alicePath}`, scriptPath, ]).finished(); assertEquals(status, 0); assertStringIncludes(output, "NOT_CAPABLE"); }, ); // A `--allow-net=unix:<path>` rule scoped to the exact socket path grants both // listen and connect. Run in a subprocess so the dynamic socket path can be // passed in the allow-net rule. Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, run: true }, }, async function netUnixScopedAllowNetGrantsAccess() { const socketPath = tmpUnixSocketPath(); const scriptPath = Deno.makeTempFileSync({ suffix: ".js" }); Deno.writeTextFileSync( scriptPath, ` const listener = Deno.listen({ path: ${JSON.stringify(socketPath)}, transport: "unix", }); const conn = await Deno.connect({ path: ${JSON.stringify(socketPath)}, transport: "unix", }); conn.close(); listener.close(); console.log("OK"); `, ); const [status, output] = await execCode3(Deno.execPath(), [ "run", "--allow-read", "--allow-write", `--allow-net=unix:${socketPath}`, scriptPath, ]).finished(); assertEquals(status, 0); assertStringIncludes(output, "OK"); }, ); // A `unix:` rule must be an absolute path: a relative path is rejected at flag // parse time before the script runs. Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, run: true }, }, async function netUnixAllowNetRejectsNonAbsolutePath() { const scriptPath = Deno.makeTempFileSync({ suffix: ".js" }); Deno.writeTextFileSync(scriptPath, "console.log('UNREACHABLE');"); const { code, stdout, stderr } = await new Deno.Command(Deno.execPath(), { args: ["run", "--allow-net=unix:relative.sock", scriptPath], stdout: "piped", stderr: "piped", }).output(); const decoder = new TextDecoder(); assert(code !== 0); assertEquals(decoder.decode(stdout), ""); assertStringIncludes(decoder.decode(stderr), "invalid unix socket"); }, ); // A `unix:` rule is lexically normalized at parse time, so a rule spelled // with `..`/`.` components still matches a connect to the normalized path. Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, run: true }, }, async function netUnixScopedAllowNetNormalizesRulePath() { const socketPath = tmpUnixSocketPath(); const parts = socketPath.split("/"); const base = parts.pop()!; const dir = parts.pop()!; const denormalizedPath = [...parts, dir, "..", dir, ".", base].join("/"); const scriptPath = Deno.makeTempFileSync({ suffix: ".js" }); Deno.writeTextFileSync( scriptPath, ` const listener = Deno.listen({ path: ${JSON.stringify(socketPath)}, transport: "unix", }); listener.close(); console.log("OK"); `, ); const [status, output] = await execCode3(Deno.execPath(), [ "run", "--allow-read", "--allow-write", `--allow-net=unix:${denormalizedPath}`, scriptPath, ]).finished(); assertEquals(status, 0); assertStringIncludes(output, "OK"); }, ); Deno.test( { permissions: { net: true } }, async function netTcpCloseCancelsPendingReadAndSendsFin() { // Regression test for #35292: closing a TCP conn while a read is in flight // must cancel that read and send a FIN, so the peer observes EOF. The // Resource trait's default `cancel_read_ops` is a no-op; net resources must // override it, otherwise `close()` leaves the socket open until the process // exits. const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 }); const port = (listener.addr as Deno.NetAddr).port; const serverSawEof = (async () => { const conn = await listener.accept(); const buf = new Uint8Array(64); const n = await conn.read(buf); conn.close(); return n === null; })(); const conn = await Deno.connect({ hostname: "127.0.0.1", port }); // Put a read in flight, then close while it is pending. const reader = conn.readable.getReader(); const pendingRead = reader.read().catch(() => {}); await delay(100); conn.close(); // Abort the timeout once the race settles so the test sanitizer does not // report a leaked timer. const ac = new AbortController(); const sawEof = await Promise.race([ serverSawEof, delay(2000, { signal: ac.signal }).then(() => false, () => false), ]); ac.abort(); assert( sawEof, "peer did not observe FIN after conn.close() with a pending read", ); await pendingRead; try { reader.releaseLock(); } catch { // already released } listener.close(); }, ); Deno.test( { ignore: Deno.build.os === "windows", permissions: { read: true, write: true, net: true }, }, async function netUnixCloseCancelsPendingReadAndSendsEof() { // Same as the TCP regression test above, for Unix domain sockets // (UnixStreamResource shares the same fix). const filePath = tmpUnixSocketPath(); const listener = Deno.listen({ path: filePath, transport: "unix" }); const serverSawEof = (async () => { const conn = await listener.accept(); const buf = new Uint8Array(64); const n = await conn.read(buf); conn.close(); return n === null; })(); const conn = await Deno.connect({ path: filePath, transport: "unix" }); const reader = conn.readable.getReader(); const pendingRead = reader.read().catch(() => {}); await delay(100); conn.close(); const ac = new AbortController(); const sawEof = await Promise.race([ serverSawEof, delay(2000, { signal: ac.signal }).then(() => false, () => false), ]); ac.abort(); assert( sawEof, "peer did not observe EOF after conn.close() with a pending read", ); await pendingRead; try { reader.releaseLock(); } catch { // already released } listener.close(); }, );