Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions doc/api/quic.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,24 @@ When a `QuicError` is passed to [`stream.destroy()`][] or
`STOP_SENDING` frame sent to the peer. Any other error type falls back to
the negotiated protocol's generic internal error code.

### Permission model

When using the [Permission Model][], the `--allow-net` flag must be passed to
allow QUIC network operations. Without it, calling [`quic.connect()`][] or
[`quic.listen()`][] will throw an `ERR_ACCESS_DENIED` error.

```console
$ node --permission --allow-fs-read=* --experimental-quic index.mjs
Error: Access to this API has been restricted. Use --allow-net to manage permissions.
code: 'ERR_ACCESS_DENIED',
permission: 'Net',
}
```

Creating a [`QuicEndpoint`][] instance without connecting or listening
is permitted even without `--allow-net`, since no network I/O occurs until
[`quic.connect()`][] or [`quic.listen()`][] is called.

## `quic.connect(address[, options])`

<!-- YAML
Expand Down Expand Up @@ -3853,6 +3871,7 @@ throughput issues caused by flow control.
[Callback error handling]: #callback-error-handling
[JSON-SEQ]: https://www.rfc-editor.org/rfc/rfc7464
[NSS Key Log Format]: https://udn.realityripple.com/docs/Mozilla/Projects/NSS/Key_Log_Format
[Permission Model]: permissions.md#permission-model
[RFC 8999]: https://www.rfc-editor.org/rfc/rfc8999
[RFC 9000]: https://www.rfc-editor.org/rfc/rfc9000
[RFC 9001]: https://www.rfc-editor.org/rfc/rfc9001
Expand All @@ -3872,6 +3891,7 @@ throughput issues caused by flow control.
[RFC 9443]: https://www.rfc-editor.org/rfc/rfc9443
[`PerformanceEntry`]: perf_hooks.md#class-performanceentry
[`PerformanceObserver`]: perf_hooks.md#class-performanceobserver
[`QuicEndpoint`]: #class-quicendpoint
[`QuicError`]: #class-quicerror
[`application.enableConnectProtocol`]: #sessionoptionsapplication
[`application.enableDatagrams`]: #sessionoptionsapplication
Expand Down
6 changes: 6 additions & 0 deletions src/quic/endpoint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <node_external_reference.h>
#include <node_process-inl.h>
#include <node_sockaddr-inl.h>
#include <permission/permission.h>
#include <timer_wrap-inl.h>
#include <util-inl.h>
#include <uv.h>
Expand Down Expand Up @@ -1745,6 +1746,9 @@ JS_METHOD_IMPL(Endpoint::DoConnect) {
SocketAddressBase* address;
ASSIGN_OR_RETURN_UNWRAP(&address, args[0]);

THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kNet, address->address()->ToString());

DCHECK(args[1]->IsObject());
Session::Options options;
if (!Session::Options::From(env, args[1]).To(&options)) {
Expand All @@ -1771,6 +1775,8 @@ JS_METHOD_IMPL(Endpoint::DoListen) {
ASSIGN_OR_RETURN_UNWRAP(&endpoint, args.This());
auto env = Environment::GetCurrent(args);

THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kNet, "");

Session::Options options;
if (Session::Options::From(env, args[0]).To(&options)) {
endpoint->Listen(options);
Expand Down
2 changes: 1 addition & 1 deletion test/cctest/test_sockaddr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ TEST(SocketAddress, SocketAddress) {
}

TEST(SocketAddress, IpHashAndIpEqual) {
sockaddr_storage s1, s2, s3, s4;
sockaddr_storage s1, s2, s3;
// Same IP, different ports.
SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 443, &s1);
SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 8080, &s2);
Expand Down
50 changes: 50 additions & 0 deletions test/parallel/test-permission-net-quic.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Flags: --permission --allow-fs-read=* --experimental-quic --no-warnings
import { hasQuic, skip, mustNotCall } from '../common/index.mjs';
import assert from 'node:assert';
import * as fixtures from '../common/fixtures.mjs';

if (!hasQuic) {
skip('QUIC is not enabled');
}

const { createPrivateKey } = await import('node:crypto');
const { connect, listen, QuicEndpoint } = await import('node:quic');

// Verify that the permission system correctly reports no net access.
assert.ok(!process.permission.has('net'));

const key = createPrivateKey(fixtures.readKey('agent1-key.pem'));
const cert = fixtures.readKey('agent1-cert.pem');

// Test: connect() should reject with ERR_ACCESS_DENIED
{
await assert.rejects(
connect('127.0.0.1:12345', { alpn: 'h3' }),
{
code: 'ERR_ACCESS_DENIED',
permission: 'Net',
},
);
}

// Test: listen() should throw ERR_ACCESS_DENIED
{
await assert.rejects(
listen(mustNotCall('onsession should not be called'), {
alpn: ['h3'],
sni: { '*': { keys: [key], certs: [cert] } },
}),
{
code: 'ERR_ACCESS_DENIED',
permission: 'Net',
},
);
}

// Test: Creating a QuicEndpoint without connect/listen is allowed
// since no network I/O occurs at construction time.
{
const endpoint = new QuicEndpoint();
// The endpoint exists but has not performed any network operations.
await endpoint.close();
}
Loading