Skip to content

Commit c855c3e

Browse files
committed
quic: use net.BlockList for limiting access to a QuicSocket
PR-URL: #34741 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 1c14810 commit c855c3e

File tree

6 files changed

+96
-1
lines changed

6 files changed

+96
-1
lines changed

doc/api/quic.md

+18
Original file line numberDiff line numberDiff line change
@@ -1445,6 +1445,24 @@ error will be thrown if `quicsock.addEndpoint()` is called either after
14451445
the `QuicSocket` has already started binding to the local ports, or after
14461446
the `QuicSocket` has been destroyed.
14471447

1448+
#### `quicsocket.blockList`
1449+
<!-- YAML
1450+
added: REPLACEME
1451+
-->
1452+
1453+
* Type: {net.BlockList}
1454+
1455+
A {net.BlockList} instance used to define rules for remote IPv4 or IPv6
1456+
addresses that this `QuicSocket` is not permitted to interact with. The
1457+
rules can be specified as either specific individual addresses, ranges
1458+
of addresses, or CIDR subnet ranges.
1459+
1460+
When listening as a server, if a packet is received from a blocked address,
1461+
the packet will be ignored.
1462+
1463+
When connecting as a client, if the remote IP address is blocked, the
1464+
connection attempt will be rejected.
1465+
14481466
#### `quicsocket.bound`
14491467
<!-- YAML
14501468
added: REPLACEME

lib/internal/quic/core.js

+11
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const { Duplex } = require('stream');
5454
const {
5555
createSecureContext: _createSecureContext
5656
} = require('tls');
57+
const BlockList = require('internal/blocklist');
5758
const {
5859
translatePeerCertificate
5960
} = require('_tls_common');
@@ -891,6 +892,7 @@ class QuicSocket extends EventEmitter {
891892
[kInternalState] = {
892893
alpn: undefined,
893894
bindPromise: undefined,
895+
blockList: undefined,
894896
client: undefined,
895897
closePromise: undefined,
896898
closePromiseResolve: undefined,
@@ -1007,8 +1009,10 @@ class QuicSocket extends EventEmitter {
10071009
this[async_id_symbol] = handle.getAsyncId();
10081010
this[kInternalState].sharedState =
10091011
new QuicSocketSharedState(handle.state);
1012+
this[kInternalState].blockList = new BlockList(handle.blockList);
10101013
} else {
10111014
this[kInternalState].sharedState = undefined;
1015+
this[kInternalState].blockList = undefined;
10121016
}
10131017
}
10141018

@@ -1303,6 +1307,9 @@ class QuicSocket extends EventEmitter {
13031307
if (this.closing)
13041308
throw new ERR_INVALID_STATE('QuicSocket is closing');
13051309

1310+
if (this.blockList.check(ip, type === AF_INET6 ? 'ipv6' : 'ipv4'))
1311+
throw new ERR_OPERATION_FAILED(`${ip} failed BlockList check`);
1312+
13061313
return new QuicClientSession(this, options, type, ip);
13071314
}
13081315

@@ -1458,6 +1465,10 @@ class QuicSocket extends EventEmitter {
14581465
return this;
14591466
}
14601467

1468+
get blockList() {
1469+
return this[kInternalState]?.blockList;
1470+
}
1471+
14611472
get endpoints() {
14621473
return Array.from(this[kInternalState].endpoints);
14631474
}

src/quic/node_quic_session.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ enum QuicSessionStateFields {
207207
V(SMOOTHED_RTT, smoothed_rtt, "Smoothed RTT") \
208208
V(CWND, cwnd, "Cwnd") \
209209
V(RECEIVE_RATE, receive_rate, "Receive Rate / Sec") \
210-
V(SEND_RATE, send_rate, "Send Rate Sec")
210+
V(SEND_RATE, send_rate, "Send Rate Sec") \
211211

212212
#define V(name, _, __) IDX_QUIC_SESSION_STATS_##name,
213213
enum QuicSessionStatsIdx : int {

src/quic/node_quic_socket.cc

+13
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ QuicSocket::QuicSocket(
252252
: AsyncWrap(quic_state->env(), wrap, AsyncWrap::PROVIDER_QUICSOCKET),
253253
StatsBase(quic_state->env(), wrap),
254254
alloc_info_(MakeAllocator()),
255+
block_list_(SocketAddressBlockListWrap::New(quic_state->env())),
255256
options_(options),
256257
state_(quic_state->env()->isolate()),
257258
max_connections_(max_connections),
@@ -269,6 +270,12 @@ QuicSocket::QuicSocket(
269270

270271
EntropySource(token_secret_, kTokenSecretLen);
271272

273+
wrap->DefineOwnProperty(
274+
env()->context(),
275+
env()->block_list_string(),
276+
block_list_->object(),
277+
PropertyAttribute::ReadOnly).Check();
278+
272279
wrap->DefineOwnProperty(
273280
env()->context(),
274281
env()->state_string(),
@@ -432,6 +439,12 @@ void QuicSocket::OnReceive(
432439
return;
433440
}
434441

442+
if (UNLIKELY(block_list_->Apply(remote_addr))) {
443+
Debug(this, "Ignoring blocked remote address: %s", remote_addr);
444+
IncrementStat(&QuicSocketStats::packets_ignored);
445+
return;
446+
}
447+
435448
IncrementStat(&QuicSocketStats::bytes_received, nread);
436449

437450
const uint8_t* data = reinterpret_cast<const uint8_t*>(buf.data());

src/quic/node_quic_socket.h

+1
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ class QuicSocket : public AsyncWrap,
516516
std::vector<BaseObjectPtr<QuicEndpoint>> endpoints_;
517517
SocketAddress::Map<BaseObjectWeakPtr<QuicEndpoint>> bound_endpoints_;
518518
BaseObjectWeakPtr<QuicEndpoint> preferred_endpoint_;
519+
BaseObjectPtr<SocketAddressBlockListWrap> block_list_;
519520

520521
uint32_t flags_ = 0;
521522
uint32_t options_ = 0;

test/parallel/test-quic-blocklist.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Flags: --no-warnings
2+
'use strict';
3+
4+
const common = require('../common');
5+
6+
if (!common.hasQuic)
7+
common.skip('missing quic');
8+
9+
const { createQuicSocket, BlockList } = require('net');
10+
const assert = require('assert');
11+
12+
const { key, cert, ca } = require('../common/quic');
13+
const { once } = require('events');
14+
15+
const idleTimeout = common.platformTimeout(1);
16+
const options = { key, cert, ca, alpn: 'zzz', idleTimeout };
17+
18+
const client = createQuicSocket({ client: options });
19+
const server = createQuicSocket({ server: options });
20+
21+
assert(client.blockList instanceof BlockList);
22+
assert(server.blockList instanceof BlockList);
23+
24+
client.blockList.addAddress('10.0.0.1');
25+
26+
assert(client.blockList.check('10.0.0.1'));
27+
28+
// Connection fails because the IP address is blocked
29+
assert.rejects(client.connect({ address: '10.0.0.1' }), {
30+
code: 'ERR_OPERATION_FAILED'
31+
}).then(common.mustCall());
32+
33+
server.blockList.addAddress('127.0.0.1');
34+
35+
(async () => {
36+
server.on('session', common.mustNotCall());
37+
38+
await server.listen();
39+
40+
const session = await client.connect({
41+
address: common.localhostIPv4,
42+
port: server.endpoints[0].address.port,
43+
idleTimeout,
44+
});
45+
46+
session.on('secure', common.mustNotCall());
47+
48+
await once(session, 'close');
49+
50+
await Promise.all([server.close(), client.close()]);
51+
52+
})().then(common.mustCall());

0 commit comments

Comments
 (0)