Commit e493f04

Browse files
jasnelladuh95
authored andcommitted
quic: add block list support for endpoints
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode/Opus 4.6 PR-URL: #63483 Backport-PR-URL: #64675 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 6d6cd45 commit e493f04

14 files changed

Lines changed: 299 additions & 31 deletions

‎doc/api/quic.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,50 @@ object. Each rate limiter has a corresponding counter
189189
were dropped. A non-zero value indicates the rate limiter is actively
190190
protecting the endpoint.
191191

192+
#### Block lists
193+
194+
Endpoints can filter incoming packets by source address using a
195+
[`net.BlockList`][]. The block list is checked before any QUIC processing
196+
occurs, so blocked packets consume no resources beyond the check itself.
197+
198+
In **deny** mode (the default), packets from addresses in the list are dropped:
199+
200+
```mjs
201+
import { BlockList } from'node:net';
202+
import { listen } from'node:quic';
203+
204+
constblocked=newBlockList();
205+
blocked.addSubnet('192.168.1.0', 24); // Block an entire subnet
206+
blocked.addAddress('10.0.0.5'); // Block a specific address
207+
208+
constendpoint=awaitlisten(onSession, {
209+
endpoint: {
210+
blockList: blocked,
211+
blockListPolicy:'deny',
212+
},
213+
// ...
214+
});
215+
```
216+
217+
In **allow** mode, only packets from addresses in the list are accepted:
218+
219+
```mjs
220+
consttrusted=newBlockList();
221+
trusted.addSubnet('10.0.0.0', 8);
222+
223+
constendpoint=awaitlisten(onSession, {
224+
endpoint: {
225+
blockList: trusted,
226+
blockListPolicy:'allow',
227+
},
228+
// ...
229+
});
230+
```
231+
232+
The block list is evaluated live — rules added or removed after the endpoint
233+
is created take effect immediately. The `endpoint.stats.packetsBlocked`
234+
counter tracks how many packets have been dropped by the filter.
235+
192236
### Applications
193237

194238
Every `QuicSession` is associated with a single application protocol, negotiated
@@ -831,6 +875,11 @@ added: v23.8.0
831875
per-host rate limiter. Read only. A non-zero value indicates one or more
832876
remote addresses are creating sessions faster than the configured rate allows.
833877

878+
### `endpointStats.packetsBlocked`
879+
880+
* Type: {bigint} The total number of incoming packets dropped by the
881+
block list filter. Read only.
882+
834883
## Class: `QuicSession`
835884

836885
<!-- YAML
@@ -2429,6 +2478,34 @@ added: v23.8.0
24292478

24302479
If not specified the endpoint will bind to IPv4 `localhost` on a random port.
24312480

2481+
#### `endpointOptions.blockList`
2482+
2483+
* Type: {net.BlockList}
2484+
2485+
An optional [`net.BlockList`][] instance for filtering incoming packets by
2486+
source address. When configured, every received UDP packet is checked against
2487+
the block list before any QUIC processing occurs, minimizing resource
2488+
expenditure on blocked sources. The block list is evaluated live — rules
2489+
added to the `BlockList` object after the endpoint is created take effect
2490+
immediately.
2491+
2492+
See [`endpointOptions.blockListPolicy`][] for how matches are interpreted.
2493+
2494+
#### `endpointOptions.blockListPolicy`
2495+
2496+
* Type: {string} One of `'deny'` or `'allow'`.
2497+
***Default:**`'deny'`
2498+
2499+
Controls how the [`endpointOptions.blockList`][] is interpreted:
2500+
2501+
*`'deny'` — Packets from addresses matching the block list are dropped.
2502+
All other addresses are accepted. This is the typical blocklist mode.
2503+
*`'allow'` — Only packets from addresses matching the block list are
2504+
accepted. All other addresses are dropped. This is an allowlist mode
2505+
for restricting access to known clients.
2506+
2507+
If no block list is configured, this option has no effect.
2508+
24322509
#### `endpointOptions.addressLRUSize`
24332510

24342511
<!-- YAML
@@ -4275,6 +4352,8 @@ throughput issues caused by flow control.
42754352
[`endpoint.busy`]: #endpointbusy
42764353
[`endpoint.maxConnectionsPerHost`]: #endpointmaxconnectionsperhost
42774354
[`endpoint.maxConnectionsTotal`]: #endpointmaxconnectionstotal
4355+
[`endpointOptions.blockListPolicy`]: #endpointoptionsblocklistpolicy
4356+
[`endpointOptions.blockList`]: #endpointoptionsblocklist
42784357
[`endpointOptions.immediateCloseBurst`]: #endpointoptionsimmediatecloseburst
42794358
[`endpointOptions.immediateCloseRate`]: #endpointoptionsimmediatecloserate
42804359
[`endpointOptions.retryBurst`]: #endpointoptionsretryburst
@@ -4288,6 +4367,7 @@ throughput issues caused by flow control.
42884367
[`error.errorCode`]: #errorerrorcode
42894368
[`fs.promises.open(path, 'r')`]: fs.md#fspromisesopenpath-flags-mode
42904369
[`maxDatagramFrameSize`]: #transportparamsmaxdatagramframesize
4370+
[`net.BlockList`]: net.md#class-netblocklist
42914371
[`quic.connect()`]: #quicconnectaddress-options
42924372
[`quic.listen()`]: #quiclistenonsession-options
42934373
[`session.close()`]: #sessioncloseoptions

‎lib/internal/blocklist.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,5 @@ ObjectSetPrototypeOf(InternalBlockList.prototype, BlockList.prototype);
298298
module.exports={
299299
BlockList,
300300
InternalBlockList,
301+
kHandle,
301302
};

‎lib/internal/quic/quic.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ if (!process.features.quic || !getOptionValue('--experimental-quic')) {
3636
}
3737

3838
const{ inspect }=require('internal/util/inspect');
39+
const{
40+
BlockList,
41+
kHandle: kBlockListHandle,
42+
}=require('internal/blocklist');
3943

4044
letdebug=require('internal/util/debuglog').debuglog('quic',(fn)=>{
4145
debug=fn;
@@ -318,6 +322,8 @@ const endpointRegistry = new SafeSet();
318322
* @property {number} [immediateCloseBurst] Burst capacity for immediate close rate limiter
319323
* @property {number} [sessionCreationRate] Per-host rate limit for session creation (per second)
320324
* @property {number} [sessionCreationBurst] Per-host burst capacity for session creation rate limiter
325+
* @property {net.BlockList} [blockList] Block list for filtering incoming packets by source address
326+
* @property {'deny'|'allow'} [blockListPolicy='deny'] How to interpret the block list
321327
* @property {ArrayBufferView} [resetTokenSecret] The reset token secret
322328
* @property {bigint|number} [retryTokenExpiration] The retry token expiration
323329
* @property {number} [rxDiagnosticLoss] The receive diagnostic loss probability (range 0.0-1.0)
@@ -4048,6 +4054,8 @@ class QuicEndpoint {
40484054
immediateCloseBurst,
40494055
sessionCreationRate,
40504056
sessionCreationBurst,
4057+
blockList,
4058+
blockListPolicy ='deny',
40514059
rxDiagnosticLoss,
40524060
txDiagnosticLoss,
40534061
udpReceiveBufferSize,
@@ -4062,6 +4070,16 @@ class QuicEndpoint {
40624070
tokenSecret,
40634071
}=options;
40644072

4073+
if(blockList!==undefined){
4074+
if(!BlockList.isBlockList(blockList)){
4075+
thrownewERR_INVALID_ARG_TYPE('options.blockList',
4076+
'net.BlockList',blockList);
4077+
}
4078+
}
4079+
4080+
validateOneOf(blockListPolicy,'options.blockListPolicy',
4081+
['deny','allow']);
4082+
40654083
// All of the other options will be validated internally by the C++ code
40664084
if(address!==undefined&&!SocketAddress.isSocketAddress(address)){
40674085
if(typeofaddress==='string'){
@@ -4093,6 +4111,9 @@ class QuicEndpoint {
40934111
immediateCloseBurst,
40944112
sessionCreationRate,
40954113
sessionCreationBurst,
4114+
// Pass the C++ handle, not the JS BlockList wrapper.
4115+
blockList: blockList?.[kBlockListHandle],
4116+
blockListPolicy,
40964117
rxDiagnosticLoss,
40974118
txDiagnosticLoss,
40984119
udpReceiveBufferSize,

‎lib/internal/quic/stats.js‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const {
6868
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT,
6969
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED,
7070
IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED,
71+
IDX_STATS_ENDPOINT_PACKETS_BLOCKED,
7172

7273
IDX_STATS_SESSION_CREATED_AT,
7374
IDX_STATS_SESSION_DESTROYED_AT,
@@ -136,6 +137,7 @@ assert(IDX_STATS_ENDPOINT_STATELESS_RESET_RATE_LIMITED !== undefined);
136137
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT!==undefined);
137138
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED!==undefined);
138139
assert(IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED!==undefined);
140+
assert(IDX_STATS_ENDPOINT_PACKETS_BLOCKED!==undefined);
139141
assert(IDX_STATS_SESSION_CREATED_AT!==undefined);
140142
assert(IDX_STATS_SESSION_DESTROYED_AT!==undefined);
141143
assert(IDX_STATS_SESSION_CLOSING_AT!==undefined);
@@ -338,6 +340,12 @@ class QuicEndpointStats {
338340
returnthis.#handle[IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED];
339341
}
340342

343+
/** @type {bigint} */
344+
getpacketsBlocked(){
345+
assertIsQuicEndpointStats(this);
346+
returnthis.#handle[IDX_STATS_ENDPOINT_PACKETS_BLOCKED];
347+
}
348+
341349
toString(){
342350
returnJSONStringify(this.toJSON());
343351
}
@@ -363,6 +371,7 @@ class QuicEndpointStats {
363371
immediateCloseCount,
364372
immediateCloseRateLimited,
365373
sessionCreationRateLimited,
374+
packetsBlocked,
366375
}=this;
367376
return{
368377
__proto__: null,
@@ -387,6 +396,7 @@ class QuicEndpointStats {
387396
immediateCloseCount: `${immediateCloseCount}`,
388397
immediateCloseRateLimited: `${immediateCloseRateLimited}`,
389398
sessionCreationRateLimited: `${sessionCreationRateLimited}`,
399+
packetsBlocked: `${packetsBlocked}`,
390400
};
391401
}
392402

@@ -421,6 +431,7 @@ class QuicEndpointStats {
421431
immediateCloseCount,
422432
immediateCloseRateLimited,
423433
sessionCreationRateLimited,
434+
packetsBlocked,
424435
}=this;
425436

426437
return`QuicEndpointStats ${inspect({
@@ -443,6 +454,7 @@ class QuicEndpointStats {
443454
immediateCloseCount,
444455
immediateCloseRateLimited,
445456
sessionCreationRateLimited,
457+
packetsBlocked,
446458
},opts)}`;
447459
}
448460

‎src/node_sockaddr.cc‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,7 @@ void SocketAddressBlockList::AddSocketAddressMask(
434434
rules_.emplace_front(std::move(rule));
435435
}
436436

437-
boolSocketAddressBlockList::Apply(
438-
const std::shared_ptr<SocketAddress>& address) {
437+
boolSocketAddressBlockList::Apply(const SocketAddress& address) {
439438
Mutex::ScopedLock lock(mutex_);
440439
for (constauto& rule : rules_) {
441440
if (rule->Apply(address)) returntrue;
@@ -457,8 +456,8 @@ SocketAddressBlockList::SocketAddressMaskRule::SocketAddressMaskRule(
457456
: network(network_), prefix(prefix_) {}
458457

459458
boolSocketAddressBlockList::SocketAddressRule::Apply(
460-
conststd::shared_ptr<SocketAddress>& address) {
461-
returnthis->address->is_match(*address.get());
459+
const SocketAddress& address) {
460+
returnthis->address->is_match(address);
462461
}
463462

464463
std::string SocketAddressBlockList::SocketAddressRule::ToString() {
@@ -470,8 +469,8 @@ std::string SocketAddressBlockList::SocketAddressRule::ToString() {
470469
}
471470

472471
boolSocketAddressBlockList::SocketAddressRangeRule::Apply(
473-
conststd::shared_ptr<SocketAddress>& address) {
474-
return*address.get() >= *start.get() && *address.get() <= *end.get();
472+
const SocketAddress& address) {
473+
return address >= *start.get() && address <= *end.get();
475474
}
476475

477476
std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
@@ -485,8 +484,8 @@ std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
485484
}
486485

487486
boolSocketAddressBlockList::SocketAddressMaskRule::Apply(
488-
conststd::shared_ptr<SocketAddress>& address) {
489-
return address->is_in_network(*network.get(), prefix);
487+
const SocketAddress& address) {
488+
return address.is_in_network(*network.get(), prefix);
490489
}
491490

492491
std::string SocketAddressBlockList::SocketAddressMaskRule::ToString() {
@@ -656,7 +655,7 @@ void SocketAddressBlockListWrap::Check(
656655
SocketAddressBase* addr;
657656
ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);
658657

659-
args.GetReturnValue().Set(wrap->blocklist_->Apply(addr->address()));
658+
args.GetReturnValue().Set(wrap->blocklist_->Apply(*addr->address()));
660659
}
661660

662661
voidSocketAddressBlockListWrap::GetRules(

‎src/node_sockaddr.h‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,14 @@ class SocketAddressBlockList : public MemoryRetainer {
258258
voidAddSocketAddressMask(const std::shared_ptr<SocketAddress>& address,
259259
int prefix);
260260

261-
boolApply(conststd::shared_ptr<SocketAddress>& address);
261+
boolApply(const SocketAddress& address);
262262

263263
size_tsize() const { return rules_.size(); }
264264

265265
v8::MaybeLocal<v8::Array> ListRules(Environment* env);
266266

267267
structRule : publicMemoryRetainer {
268-
virtualboolApply(conststd::shared_ptr<SocketAddress>& address) = 0;
268+
virtualboolApply(const SocketAddress& address) = 0;
269269
inline v8::MaybeLocal<v8::Value> ToV8String(Environment* env);
270270
virtual std::string ToString() = 0;
271271
};
@@ -275,7 +275,7 @@ class SocketAddressBlockList : public MemoryRetainer {
275275

276276
explicitSocketAddressRule(const std::shared_ptr<SocketAddress>& address);
277277

278-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
278+
boolApply(const SocketAddress& address) override;
279279
std::string ToString() override;
280280

281281
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -290,7 +290,7 @@ class SocketAddressBlockList : public MemoryRetainer {
290290
SocketAddressRangeRule(const std::shared_ptr<SocketAddress>& start,
291291
const std::shared_ptr<SocketAddress>& end);
292292

293-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
293+
boolApply(const SocketAddress& address) override;
294294
std::string ToString() override;
295295

296296
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -305,7 +305,7 @@ class SocketAddressBlockList : public MemoryRetainer {
305305
SocketAddressMaskRule(const std::shared_ptr<SocketAddress>& address,
306306
int prefix);
307307

308-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
308+
boolApply(const SocketAddress& address) override;
309309
std::string ToString() override;
310310

311311
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -353,6 +353,10 @@ class SocketAddressBlockListWrap : public BaseObject {
353353
std::shared_ptr<SocketAddressBlockList> blocklist =
354354
std::make_shared<SocketAddressBlockList>());
355355

356+
inlineconst std::shared_ptr<SocketAddressBlockList>& blocklist() const {
357+
return blocklist_;
358+
}
359+
356360
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
357361
SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap)
358362
SET_SELF_SIZE(SocketAddressBlockListWrap)

‎src/quic/bindingdata.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SessionManager;
7070
V(ack_delay_exponent, "ackDelayExponent") \
7171
V(active_connection_id_limit, "activeConnectionIDLimit") \
7272
V(address_lru_size, "addressLRUSize") \
73+
V(allow, "allow") \
7374
V(application, "application") \
7475
V(authoritative, "authoritative") \
7576
V(bbr, "bbr") \
@@ -81,6 +82,7 @@ class SessionManager;
8182
V(crl, "crl") \
8283
V(cubic, "cubic") \
8384
V(datagram_drop_policy, "datagramDropPolicy") \
85+
V(deny, "deny") \
8486
V(disable_stateless_reset, "disableStatelessReset") \
8587
V(draining_period_multiplier, "drainingPeriodMultiplier") \
8688
V(enable_connect_protocol, "enableConnectProtocol") \
@@ -127,6 +129,8 @@ class SessionManager;
127129
V(immediate_close_burst, "immediateCloseBurst") \
128130
V(session_creation_rate, "sessionCreationRate") \
129131
V(session_creation_burst, "sessionCreationBurst") \
132+
V(block_list, "blockList") \
133+
V(block_list_policy, "blockListPolicy") \
130134
V(max_stream_window, "maxStreamWindow") \
131135
V(max_window, "maxWindow") \
132136
V(min_version, "minVersion") \

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit e493f04

Browse files
jasnelladuh95
authored andcommitted
quic: add block list support for endpoints
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode/Opus 4.6 PR-URL: #63483 Backport-PR-URL: #64675 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 6d6cd45 commit e493f04

14 files changed

Lines changed: 299 additions & 31 deletions

‎doc/api/quic.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,50 @@ object. Each rate limiter has a corresponding counter
189189
were dropped. A non-zero value indicates the rate limiter is actively
190190
protecting the endpoint.
191191

192+
#### Block lists
193+
194+
Endpoints can filter incoming packets by source address using a
195+
[`net.BlockList`][]. The block list is checked before any QUIC processing
196+
occurs, so blocked packets consume no resources beyond the check itself.
197+
198+
In **deny** mode (the default), packets from addresses in the list are dropped:
199+
200+
```mjs
201+
import { BlockList } from'node:net';
202+
import { listen } from'node:quic';
203+
204+
constblocked=newBlockList();
205+
blocked.addSubnet('192.168.1.0', 24); // Block an entire subnet
206+
blocked.addAddress('10.0.0.5'); // Block a specific address
207+
208+
constendpoint=awaitlisten(onSession, {
209+
endpoint: {
210+
blockList: blocked,
211+
blockListPolicy:'deny',
212+
},
213+
// ...
214+
});
215+
```
216+
217+
In **allow** mode, only packets from addresses in the list are accepted:
218+
219+
```mjs
220+
consttrusted=newBlockList();
221+
trusted.addSubnet('10.0.0.0', 8);
222+
223+
constendpoint=awaitlisten(onSession, {
224+
endpoint: {
225+
blockList: trusted,
226+
blockListPolicy:'allow',
227+
},
228+
// ...
229+
});
230+
```
231+
232+
The block list is evaluated live — rules added or removed after the endpoint
233+
is created take effect immediately. The `endpoint.stats.packetsBlocked`
234+
counter tracks how many packets have been dropped by the filter.
235+
192236
### Applications
193237

194238
Every `QuicSession` is associated with a single application protocol, negotiated
@@ -831,6 +875,11 @@ added: v23.8.0
831875
per-host rate limiter. Read only. A non-zero value indicates one or more
832876
remote addresses are creating sessions faster than the configured rate allows.
833877

878+
### `endpointStats.packetsBlocked`
879+
880+
* Type: {bigint} The total number of incoming packets dropped by the
881+
block list filter. Read only.
882+
834883
## Class: `QuicSession`
835884

836885
<!-- YAML
@@ -2429,6 +2478,34 @@ added: v23.8.0
24292478

24302479
If not specified the endpoint will bind to IPv4 `localhost` on a random port.
24312480

2481+
#### `endpointOptions.blockList`
2482+
2483+
* Type: {net.BlockList}
2484+
2485+
An optional [`net.BlockList`][] instance for filtering incoming packets by
2486+
source address. When configured, every received UDP packet is checked against
2487+
the block list before any QUIC processing occurs, minimizing resource
2488+
expenditure on blocked sources. The block list is evaluated live — rules
2489+
added to the `BlockList` object after the endpoint is created take effect
2490+
immediately.
2491+
2492+
See [`endpointOptions.blockListPolicy`][] for how matches are interpreted.
2493+
2494+
#### `endpointOptions.blockListPolicy`
2495+
2496+
* Type: {string} One of `'deny'` or `'allow'`.
2497+
***Default:**`'deny'`
2498+
2499+
Controls how the [`endpointOptions.blockList`][] is interpreted:
2500+
2501+
*`'deny'` — Packets from addresses matching the block list are dropped.
2502+
All other addresses are accepted. This is the typical blocklist mode.
2503+
*`'allow'` — Only packets from addresses matching the block list are
2504+
accepted. All other addresses are dropped. This is an allowlist mode
2505+
for restricting access to known clients.
2506+
2507+
If no block list is configured, this option has no effect.
2508+
24322509
#### `endpointOptions.addressLRUSize`
24332510

24342511
<!-- YAML
@@ -4275,6 +4352,8 @@ throughput issues caused by flow control.
42754352
[`endpoint.busy`]: #endpointbusy
42764353
[`endpoint.maxConnectionsPerHost`]: #endpointmaxconnectionsperhost
42774354
[`endpoint.maxConnectionsTotal`]: #endpointmaxconnectionstotal
4355+
[`endpointOptions.blockListPolicy`]: #endpointoptionsblocklistpolicy
4356+
[`endpointOptions.blockList`]: #endpointoptionsblocklist
42784357
[`endpointOptions.immediateCloseBurst`]: #endpointoptionsimmediatecloseburst
42794358
[`endpointOptions.immediateCloseRate`]: #endpointoptionsimmediatecloserate
42804359
[`endpointOptions.retryBurst`]: #endpointoptionsretryburst
@@ -4288,6 +4367,7 @@ throughput issues caused by flow control.
42884367
[`error.errorCode`]: #errorerrorcode
42894368
[`fs.promises.open(path, 'r')`]: fs.md#fspromisesopenpath-flags-mode
42904369
[`maxDatagramFrameSize`]: #transportparamsmaxdatagramframesize
4370+
[`net.BlockList`]: net.md#class-netblocklist
42914371
[`quic.connect()`]: #quicconnectaddress-options
42924372
[`quic.listen()`]: #quiclistenonsession-options
42934373
[`session.close()`]: #sessioncloseoptions

‎lib/internal/blocklist.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,5 @@ ObjectSetPrototypeOf(InternalBlockList.prototype, BlockList.prototype);
298298
module.exports={
299299
BlockList,
300300
InternalBlockList,
301+
kHandle,
301302
};

‎lib/internal/quic/quic.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ if (!process.features.quic || !getOptionValue('--experimental-quic')) {
3636
}
3737

3838
const{ inspect }=require('internal/util/inspect');
39+
const{
40+
BlockList,
41+
kHandle: kBlockListHandle,
42+
}=require('internal/blocklist');
3943

4044
letdebug=require('internal/util/debuglog').debuglog('quic',(fn)=>{
4145
debug=fn;
@@ -318,6 +322,8 @@ const endpointRegistry = new SafeSet();
318322
* @property {number} [immediateCloseBurst] Burst capacity for immediate close rate limiter
319323
* @property {number} [sessionCreationRate] Per-host rate limit for session creation (per second)
320324
* @property {number} [sessionCreationBurst] Per-host burst capacity for session creation rate limiter
325+
* @property {net.BlockList} [blockList] Block list for filtering incoming packets by source address
326+
* @property {'deny'|'allow'} [blockListPolicy='deny'] How to interpret the block list
321327
* @property {ArrayBufferView} [resetTokenSecret] The reset token secret
322328
* @property {bigint|number} [retryTokenExpiration] The retry token expiration
323329
* @property {number} [rxDiagnosticLoss] The receive diagnostic loss probability (range 0.0-1.0)
@@ -4048,6 +4054,8 @@ class QuicEndpoint {
40484054
immediateCloseBurst,
40494055
sessionCreationRate,
40504056
sessionCreationBurst,
4057+
blockList,
4058+
blockListPolicy ='deny',
40514059
rxDiagnosticLoss,
40524060
txDiagnosticLoss,
40534061
udpReceiveBufferSize,
@@ -4062,6 +4070,16 @@ class QuicEndpoint {
40624070
tokenSecret,
40634071
}=options;
40644072

4073+
if(blockList!==undefined){
4074+
if(!BlockList.isBlockList(blockList)){
4075+
thrownewERR_INVALID_ARG_TYPE('options.blockList',
4076+
'net.BlockList',blockList);
4077+
}
4078+
}
4079+
4080+
validateOneOf(blockListPolicy,'options.blockListPolicy',
4081+
['deny','allow']);
4082+
40654083
// All of the other options will be validated internally by the C++ code
40664084
if(address!==undefined&&!SocketAddress.isSocketAddress(address)){
40674085
if(typeofaddress==='string'){
@@ -4093,6 +4111,9 @@ class QuicEndpoint {
40934111
immediateCloseBurst,
40944112
sessionCreationRate,
40954113
sessionCreationBurst,
4114+
// Pass the C++ handle, not the JS BlockList wrapper.
4115+
blockList: blockList?.[kBlockListHandle],
4116+
blockListPolicy,
40964117
rxDiagnosticLoss,
40974118
txDiagnosticLoss,
40984119
udpReceiveBufferSize,

‎lib/internal/quic/stats.js‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const {
6868
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT,
6969
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED,
7070
IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED,
71+
IDX_STATS_ENDPOINT_PACKETS_BLOCKED,
7172

7273
IDX_STATS_SESSION_CREATED_AT,
7374
IDX_STATS_SESSION_DESTROYED_AT,
@@ -136,6 +137,7 @@ assert(IDX_STATS_ENDPOINT_STATELESS_RESET_RATE_LIMITED !== undefined);
136137
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT!==undefined);
137138
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED!==undefined);
138139
assert(IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED!==undefined);
140+
assert(IDX_STATS_ENDPOINT_PACKETS_BLOCKED!==undefined);
139141
assert(IDX_STATS_SESSION_CREATED_AT!==undefined);
140142
assert(IDX_STATS_SESSION_DESTROYED_AT!==undefined);
141143
assert(IDX_STATS_SESSION_CLOSING_AT!==undefined);
@@ -338,6 +340,12 @@ class QuicEndpointStats {
338340
returnthis.#handle[IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED];
339341
}
340342

343+
/** @type {bigint} */
344+
getpacketsBlocked(){
345+
assertIsQuicEndpointStats(this);
346+
returnthis.#handle[IDX_STATS_ENDPOINT_PACKETS_BLOCKED];
347+
}
348+
341349
toString(){
342350
returnJSONStringify(this.toJSON());
343351
}
@@ -363,6 +371,7 @@ class QuicEndpointStats {
363371
immediateCloseCount,
364372
immediateCloseRateLimited,
365373
sessionCreationRateLimited,
374+
packetsBlocked,
366375
}=this;
367376
return{
368377
__proto__: null,
@@ -387,6 +396,7 @@ class QuicEndpointStats {
387396
immediateCloseCount: `${immediateCloseCount}`,
388397
immediateCloseRateLimited: `${immediateCloseRateLimited}`,
389398
sessionCreationRateLimited: `${sessionCreationRateLimited}`,
399+
packetsBlocked: `${packetsBlocked}`,
390400
};
391401
}
392402

@@ -421,6 +431,7 @@ class QuicEndpointStats {
421431
immediateCloseCount,
422432
immediateCloseRateLimited,
423433
sessionCreationRateLimited,
434+
packetsBlocked,
424435
}=this;
425436

426437
return`QuicEndpointStats ${inspect({
@@ -443,6 +454,7 @@ class QuicEndpointStats {
443454
immediateCloseCount,
444455
immediateCloseRateLimited,
445456
sessionCreationRateLimited,
457+
packetsBlocked,
446458
},opts)}`;
447459
}
448460

‎src/node_sockaddr.cc‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,7 @@ void SocketAddressBlockList::AddSocketAddressMask(
434434
rules_.emplace_front(std::move(rule));
435435
}
436436

437-
boolSocketAddressBlockList::Apply(
438-
const std::shared_ptr<SocketAddress>& address) {
437+
boolSocketAddressBlockList::Apply(const SocketAddress& address) {
439438
Mutex::ScopedLock lock(mutex_);
440439
for (constauto& rule : rules_) {
441440
if (rule->Apply(address)) returntrue;
@@ -457,8 +456,8 @@ SocketAddressBlockList::SocketAddressMaskRule::SocketAddressMaskRule(
457456
: network(network_), prefix(prefix_) {}
458457

459458
boolSocketAddressBlockList::SocketAddressRule::Apply(
460-
conststd::shared_ptr<SocketAddress>& address) {
461-
returnthis->address->is_match(*address.get());
459+
const SocketAddress& address) {
460+
returnthis->address->is_match(address);
462461
}
463462

464463
std::string SocketAddressBlockList::SocketAddressRule::ToString() {
@@ -470,8 +469,8 @@ std::string SocketAddressBlockList::SocketAddressRule::ToString() {
470469
}
471470

472471
boolSocketAddressBlockList::SocketAddressRangeRule::Apply(
473-
conststd::shared_ptr<SocketAddress>& address) {
474-
return*address.get() >= *start.get() && *address.get() <= *end.get();
472+
const SocketAddress& address) {
473+
return address >= *start.get() && address <= *end.get();
475474
}
476475

477476
std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
@@ -485,8 +484,8 @@ std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
485484
}
486485

487486
boolSocketAddressBlockList::SocketAddressMaskRule::Apply(
488-
conststd::shared_ptr<SocketAddress>& address) {
489-
return address->is_in_network(*network.get(), prefix);
487+
const SocketAddress& address) {
488+
return address.is_in_network(*network.get(), prefix);
490489
}
491490

492491
std::string SocketAddressBlockList::SocketAddressMaskRule::ToString() {
@@ -656,7 +655,7 @@ void SocketAddressBlockListWrap::Check(
656655
SocketAddressBase* addr;
657656
ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);
658657

659-
args.GetReturnValue().Set(wrap->blocklist_->Apply(addr->address()));
658+
args.GetReturnValue().Set(wrap->blocklist_->Apply(*addr->address()));
660659
}
661660

662661
voidSocketAddressBlockListWrap::GetRules(

‎src/node_sockaddr.h‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,14 @@ class SocketAddressBlockList : public MemoryRetainer {
258258
voidAddSocketAddressMask(const std::shared_ptr<SocketAddress>& address,
259259
int prefix);
260260

261-
boolApply(conststd::shared_ptr<SocketAddress>& address);
261+
boolApply(const SocketAddress& address);
262262

263263
size_tsize() const { return rules_.size(); }
264264

265265
v8::MaybeLocal<v8::Array> ListRules(Environment* env);
266266

267267
structRule : publicMemoryRetainer {
268-
virtualboolApply(conststd::shared_ptr<SocketAddress>& address) = 0;
268+
virtualboolApply(const SocketAddress& address) = 0;
269269
inline v8::MaybeLocal<v8::Value> ToV8String(Environment* env);
270270
virtual std::string ToString() = 0;
271271
};
@@ -275,7 +275,7 @@ class SocketAddressBlockList : public MemoryRetainer {
275275

276276
explicitSocketAddressRule(const std::shared_ptr<SocketAddress>& address);
277277

278-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
278+
boolApply(const SocketAddress& address) override;
279279
std::string ToString() override;
280280

281281
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -290,7 +290,7 @@ class SocketAddressBlockList : public MemoryRetainer {
290290
SocketAddressRangeRule(const std::shared_ptr<SocketAddress>& start,
291291
const std::shared_ptr<SocketAddress>& end);
292292

293-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
293+
boolApply(const SocketAddress& address) override;
294294
std::string ToString() override;
295295

296296
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -305,7 +305,7 @@ class SocketAddressBlockList : public MemoryRetainer {
305305
SocketAddressMaskRule(const std::shared_ptr<SocketAddress>& address,
306306
int prefix);
307307

308-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
308+
boolApply(const SocketAddress& address) override;
309309
std::string ToString() override;
310310

311311
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -353,6 +353,10 @@ class SocketAddressBlockListWrap : public BaseObject {
353353
std::shared_ptr<SocketAddressBlockList> blocklist =
354354
std::make_shared<SocketAddressBlockList>());
355355

356+
inlineconst std::shared_ptr<SocketAddressBlockList>& blocklist() const {
357+
return blocklist_;
358+
}
359+
356360
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
357361
SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap)
358362
SET_SELF_SIZE(SocketAddressBlockListWrap)

‎src/quic/bindingdata.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SessionManager;
7070
V(ack_delay_exponent, "ackDelayExponent") \
7171
V(active_connection_id_limit, "activeConnectionIDLimit") \
7272
V(address_lru_size, "addressLRUSize") \
73+
V(allow, "allow") \
7374
V(application, "application") \
7475
V(authoritative, "authoritative") \
7576
V(bbr, "bbr") \
@@ -81,6 +82,7 @@ class SessionManager;
8182
V(crl, "crl") \
8283
V(cubic, "cubic") \
8384
V(datagram_drop_policy, "datagramDropPolicy") \
85+
V(deny, "deny") \
8486
V(disable_stateless_reset, "disableStatelessReset") \
8587
V(draining_period_multiplier, "drainingPeriodMultiplier") \
8688
V(enable_connect_protocol, "enableConnectProtocol") \
@@ -127,6 +129,8 @@ class SessionManager;
127129
V(immediate_close_burst, "immediateCloseBurst") \
128130
V(session_creation_rate, "sessionCreationRate") \
129131
V(session_creation_burst, "sessionCreationBurst") \
132+
V(block_list, "blockList") \
133+
V(block_list_policy, "blockListPolicy") \
130134
V(max_stream_window, "maxStreamWindow") \
131135
V(max_window, "maxWindow") \
132136
V(min_version, "minVersion") \

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit e493f04

Browse files
jasnelladuh95
authored andcommitted
quic: add block list support for endpoints
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode/Opus 4.6 PR-URL: #63483 Backport-PR-URL: #64675 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 6d6cd45 commit e493f04

14 files changed

Lines changed: 299 additions & 31 deletions

‎doc/api/quic.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,50 @@ object. Each rate limiter has a corresponding counter
189189
were dropped. A non-zero value indicates the rate limiter is actively
190190
protecting the endpoint.
191191

192+
#### Block lists
193+
194+
Endpoints can filter incoming packets by source address using a
195+
[`net.BlockList`][]. The block list is checked before any QUIC processing
196+
occurs, so blocked packets consume no resources beyond the check itself.
197+
198+
In **deny** mode (the default), packets from addresses in the list are dropped:
199+
200+
```mjs
201+
import { BlockList } from'node:net';
202+
import { listen } from'node:quic';
203+
204+
constblocked=newBlockList();
205+
blocked.addSubnet('192.168.1.0', 24); // Block an entire subnet
206+
blocked.addAddress('10.0.0.5'); // Block a specific address
207+
208+
constendpoint=awaitlisten(onSession, {
209+
endpoint: {
210+
blockList: blocked,
211+
blockListPolicy:'deny',
212+
},
213+
// ...
214+
});
215+
```
216+
217+
In **allow** mode, only packets from addresses in the list are accepted:
218+
219+
```mjs
220+
consttrusted=newBlockList();
221+
trusted.addSubnet('10.0.0.0', 8);
222+
223+
constendpoint=awaitlisten(onSession, {
224+
endpoint: {
225+
blockList: trusted,
226+
blockListPolicy:'allow',
227+
},
228+
// ...
229+
});
230+
```
231+
232+
The block list is evaluated live — rules added or removed after the endpoint
233+
is created take effect immediately. The `endpoint.stats.packetsBlocked`
234+
counter tracks how many packets have been dropped by the filter.
235+
192236
### Applications
193237

194238
Every `QuicSession` is associated with a single application protocol, negotiated
@@ -831,6 +875,11 @@ added: v23.8.0
831875
per-host rate limiter. Read only. A non-zero value indicates one or more
832876
remote addresses are creating sessions faster than the configured rate allows.
833877

878+
### `endpointStats.packetsBlocked`
879+
880+
* Type: {bigint} The total number of incoming packets dropped by the
881+
block list filter. Read only.
882+
834883
## Class: `QuicSession`
835884

836885
<!-- YAML
@@ -2429,6 +2478,34 @@ added: v23.8.0
24292478

24302479
If not specified the endpoint will bind to IPv4 `localhost` on a random port.
24312480

2481+
#### `endpointOptions.blockList`
2482+
2483+
* Type: {net.BlockList}
2484+
2485+
An optional [`net.BlockList`][] instance for filtering incoming packets by
2486+
source address. When configured, every received UDP packet is checked against
2487+
the block list before any QUIC processing occurs, minimizing resource
2488+
expenditure on blocked sources. The block list is evaluated live — rules
2489+
added to the `BlockList` object after the endpoint is created take effect
2490+
immediately.
2491+
2492+
See [`endpointOptions.blockListPolicy`][] for how matches are interpreted.
2493+
2494+
#### `endpointOptions.blockListPolicy`
2495+
2496+
* Type: {string} One of `'deny'` or `'allow'`.
2497+
***Default:**`'deny'`
2498+
2499+
Controls how the [`endpointOptions.blockList`][] is interpreted:
2500+
2501+
*`'deny'` — Packets from addresses matching the block list are dropped.
2502+
All other addresses are accepted. This is the typical blocklist mode.
2503+
*`'allow'` — Only packets from addresses matching the block list are
2504+
accepted. All other addresses are dropped. This is an allowlist mode
2505+
for restricting access to known clients.
2506+
2507+
If no block list is configured, this option has no effect.
2508+
24322509
#### `endpointOptions.addressLRUSize`
24332510

24342511
<!-- YAML
@@ -4275,6 +4352,8 @@ throughput issues caused by flow control.
42754352
[`endpoint.busy`]: #endpointbusy
42764353
[`endpoint.maxConnectionsPerHost`]: #endpointmaxconnectionsperhost
42774354
[`endpoint.maxConnectionsTotal`]: #endpointmaxconnectionstotal
4355+
[`endpointOptions.blockListPolicy`]: #endpointoptionsblocklistpolicy
4356+
[`endpointOptions.blockList`]: #endpointoptionsblocklist
42784357
[`endpointOptions.immediateCloseBurst`]: #endpointoptionsimmediatecloseburst
42794358
[`endpointOptions.immediateCloseRate`]: #endpointoptionsimmediatecloserate
42804359
[`endpointOptions.retryBurst`]: #endpointoptionsretryburst
@@ -4288,6 +4367,7 @@ throughput issues caused by flow control.
42884367
[`error.errorCode`]: #errorerrorcode
42894368
[`fs.promises.open(path, 'r')`]: fs.md#fspromisesopenpath-flags-mode
42904369
[`maxDatagramFrameSize`]: #transportparamsmaxdatagramframesize
4370+
[`net.BlockList`]: net.md#class-netblocklist
42914371
[`quic.connect()`]: #quicconnectaddress-options
42924372
[`quic.listen()`]: #quiclistenonsession-options
42934373
[`session.close()`]: #sessioncloseoptions

‎lib/internal/blocklist.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,5 @@ ObjectSetPrototypeOf(InternalBlockList.prototype, BlockList.prototype);
298298
module.exports={
299299
BlockList,
300300
InternalBlockList,
301+
kHandle,
301302
};

‎lib/internal/quic/quic.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ if (!process.features.quic || !getOptionValue('--experimental-quic')) {
3636
}
3737

3838
const{ inspect }=require('internal/util/inspect');
39+
const{
40+
BlockList,
41+
kHandle: kBlockListHandle,
42+
}=require('internal/blocklist');
3943

4044
letdebug=require('internal/util/debuglog').debuglog('quic',(fn)=>{
4145
debug=fn;
@@ -318,6 +322,8 @@ const endpointRegistry = new SafeSet();
318322
* @property {number} [immediateCloseBurst] Burst capacity for immediate close rate limiter
319323
* @property {number} [sessionCreationRate] Per-host rate limit for session creation (per second)
320324
* @property {number} [sessionCreationBurst] Per-host burst capacity for session creation rate limiter
325+
* @property {net.BlockList} [blockList] Block list for filtering incoming packets by source address
326+
* @property {'deny'|'allow'} [blockListPolicy='deny'] How to interpret the block list
321327
* @property {ArrayBufferView} [resetTokenSecret] The reset token secret
322328
* @property {bigint|number} [retryTokenExpiration] The retry token expiration
323329
* @property {number} [rxDiagnosticLoss] The receive diagnostic loss probability (range 0.0-1.0)
@@ -4048,6 +4054,8 @@ class QuicEndpoint {
40484054
immediateCloseBurst,
40494055
sessionCreationRate,
40504056
sessionCreationBurst,
4057+
blockList,
4058+
blockListPolicy ='deny',
40514059
rxDiagnosticLoss,
40524060
txDiagnosticLoss,
40534061
udpReceiveBufferSize,
@@ -4062,6 +4070,16 @@ class QuicEndpoint {
40624070
tokenSecret,
40634071
}=options;
40644072

4073+
if(blockList!==undefined){
4074+
if(!BlockList.isBlockList(blockList)){
4075+
thrownewERR_INVALID_ARG_TYPE('options.blockList',
4076+
'net.BlockList',blockList);
4077+
}
4078+
}
4079+
4080+
validateOneOf(blockListPolicy,'options.blockListPolicy',
4081+
['deny','allow']);
4082+
40654083
// All of the other options will be validated internally by the C++ code
40664084
if(address!==undefined&&!SocketAddress.isSocketAddress(address)){
40674085
if(typeofaddress==='string'){
@@ -4093,6 +4111,9 @@ class QuicEndpoint {
40934111
immediateCloseBurst,
40944112
sessionCreationRate,
40954113
sessionCreationBurst,
4114+
// Pass the C++ handle, not the JS BlockList wrapper.
4115+
blockList: blockList?.[kBlockListHandle],
4116+
blockListPolicy,
40964117
rxDiagnosticLoss,
40974118
txDiagnosticLoss,
40984119
udpReceiveBufferSize,

‎lib/internal/quic/stats.js‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const {
6868
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT,
6969
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED,
7070
IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED,
71+
IDX_STATS_ENDPOINT_PACKETS_BLOCKED,
7172

7273
IDX_STATS_SESSION_CREATED_AT,
7374
IDX_STATS_SESSION_DESTROYED_AT,
@@ -136,6 +137,7 @@ assert(IDX_STATS_ENDPOINT_STATELESS_RESET_RATE_LIMITED !== undefined);
136137
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT!==undefined);
137138
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED!==undefined);
138139
assert(IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED!==undefined);
140+
assert(IDX_STATS_ENDPOINT_PACKETS_BLOCKED!==undefined);
139141
assert(IDX_STATS_SESSION_CREATED_AT!==undefined);
140142
assert(IDX_STATS_SESSION_DESTROYED_AT!==undefined);
141143
assert(IDX_STATS_SESSION_CLOSING_AT!==undefined);
@@ -338,6 +340,12 @@ class QuicEndpointStats {
338340
returnthis.#handle[IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED];
339341
}
340342

343+
/** @type {bigint} */
344+
getpacketsBlocked(){
345+
assertIsQuicEndpointStats(this);
346+
returnthis.#handle[IDX_STATS_ENDPOINT_PACKETS_BLOCKED];
347+
}
348+
341349
toString(){
342350
returnJSONStringify(this.toJSON());
343351
}
@@ -363,6 +371,7 @@ class QuicEndpointStats {
363371
immediateCloseCount,
364372
immediateCloseRateLimited,
365373
sessionCreationRateLimited,
374+
packetsBlocked,
366375
}=this;
367376
return{
368377
__proto__: null,
@@ -387,6 +396,7 @@ class QuicEndpointStats {
387396
immediateCloseCount: `${immediateCloseCount}`,
388397
immediateCloseRateLimited: `${immediateCloseRateLimited}`,
389398
sessionCreationRateLimited: `${sessionCreationRateLimited}`,
399+
packetsBlocked: `${packetsBlocked}`,
390400
};
391401
}
392402

@@ -421,6 +431,7 @@ class QuicEndpointStats {
421431
immediateCloseCount,
422432
immediateCloseRateLimited,
423433
sessionCreationRateLimited,
434+
packetsBlocked,
424435
}=this;
425436

426437
return`QuicEndpointStats ${inspect({
@@ -443,6 +454,7 @@ class QuicEndpointStats {
443454
immediateCloseCount,
444455
immediateCloseRateLimited,
445456
sessionCreationRateLimited,
457+
packetsBlocked,
446458
},opts)}`;
447459
}
448460

‎src/node_sockaddr.cc‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,7 @@ void SocketAddressBlockList::AddSocketAddressMask(
434434
rules_.emplace_front(std::move(rule));
435435
}
436436

437-
boolSocketAddressBlockList::Apply(
438-
const std::shared_ptr<SocketAddress>& address) {
437+
boolSocketAddressBlockList::Apply(const SocketAddress& address) {
439438
Mutex::ScopedLock lock(mutex_);
440439
for (constauto& rule : rules_) {
441440
if (rule->Apply(address)) returntrue;
@@ -457,8 +456,8 @@ SocketAddressBlockList::SocketAddressMaskRule::SocketAddressMaskRule(
457456
: network(network_), prefix(prefix_) {}
458457

459458
boolSocketAddressBlockList::SocketAddressRule::Apply(
460-
conststd::shared_ptr<SocketAddress>& address) {
461-
returnthis->address->is_match(*address.get());
459+
const SocketAddress& address) {
460+
returnthis->address->is_match(address);
462461
}
463462

464463
std::string SocketAddressBlockList::SocketAddressRule::ToString() {
@@ -470,8 +469,8 @@ std::string SocketAddressBlockList::SocketAddressRule::ToString() {
470469
}
471470

472471
boolSocketAddressBlockList::SocketAddressRangeRule::Apply(
473-
conststd::shared_ptr<SocketAddress>& address) {
474-
return*address.get() >= *start.get() && *address.get() <= *end.get();
472+
const SocketAddress& address) {
473+
return address >= *start.get() && address <= *end.get();
475474
}
476475

477476
std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
@@ -485,8 +484,8 @@ std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
485484
}
486485

487486
boolSocketAddressBlockList::SocketAddressMaskRule::Apply(
488-
conststd::shared_ptr<SocketAddress>& address) {
489-
return address->is_in_network(*network.get(), prefix);
487+
const SocketAddress& address) {
488+
return address.is_in_network(*network.get(), prefix);
490489
}
491490

492491
std::string SocketAddressBlockList::SocketAddressMaskRule::ToString() {
@@ -656,7 +655,7 @@ void SocketAddressBlockListWrap::Check(
656655
SocketAddressBase* addr;
657656
ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);
658657

659-
args.GetReturnValue().Set(wrap->blocklist_->Apply(addr->address()));
658+
args.GetReturnValue().Set(wrap->blocklist_->Apply(*addr->address()));
660659
}
661660

662661
voidSocketAddressBlockListWrap::GetRules(

‎src/node_sockaddr.h‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,14 @@ class SocketAddressBlockList : public MemoryRetainer {
258258
voidAddSocketAddressMask(const std::shared_ptr<SocketAddress>& address,
259259
int prefix);
260260

261-
boolApply(conststd::shared_ptr<SocketAddress>& address);
261+
boolApply(const SocketAddress& address);
262262

263263
size_tsize() const { return rules_.size(); }
264264

265265
v8::MaybeLocal<v8::Array> ListRules(Environment* env);
266266

267267
structRule : publicMemoryRetainer {
268-
virtualboolApply(conststd::shared_ptr<SocketAddress>& address) = 0;
268+
virtualboolApply(const SocketAddress& address) = 0;
269269
inline v8::MaybeLocal<v8::Value> ToV8String(Environment* env);
270270
virtual std::string ToString() = 0;
271271
};
@@ -275,7 +275,7 @@ class SocketAddressBlockList : public MemoryRetainer {
275275

276276
explicitSocketAddressRule(const std::shared_ptr<SocketAddress>& address);
277277

278-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
278+
boolApply(const SocketAddress& address) override;
279279
std::string ToString() override;
280280

281281
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -290,7 +290,7 @@ class SocketAddressBlockList : public MemoryRetainer {
290290
SocketAddressRangeRule(const std::shared_ptr<SocketAddress>& start,
291291
const std::shared_ptr<SocketAddress>& end);
292292

293-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
293+
boolApply(const SocketAddress& address) override;
294294
std::string ToString() override;
295295

296296
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -305,7 +305,7 @@ class SocketAddressBlockList : public MemoryRetainer {
305305
SocketAddressMaskRule(const std::shared_ptr<SocketAddress>& address,
306306
int prefix);
307307

308-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
308+
boolApply(const SocketAddress& address) override;
309309
std::string ToString() override;
310310

311311
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -353,6 +353,10 @@ class SocketAddressBlockListWrap : public BaseObject {
353353
std::shared_ptr<SocketAddressBlockList> blocklist =
354354
std::make_shared<SocketAddressBlockList>());
355355

356+
inlineconst std::shared_ptr<SocketAddressBlockList>& blocklist() const {
357+
return blocklist_;
358+
}
359+
356360
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
357361
SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap)
358362
SET_SELF_SIZE(SocketAddressBlockListWrap)

‎src/quic/bindingdata.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SessionManager;
7070
V(ack_delay_exponent, "ackDelayExponent") \
7171
V(active_connection_id_limit, "activeConnectionIDLimit") \
7272
V(address_lru_size, "addressLRUSize") \
73+
V(allow, "allow") \
7374
V(application, "application") \
7475
V(authoritative, "authoritative") \
7576
V(bbr, "bbr") \
@@ -81,6 +82,7 @@ class SessionManager;
8182
V(crl, "crl") \
8283
V(cubic, "cubic") \
8384
V(datagram_drop_policy, "datagramDropPolicy") \
85+
V(deny, "deny") \
8486
V(disable_stateless_reset, "disableStatelessReset") \
8587
V(draining_period_multiplier, "drainingPeriodMultiplier") \
8688
V(enable_connect_protocol, "enableConnectProtocol") \
@@ -127,6 +129,8 @@ class SessionManager;
127129
V(immediate_close_burst, "immediateCloseBurst") \
128130
V(session_creation_rate, "sessionCreationRate") \
129131
V(session_creation_burst, "sessionCreationBurst") \
132+
V(block_list, "blockList") \
133+
V(block_list_policy, "blockListPolicy") \
130134
V(max_stream_window, "maxStreamWindow") \
131135
V(max_window, "maxWindow") \
132136
V(min_version, "minVersion") \

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit e493f04

Browse files
jasnelladuh95
authored andcommitted
quic: add block list support for endpoints
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode/Opus 4.6 PR-URL: #63483 Backport-PR-URL: #64675 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 6d6cd45 commit e493f04

14 files changed

Lines changed: 299 additions & 31 deletions

‎doc/api/quic.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,50 @@ object. Each rate limiter has a corresponding counter
189189
were dropped. A non-zero value indicates the rate limiter is actively
190190
protecting the endpoint.
191191

192+
#### Block lists
193+
194+
Endpoints can filter incoming packets by source address using a
195+
[`net.BlockList`][]. The block list is checked before any QUIC processing
196+
occurs, so blocked packets consume no resources beyond the check itself.
197+
198+
In **deny** mode (the default), packets from addresses in the list are dropped:
199+
200+
```mjs
201+
import { BlockList } from'node:net';
202+
import { listen } from'node:quic';
203+
204+
constblocked=newBlockList();
205+
blocked.addSubnet('192.168.1.0', 24); // Block an entire subnet
206+
blocked.addAddress('10.0.0.5'); // Block a specific address
207+
208+
constendpoint=awaitlisten(onSession, {
209+
endpoint: {
210+
blockList: blocked,
211+
blockListPolicy:'deny',
212+
},
213+
// ...
214+
});
215+
```
216+
217+
In **allow** mode, only packets from addresses in the list are accepted:
218+
219+
```mjs
220+
consttrusted=newBlockList();
221+
trusted.addSubnet('10.0.0.0', 8);
222+
223+
constendpoint=awaitlisten(onSession, {
224+
endpoint: {
225+
blockList: trusted,
226+
blockListPolicy:'allow',
227+
},
228+
// ...
229+
});
230+
```
231+
232+
The block list is evaluated live — rules added or removed after the endpoint
233+
is created take effect immediately. The `endpoint.stats.packetsBlocked`
234+
counter tracks how many packets have been dropped by the filter.
235+
192236
### Applications
193237

194238
Every `QuicSession` is associated with a single application protocol, negotiated
@@ -831,6 +875,11 @@ added: v23.8.0
831875
per-host rate limiter. Read only. A non-zero value indicates one or more
832876
remote addresses are creating sessions faster than the configured rate allows.
833877

878+
### `endpointStats.packetsBlocked`
879+
880+
* Type: {bigint} The total number of incoming packets dropped by the
881+
block list filter. Read only.
882+
834883
## Class: `QuicSession`
835884

836885
<!-- YAML
@@ -2429,6 +2478,34 @@ added: v23.8.0
24292478

24302479
If not specified the endpoint will bind to IPv4 `localhost` on a random port.
24312480

2481+
#### `endpointOptions.blockList`
2482+
2483+
* Type: {net.BlockList}
2484+
2485+
An optional [`net.BlockList`][] instance for filtering incoming packets by
2486+
source address. When configured, every received UDP packet is checked against
2487+
the block list before any QUIC processing occurs, minimizing resource
2488+
expenditure on blocked sources. The block list is evaluated live — rules
2489+
added to the `BlockList` object after the endpoint is created take effect
2490+
immediately.
2491+
2492+
See [`endpointOptions.blockListPolicy`][] for how matches are interpreted.
2493+
2494+
#### `endpointOptions.blockListPolicy`
2495+
2496+
* Type: {string} One of `'deny'` or `'allow'`.
2497+
***Default:**`'deny'`
2498+
2499+
Controls how the [`endpointOptions.blockList`][] is interpreted:
2500+
2501+
*`'deny'` — Packets from addresses matching the block list are dropped.
2502+
All other addresses are accepted. This is the typical blocklist mode.
2503+
*`'allow'` — Only packets from addresses matching the block list are
2504+
accepted. All other addresses are dropped. This is an allowlist mode
2505+
for restricting access to known clients.
2506+
2507+
If no block list is configured, this option has no effect.
2508+
24322509
#### `endpointOptions.addressLRUSize`
24332510

24342511
<!-- YAML
@@ -4275,6 +4352,8 @@ throughput issues caused by flow control.
42754352
[`endpoint.busy`]: #endpointbusy
42764353
[`endpoint.maxConnectionsPerHost`]: #endpointmaxconnectionsperhost
42774354
[`endpoint.maxConnectionsTotal`]: #endpointmaxconnectionstotal
4355+
[`endpointOptions.blockListPolicy`]: #endpointoptionsblocklistpolicy
4356+
[`endpointOptions.blockList`]: #endpointoptionsblocklist
42784357
[`endpointOptions.immediateCloseBurst`]: #endpointoptionsimmediatecloseburst
42794358
[`endpointOptions.immediateCloseRate`]: #endpointoptionsimmediatecloserate
42804359
[`endpointOptions.retryBurst`]: #endpointoptionsretryburst
@@ -4288,6 +4367,7 @@ throughput issues caused by flow control.
42884367
[`error.errorCode`]: #errorerrorcode
42894368
[`fs.promises.open(path, 'r')`]: fs.md#fspromisesopenpath-flags-mode
42904369
[`maxDatagramFrameSize`]: #transportparamsmaxdatagramframesize
4370+
[`net.BlockList`]: net.md#class-netblocklist
42914371
[`quic.connect()`]: #quicconnectaddress-options
42924372
[`quic.listen()`]: #quiclistenonsession-options
42934373
[`session.close()`]: #sessioncloseoptions

‎lib/internal/blocklist.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,5 @@ ObjectSetPrototypeOf(InternalBlockList.prototype, BlockList.prototype);
298298
module.exports={
299299
BlockList,
300300
InternalBlockList,
301+
kHandle,
301302
};

‎lib/internal/quic/quic.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ if (!process.features.quic || !getOptionValue('--experimental-quic')) {
3636
}
3737

3838
const{ inspect }=require('internal/util/inspect');
39+
const{
40+
BlockList,
41+
kHandle: kBlockListHandle,
42+
}=require('internal/blocklist');
3943

4044
letdebug=require('internal/util/debuglog').debuglog('quic',(fn)=>{
4145
debug=fn;
@@ -318,6 +322,8 @@ const endpointRegistry = new SafeSet();
318322
* @property {number} [immediateCloseBurst] Burst capacity for immediate close rate limiter
319323
* @property {number} [sessionCreationRate] Per-host rate limit for session creation (per second)
320324
* @property {number} [sessionCreationBurst] Per-host burst capacity for session creation rate limiter
325+
* @property {net.BlockList} [blockList] Block list for filtering incoming packets by source address
326+
* @property {'deny'|'allow'} [blockListPolicy='deny'] How to interpret the block list
321327
* @property {ArrayBufferView} [resetTokenSecret] The reset token secret
322328
* @property {bigint|number} [retryTokenExpiration] The retry token expiration
323329
* @property {number} [rxDiagnosticLoss] The receive diagnostic loss probability (range 0.0-1.0)
@@ -4048,6 +4054,8 @@ class QuicEndpoint {
40484054
immediateCloseBurst,
40494055
sessionCreationRate,
40504056
sessionCreationBurst,
4057+
blockList,
4058+
blockListPolicy ='deny',
40514059
rxDiagnosticLoss,
40524060
txDiagnosticLoss,
40534061
udpReceiveBufferSize,
@@ -4062,6 +4070,16 @@ class QuicEndpoint {
40624070
tokenSecret,
40634071
}=options;
40644072

4073+
if(blockList!==undefined){
4074+
if(!BlockList.isBlockList(blockList)){
4075+
thrownewERR_INVALID_ARG_TYPE('options.blockList',
4076+
'net.BlockList',blockList);
4077+
}
4078+
}
4079+
4080+
validateOneOf(blockListPolicy,'options.blockListPolicy',
4081+
['deny','allow']);
4082+
40654083
// All of the other options will be validated internally by the C++ code
40664084
if(address!==undefined&&!SocketAddress.isSocketAddress(address)){
40674085
if(typeofaddress==='string'){
@@ -4093,6 +4111,9 @@ class QuicEndpoint {
40934111
immediateCloseBurst,
40944112
sessionCreationRate,
40954113
sessionCreationBurst,
4114+
// Pass the C++ handle, not the JS BlockList wrapper.
4115+
blockList: blockList?.[kBlockListHandle],
4116+
blockListPolicy,
40964117
rxDiagnosticLoss,
40974118
txDiagnosticLoss,
40984119
udpReceiveBufferSize,

‎lib/internal/quic/stats.js‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const {
6868
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT,
6969
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED,
7070
IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED,
71+
IDX_STATS_ENDPOINT_PACKETS_BLOCKED,
7172

7273
IDX_STATS_SESSION_CREATED_AT,
7374
IDX_STATS_SESSION_DESTROYED_AT,
@@ -136,6 +137,7 @@ assert(IDX_STATS_ENDPOINT_STATELESS_RESET_RATE_LIMITED !== undefined);
136137
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT!==undefined);
137138
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED!==undefined);
138139
assert(IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED!==undefined);
140+
assert(IDX_STATS_ENDPOINT_PACKETS_BLOCKED!==undefined);
139141
assert(IDX_STATS_SESSION_CREATED_AT!==undefined);
140142
assert(IDX_STATS_SESSION_DESTROYED_AT!==undefined);
141143
assert(IDX_STATS_SESSION_CLOSING_AT!==undefined);
@@ -338,6 +340,12 @@ class QuicEndpointStats {
338340
returnthis.#handle[IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED];
339341
}
340342

343+
/** @type {bigint} */
344+
getpacketsBlocked(){
345+
assertIsQuicEndpointStats(this);
346+
returnthis.#handle[IDX_STATS_ENDPOINT_PACKETS_BLOCKED];
347+
}
348+
341349
toString(){
342350
returnJSONStringify(this.toJSON());
343351
}
@@ -363,6 +371,7 @@ class QuicEndpointStats {
363371
immediateCloseCount,
364372
immediateCloseRateLimited,
365373
sessionCreationRateLimited,
374+
packetsBlocked,
366375
}=this;
367376
return{
368377
__proto__: null,
@@ -387,6 +396,7 @@ class QuicEndpointStats {
387396
immediateCloseCount: `${immediateCloseCount}`,
388397
immediateCloseRateLimited: `${immediateCloseRateLimited}`,
389398
sessionCreationRateLimited: `${sessionCreationRateLimited}`,
399+
packetsBlocked: `${packetsBlocked}`,
390400
};
391401
}
392402

@@ -421,6 +431,7 @@ class QuicEndpointStats {
421431
immediateCloseCount,
422432
immediateCloseRateLimited,
423433
sessionCreationRateLimited,
434+
packetsBlocked,
424435
}=this;
425436

426437
return`QuicEndpointStats ${inspect({
@@ -443,6 +454,7 @@ class QuicEndpointStats {
443454
immediateCloseCount,
444455
immediateCloseRateLimited,
445456
sessionCreationRateLimited,
457+
packetsBlocked,
446458
},opts)}`;
447459
}
448460

‎src/node_sockaddr.cc‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,7 @@ void SocketAddressBlockList::AddSocketAddressMask(
434434
rules_.emplace_front(std::move(rule));
435435
}
436436

437-
boolSocketAddressBlockList::Apply(
438-
const std::shared_ptr<SocketAddress>& address) {
437+
boolSocketAddressBlockList::Apply(const SocketAddress& address) {
439438
Mutex::ScopedLock lock(mutex_);
440439
for (constauto& rule : rules_) {
441440
if (rule->Apply(address)) returntrue;
@@ -457,8 +456,8 @@ SocketAddressBlockList::SocketAddressMaskRule::SocketAddressMaskRule(
457456
: network(network_), prefix(prefix_) {}
458457

459458
boolSocketAddressBlockList::SocketAddressRule::Apply(
460-
conststd::shared_ptr<SocketAddress>& address) {
461-
returnthis->address->is_match(*address.get());
459+
const SocketAddress& address) {
460+
returnthis->address->is_match(address);
462461
}
463462

464463
std::string SocketAddressBlockList::SocketAddressRule::ToString() {
@@ -470,8 +469,8 @@ std::string SocketAddressBlockList::SocketAddressRule::ToString() {
470469
}
471470

472471
boolSocketAddressBlockList::SocketAddressRangeRule::Apply(
473-
conststd::shared_ptr<SocketAddress>& address) {
474-
return*address.get() >= *start.get() && *address.get() <= *end.get();
472+
const SocketAddress& address) {
473+
return address >= *start.get() && address <= *end.get();
475474
}
476475

477476
std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
@@ -485,8 +484,8 @@ std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
485484
}
486485

487486
boolSocketAddressBlockList::SocketAddressMaskRule::Apply(
488-
conststd::shared_ptr<SocketAddress>& address) {
489-
return address->is_in_network(*network.get(), prefix);
487+
const SocketAddress& address) {
488+
return address.is_in_network(*network.get(), prefix);
490489
}
491490

492491
std::string SocketAddressBlockList::SocketAddressMaskRule::ToString() {
@@ -656,7 +655,7 @@ void SocketAddressBlockListWrap::Check(
656655
SocketAddressBase* addr;
657656
ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);
658657

659-
args.GetReturnValue().Set(wrap->blocklist_->Apply(addr->address()));
658+
args.GetReturnValue().Set(wrap->blocklist_->Apply(*addr->address()));
660659
}
661660

662661
voidSocketAddressBlockListWrap::GetRules(

‎src/node_sockaddr.h‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,14 @@ class SocketAddressBlockList : public MemoryRetainer {
258258
voidAddSocketAddressMask(const std::shared_ptr<SocketAddress>& address,
259259
int prefix);
260260

261-
boolApply(conststd::shared_ptr<SocketAddress>& address);
261+
boolApply(const SocketAddress& address);
262262

263263
size_tsize() const { return rules_.size(); }
264264

265265
v8::MaybeLocal<v8::Array> ListRules(Environment* env);
266266

267267
structRule : publicMemoryRetainer {
268-
virtualboolApply(conststd::shared_ptr<SocketAddress>& address) = 0;
268+
virtualboolApply(const SocketAddress& address) = 0;
269269
inline v8::MaybeLocal<v8::Value> ToV8String(Environment* env);
270270
virtual std::string ToString() = 0;
271271
};
@@ -275,7 +275,7 @@ class SocketAddressBlockList : public MemoryRetainer {
275275

276276
explicitSocketAddressRule(const std::shared_ptr<SocketAddress>& address);
277277

278-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
278+
boolApply(const SocketAddress& address) override;
279279
std::string ToString() override;
280280

281281
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -290,7 +290,7 @@ class SocketAddressBlockList : public MemoryRetainer {
290290
SocketAddressRangeRule(const std::shared_ptr<SocketAddress>& start,
291291
const std::shared_ptr<SocketAddress>& end);
292292

293-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
293+
boolApply(const SocketAddress& address) override;
294294
std::string ToString() override;
295295

296296
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -305,7 +305,7 @@ class SocketAddressBlockList : public MemoryRetainer {
305305
SocketAddressMaskRule(const std::shared_ptr<SocketAddress>& address,
306306
int prefix);
307307

308-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
308+
boolApply(const SocketAddress& address) override;
309309
std::string ToString() override;
310310

311311
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -353,6 +353,10 @@ class SocketAddressBlockListWrap : public BaseObject {
353353
std::shared_ptr<SocketAddressBlockList> blocklist =
354354
std::make_shared<SocketAddressBlockList>());
355355

356+
inlineconst std::shared_ptr<SocketAddressBlockList>& blocklist() const {
357+
return blocklist_;
358+
}
359+
356360
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
357361
SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap)
358362
SET_SELF_SIZE(SocketAddressBlockListWrap)

‎src/quic/bindingdata.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SessionManager;
7070
V(ack_delay_exponent, "ackDelayExponent") \
7171
V(active_connection_id_limit, "activeConnectionIDLimit") \
7272
V(address_lru_size, "addressLRUSize") \
73+
V(allow, "allow") \
7374
V(application, "application") \
7475
V(authoritative, "authoritative") \
7576
V(bbr, "bbr") \
@@ -81,6 +82,7 @@ class SessionManager;
8182
V(crl, "crl") \
8283
V(cubic, "cubic") \
8384
V(datagram_drop_policy, "datagramDropPolicy") \
85+
V(deny, "deny") \
8486
V(disable_stateless_reset, "disableStatelessReset") \
8587
V(draining_period_multiplier, "drainingPeriodMultiplier") \
8688
V(enable_connect_protocol, "enableConnectProtocol") \
@@ -127,6 +129,8 @@ class SessionManager;
127129
V(immediate_close_burst, "immediateCloseBurst") \
128130
V(session_creation_rate, "sessionCreationRate") \
129131
V(session_creation_burst, "sessionCreationBurst") \
132+
V(block_list, "blockList") \
133+
V(block_list_policy, "blockListPolicy") \
130134
V(max_stream_window, "maxStreamWindow") \
131135
V(max_window, "maxWindow") \
132136
V(min_version, "minVersion") \

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit e493f04

Browse files
jasnelladuh95
authored andcommitted
quic: add block list support for endpoints
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode/Opus 4.6 PR-URL: #63483 Backport-PR-URL: #64675 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 6d6cd45 commit e493f04

14 files changed

Lines changed: 299 additions & 31 deletions

‎doc/api/quic.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,50 @@ object. Each rate limiter has a corresponding counter
189189
were dropped. A non-zero value indicates the rate limiter is actively
190190
protecting the endpoint.
191191

192+
#### Block lists
193+
194+
Endpoints can filter incoming packets by source address using a
195+
[`net.BlockList`][]. The block list is checked before any QUIC processing
196+
occurs, so blocked packets consume no resources beyond the check itself.
197+
198+
In **deny** mode (the default), packets from addresses in the list are dropped:
199+
200+
```mjs
201+
import { BlockList } from'node:net';
202+
import { listen } from'node:quic';
203+
204+
constblocked=newBlockList();
205+
blocked.addSubnet('192.168.1.0', 24); // Block an entire subnet
206+
blocked.addAddress('10.0.0.5'); // Block a specific address
207+
208+
constendpoint=awaitlisten(onSession, {
209+
endpoint: {
210+
blockList: blocked,
211+
blockListPolicy:'deny',
212+
},
213+
// ...
214+
});
215+
```
216+
217+
In **allow** mode, only packets from addresses in the list are accepted:
218+
219+
```mjs
220+
consttrusted=newBlockList();
221+
trusted.addSubnet('10.0.0.0', 8);
222+
223+
constendpoint=awaitlisten(onSession, {
224+
endpoint: {
225+
blockList: trusted,
226+
blockListPolicy:'allow',
227+
},
228+
// ...
229+
});
230+
```
231+
232+
The block list is evaluated live — rules added or removed after the endpoint
233+
is created take effect immediately. The `endpoint.stats.packetsBlocked`
234+
counter tracks how many packets have been dropped by the filter.
235+
192236
### Applications
193237

194238
Every `QuicSession` is associated with a single application protocol, negotiated
@@ -831,6 +875,11 @@ added: v23.8.0
831875
per-host rate limiter. Read only. A non-zero value indicates one or more
832876
remote addresses are creating sessions faster than the configured rate allows.
833877

878+
### `endpointStats.packetsBlocked`
879+
880+
* Type: {bigint} The total number of incoming packets dropped by the
881+
block list filter. Read only.
882+
834883
## Class: `QuicSession`
835884

836885
<!-- YAML
@@ -2429,6 +2478,34 @@ added: v23.8.0
24292478

24302479
If not specified the endpoint will bind to IPv4 `localhost` on a random port.
24312480

2481+
#### `endpointOptions.blockList`
2482+
2483+
* Type: {net.BlockList}
2484+
2485+
An optional [`net.BlockList`][] instance for filtering incoming packets by
2486+
source address. When configured, every received UDP packet is checked against
2487+
the block list before any QUIC processing occurs, minimizing resource
2488+
expenditure on blocked sources. The block list is evaluated live — rules
2489+
added to the `BlockList` object after the endpoint is created take effect
2490+
immediately.
2491+
2492+
See [`endpointOptions.blockListPolicy`][] for how matches are interpreted.
2493+
2494+
#### `endpointOptions.blockListPolicy`
2495+
2496+
* Type: {string} One of `'deny'` or `'allow'`.
2497+
***Default:**`'deny'`
2498+
2499+
Controls how the [`endpointOptions.blockList`][] is interpreted:
2500+
2501+
*`'deny'` — Packets from addresses matching the block list are dropped.
2502+
All other addresses are accepted. This is the typical blocklist mode.
2503+
*`'allow'` — Only packets from addresses matching the block list are
2504+
accepted. All other addresses are dropped. This is an allowlist mode
2505+
for restricting access to known clients.
2506+
2507+
If no block list is configured, this option has no effect.
2508+
24322509
#### `endpointOptions.addressLRUSize`
24332510

24342511
<!-- YAML
@@ -4275,6 +4352,8 @@ throughput issues caused by flow control.
42754352
[`endpoint.busy`]: #endpointbusy
42764353
[`endpoint.maxConnectionsPerHost`]: #endpointmaxconnectionsperhost
42774354
[`endpoint.maxConnectionsTotal`]: #endpointmaxconnectionstotal
4355+
[`endpointOptions.blockListPolicy`]: #endpointoptionsblocklistpolicy
4356+
[`endpointOptions.blockList`]: #endpointoptionsblocklist
42784357
[`endpointOptions.immediateCloseBurst`]: #endpointoptionsimmediatecloseburst
42794358
[`endpointOptions.immediateCloseRate`]: #endpointoptionsimmediatecloserate
42804359
[`endpointOptions.retryBurst`]: #endpointoptionsretryburst
@@ -4288,6 +4367,7 @@ throughput issues caused by flow control.
42884367
[`error.errorCode`]: #errorerrorcode
42894368
[`fs.promises.open(path, 'r')`]: fs.md#fspromisesopenpath-flags-mode
42904369
[`maxDatagramFrameSize`]: #transportparamsmaxdatagramframesize
4370+
[`net.BlockList`]: net.md#class-netblocklist
42914371
[`quic.connect()`]: #quicconnectaddress-options
42924372
[`quic.listen()`]: #quiclistenonsession-options
42934373
[`session.close()`]: #sessioncloseoptions

‎lib/internal/blocklist.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,5 @@ ObjectSetPrototypeOf(InternalBlockList.prototype, BlockList.prototype);
298298
module.exports={
299299
BlockList,
300300
InternalBlockList,
301+
kHandle,
301302
};

‎lib/internal/quic/quic.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ if (!process.features.quic || !getOptionValue('--experimental-quic')) {
3636
}
3737

3838
const{ inspect }=require('internal/util/inspect');
39+
const{
40+
BlockList,
41+
kHandle: kBlockListHandle,
42+
}=require('internal/blocklist');
3943

4044
letdebug=require('internal/util/debuglog').debuglog('quic',(fn)=>{
4145
debug=fn;
@@ -318,6 +322,8 @@ const endpointRegistry = new SafeSet();
318322
* @property {number} [immediateCloseBurst] Burst capacity for immediate close rate limiter
319323
* @property {number} [sessionCreationRate] Per-host rate limit for session creation (per second)
320324
* @property {number} [sessionCreationBurst] Per-host burst capacity for session creation rate limiter
325+
* @property {net.BlockList} [blockList] Block list for filtering incoming packets by source address
326+
* @property {'deny'|'allow'} [blockListPolicy='deny'] How to interpret the block list
321327
* @property {ArrayBufferView} [resetTokenSecret] The reset token secret
322328
* @property {bigint|number} [retryTokenExpiration] The retry token expiration
323329
* @property {number} [rxDiagnosticLoss] The receive diagnostic loss probability (range 0.0-1.0)
@@ -4048,6 +4054,8 @@ class QuicEndpoint {
40484054
immediateCloseBurst,
40494055
sessionCreationRate,
40504056
sessionCreationBurst,
4057+
blockList,
4058+
blockListPolicy ='deny',
40514059
rxDiagnosticLoss,
40524060
txDiagnosticLoss,
40534061
udpReceiveBufferSize,
@@ -4062,6 +4070,16 @@ class QuicEndpoint {
40624070
tokenSecret,
40634071
}=options;
40644072

4073+
if(blockList!==undefined){
4074+
if(!BlockList.isBlockList(blockList)){
4075+
thrownewERR_INVALID_ARG_TYPE('options.blockList',
4076+
'net.BlockList',blockList);
4077+
}
4078+
}
4079+
4080+
validateOneOf(blockListPolicy,'options.blockListPolicy',
4081+
['deny','allow']);
4082+
40654083
// All of the other options will be validated internally by the C++ code
40664084
if(address!==undefined&&!SocketAddress.isSocketAddress(address)){
40674085
if(typeofaddress==='string'){
@@ -4093,6 +4111,9 @@ class QuicEndpoint {
40934111
immediateCloseBurst,
40944112
sessionCreationRate,
40954113
sessionCreationBurst,
4114+
// Pass the C++ handle, not the JS BlockList wrapper.
4115+
blockList: blockList?.[kBlockListHandle],
4116+
blockListPolicy,
40964117
rxDiagnosticLoss,
40974118
txDiagnosticLoss,
40984119
udpReceiveBufferSize,

‎lib/internal/quic/stats.js‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const {
6868
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT,
6969
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED,
7070
IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED,
71+
IDX_STATS_ENDPOINT_PACKETS_BLOCKED,
7172

7273
IDX_STATS_SESSION_CREATED_AT,
7374
IDX_STATS_SESSION_DESTROYED_AT,
@@ -136,6 +137,7 @@ assert(IDX_STATS_ENDPOINT_STATELESS_RESET_RATE_LIMITED !== undefined);
136137
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT!==undefined);
137138
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED!==undefined);
138139
assert(IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED!==undefined);
140+
assert(IDX_STATS_ENDPOINT_PACKETS_BLOCKED!==undefined);
139141
assert(IDX_STATS_SESSION_CREATED_AT!==undefined);
140142
assert(IDX_STATS_SESSION_DESTROYED_AT!==undefined);
141143
assert(IDX_STATS_SESSION_CLOSING_AT!==undefined);
@@ -338,6 +340,12 @@ class QuicEndpointStats {
338340
returnthis.#handle[IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED];
339341
}
340342

343+
/** @type {bigint} */
344+
getpacketsBlocked(){
345+
assertIsQuicEndpointStats(this);
346+
returnthis.#handle[IDX_STATS_ENDPOINT_PACKETS_BLOCKED];
347+
}
348+
341349
toString(){
342350
returnJSONStringify(this.toJSON());
343351
}
@@ -363,6 +371,7 @@ class QuicEndpointStats {
363371
immediateCloseCount,
364372
immediateCloseRateLimited,
365373
sessionCreationRateLimited,
374+
packetsBlocked,
366375
}=this;
367376
return{
368377
__proto__: null,
@@ -387,6 +396,7 @@ class QuicEndpointStats {
387396
immediateCloseCount: `${immediateCloseCount}`,
388397
immediateCloseRateLimited: `${immediateCloseRateLimited}`,
389398
sessionCreationRateLimited: `${sessionCreationRateLimited}`,
399+
packetsBlocked: `${packetsBlocked}`,
390400
};
391401
}
392402

@@ -421,6 +431,7 @@ class QuicEndpointStats {
421431
immediateCloseCount,
422432
immediateCloseRateLimited,
423433
sessionCreationRateLimited,
434+
packetsBlocked,
424435
}=this;
425436

426437
return`QuicEndpointStats ${inspect({
@@ -443,6 +454,7 @@ class QuicEndpointStats {
443454
immediateCloseCount,
444455
immediateCloseRateLimited,
445456
sessionCreationRateLimited,
457+
packetsBlocked,
446458
},opts)}`;
447459
}
448460

‎src/node_sockaddr.cc‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,7 @@ void SocketAddressBlockList::AddSocketAddressMask(
434434
rules_.emplace_front(std::move(rule));
435435
}
436436

437-
boolSocketAddressBlockList::Apply(
438-
const std::shared_ptr<SocketAddress>& address) {
437+
boolSocketAddressBlockList::Apply(const SocketAddress& address) {
439438
Mutex::ScopedLock lock(mutex_);
440439
for (constauto& rule : rules_) {
441440
if (rule->Apply(address)) returntrue;
@@ -457,8 +456,8 @@ SocketAddressBlockList::SocketAddressMaskRule::SocketAddressMaskRule(
457456
: network(network_), prefix(prefix_) {}
458457

459458
boolSocketAddressBlockList::SocketAddressRule::Apply(
460-
conststd::shared_ptr<SocketAddress>& address) {
461-
returnthis->address->is_match(*address.get());
459+
const SocketAddress& address) {
460+
returnthis->address->is_match(address);
462461
}
463462

464463
std::string SocketAddressBlockList::SocketAddressRule::ToString() {
@@ -470,8 +469,8 @@ std::string SocketAddressBlockList::SocketAddressRule::ToString() {
470469
}
471470

472471
boolSocketAddressBlockList::SocketAddressRangeRule::Apply(
473-
conststd::shared_ptr<SocketAddress>& address) {
474-
return*address.get() >= *start.get() && *address.get() <= *end.get();
472+
const SocketAddress& address) {
473+
return address >= *start.get() && address <= *end.get();
475474
}
476475

477476
std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
@@ -485,8 +484,8 @@ std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
485484
}
486485

487486
boolSocketAddressBlockList::SocketAddressMaskRule::Apply(
488-
conststd::shared_ptr<SocketAddress>& address) {
489-
return address->is_in_network(*network.get(), prefix);
487+
const SocketAddress& address) {
488+
return address.is_in_network(*network.get(), prefix);
490489
}
491490

492491
std::string SocketAddressBlockList::SocketAddressMaskRule::ToString() {
@@ -656,7 +655,7 @@ void SocketAddressBlockListWrap::Check(
656655
SocketAddressBase* addr;
657656
ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);
658657

659-
args.GetReturnValue().Set(wrap->blocklist_->Apply(addr->address()));
658+
args.GetReturnValue().Set(wrap->blocklist_->Apply(*addr->address()));
660659
}
661660

662661
voidSocketAddressBlockListWrap::GetRules(

‎src/node_sockaddr.h‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,14 @@ class SocketAddressBlockList : public MemoryRetainer {
258258
voidAddSocketAddressMask(const std::shared_ptr<SocketAddress>& address,
259259
int prefix);
260260

261-
boolApply(conststd::shared_ptr<SocketAddress>& address);
261+
boolApply(const SocketAddress& address);
262262

263263
size_tsize() const { return rules_.size(); }
264264

265265
v8::MaybeLocal<v8::Array> ListRules(Environment* env);
266266

267267
structRule : publicMemoryRetainer {
268-
virtualboolApply(conststd::shared_ptr<SocketAddress>& address) = 0;
268+
virtualboolApply(const SocketAddress& address) = 0;
269269
inline v8::MaybeLocal<v8::Value> ToV8String(Environment* env);
270270
virtual std::string ToString() = 0;
271271
};
@@ -275,7 +275,7 @@ class SocketAddressBlockList : public MemoryRetainer {
275275

276276
explicitSocketAddressRule(const std::shared_ptr<SocketAddress>& address);
277277

278-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
278+
boolApply(const SocketAddress& address) override;
279279
std::string ToString() override;
280280

281281
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -290,7 +290,7 @@ class SocketAddressBlockList : public MemoryRetainer {
290290
SocketAddressRangeRule(const std::shared_ptr<SocketAddress>& start,
291291
const std::shared_ptr<SocketAddress>& end);
292292

293-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
293+
boolApply(const SocketAddress& address) override;
294294
std::string ToString() override;
295295

296296
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -305,7 +305,7 @@ class SocketAddressBlockList : public MemoryRetainer {
305305
SocketAddressMaskRule(const std::shared_ptr<SocketAddress>& address,
306306
int prefix);
307307

308-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
308+
boolApply(const SocketAddress& address) override;
309309
std::string ToString() override;
310310

311311
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -353,6 +353,10 @@ class SocketAddressBlockListWrap : public BaseObject {
353353
std::shared_ptr<SocketAddressBlockList> blocklist =
354354
std::make_shared<SocketAddressBlockList>());
355355

356+
inlineconst std::shared_ptr<SocketAddressBlockList>& blocklist() const {
357+
return blocklist_;
358+
}
359+
356360
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
357361
SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap)
358362
SET_SELF_SIZE(SocketAddressBlockListWrap)

‎src/quic/bindingdata.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SessionManager;
7070
V(ack_delay_exponent, "ackDelayExponent") \
7171
V(active_connection_id_limit, "activeConnectionIDLimit") \
7272
V(address_lru_size, "addressLRUSize") \
73+
V(allow, "allow") \
7374
V(application, "application") \
7475
V(authoritative, "authoritative") \
7576
V(bbr, "bbr") \
@@ -81,6 +82,7 @@ class SessionManager;
8182
V(crl, "crl") \
8283
V(cubic, "cubic") \
8384
V(datagram_drop_policy, "datagramDropPolicy") \
85+
V(deny, "deny") \
8486
V(disable_stateless_reset, "disableStatelessReset") \
8587
V(draining_period_multiplier, "drainingPeriodMultiplier") \
8688
V(enable_connect_protocol, "enableConnectProtocol") \
@@ -127,6 +129,8 @@ class SessionManager;
127129
V(immediate_close_burst, "immediateCloseBurst") \
128130
V(session_creation_rate, "sessionCreationRate") \
129131
V(session_creation_burst, "sessionCreationBurst") \
132+
V(block_list, "blockList") \
133+
V(block_list_policy, "blockListPolicy") \
130134
V(max_stream_window, "maxStreamWindow") \
131135
V(max_window, "maxWindow") \
132136
V(min_version, "minVersion") \

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit e493f04

Browse files
jasnelladuh95
authored andcommitted
quic: add block list support for endpoints
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode/Opus 4.6 PR-URL: #63483 Backport-PR-URL: #64675 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 6d6cd45 commit e493f04

14 files changed

Lines changed: 299 additions & 31 deletions

‎doc/api/quic.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,50 @@ object. Each rate limiter has a corresponding counter
189189
were dropped. A non-zero value indicates the rate limiter is actively
190190
protecting the endpoint.
191191

192+
#### Block lists
193+
194+
Endpoints can filter incoming packets by source address using a
195+
[`net.BlockList`][]. The block list is checked before any QUIC processing
196+
occurs, so blocked packets consume no resources beyond the check itself.
197+
198+
In **deny** mode (the default), packets from addresses in the list are dropped:
199+
200+
```mjs
201+
import { BlockList } from'node:net';
202+
import { listen } from'node:quic';
203+
204+
constblocked=newBlockList();
205+
blocked.addSubnet('192.168.1.0', 24); // Block an entire subnet
206+
blocked.addAddress('10.0.0.5'); // Block a specific address
207+
208+
constendpoint=awaitlisten(onSession, {
209+
endpoint: {
210+
blockList: blocked,
211+
blockListPolicy:'deny',
212+
},
213+
// ...
214+
});
215+
```
216+
217+
In **allow** mode, only packets from addresses in the list are accepted:
218+
219+
```mjs
220+
consttrusted=newBlockList();
221+
trusted.addSubnet('10.0.0.0', 8);
222+
223+
constendpoint=awaitlisten(onSession, {
224+
endpoint: {
225+
blockList: trusted,
226+
blockListPolicy:'allow',
227+
},
228+
// ...
229+
});
230+
```
231+
232+
The block list is evaluated live — rules added or removed after the endpoint
233+
is created take effect immediately. The `endpoint.stats.packetsBlocked`
234+
counter tracks how many packets have been dropped by the filter.
235+
192236
### Applications
193237

194238
Every `QuicSession` is associated with a single application protocol, negotiated
@@ -831,6 +875,11 @@ added: v23.8.0
831875
per-host rate limiter. Read only. A non-zero value indicates one or more
832876
remote addresses are creating sessions faster than the configured rate allows.
833877

878+
### `endpointStats.packetsBlocked`
879+
880+
* Type: {bigint} The total number of incoming packets dropped by the
881+
block list filter. Read only.
882+
834883
## Class: `QuicSession`
835884

836885
<!-- YAML
@@ -2429,6 +2478,34 @@ added: v23.8.0
24292478

24302479
If not specified the endpoint will bind to IPv4 `localhost` on a random port.
24312480

2481+
#### `endpointOptions.blockList`
2482+
2483+
* Type: {net.BlockList}
2484+
2485+
An optional [`net.BlockList`][] instance for filtering incoming packets by
2486+
source address. When configured, every received UDP packet is checked against
2487+
the block list before any QUIC processing occurs, minimizing resource
2488+
expenditure on blocked sources. The block list is evaluated live — rules
2489+
added to the `BlockList` object after the endpoint is created take effect
2490+
immediately.
2491+
2492+
See [`endpointOptions.blockListPolicy`][] for how matches are interpreted.
2493+
2494+
#### `endpointOptions.blockListPolicy`
2495+
2496+
* Type: {string} One of `'deny'` or `'allow'`.
2497+
***Default:**`'deny'`
2498+
2499+
Controls how the [`endpointOptions.blockList`][] is interpreted:
2500+
2501+
*`'deny'` — Packets from addresses matching the block list are dropped.
2502+
All other addresses are accepted. This is the typical blocklist mode.
2503+
*`'allow'` — Only packets from addresses matching the block list are
2504+
accepted. All other addresses are dropped. This is an allowlist mode
2505+
for restricting access to known clients.
2506+
2507+
If no block list is configured, this option has no effect.
2508+
24322509
#### `endpointOptions.addressLRUSize`
24332510

24342511
<!-- YAML
@@ -4275,6 +4352,8 @@ throughput issues caused by flow control.
42754352
[`endpoint.busy`]: #endpointbusy
42764353
[`endpoint.maxConnectionsPerHost`]: #endpointmaxconnectionsperhost
42774354
[`endpoint.maxConnectionsTotal`]: #endpointmaxconnectionstotal
4355+
[`endpointOptions.blockListPolicy`]: #endpointoptionsblocklistpolicy
4356+
[`endpointOptions.blockList`]: #endpointoptionsblocklist
42784357
[`endpointOptions.immediateCloseBurst`]: #endpointoptionsimmediatecloseburst
42794358
[`endpointOptions.immediateCloseRate`]: #endpointoptionsimmediatecloserate
42804359
[`endpointOptions.retryBurst`]: #endpointoptionsretryburst
@@ -4288,6 +4367,7 @@ throughput issues caused by flow control.
42884367
[`error.errorCode`]: #errorerrorcode
42894368
[`fs.promises.open(path, 'r')`]: fs.md#fspromisesopenpath-flags-mode
42904369
[`maxDatagramFrameSize`]: #transportparamsmaxdatagramframesize
4370+
[`net.BlockList`]: net.md#class-netblocklist
42914371
[`quic.connect()`]: #quicconnectaddress-options
42924372
[`quic.listen()`]: #quiclistenonsession-options
42934373
[`session.close()`]: #sessioncloseoptions

‎lib/internal/blocklist.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,5 @@ ObjectSetPrototypeOf(InternalBlockList.prototype, BlockList.prototype);
298298
module.exports={
299299
BlockList,
300300
InternalBlockList,
301+
kHandle,
301302
};

‎lib/internal/quic/quic.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ if (!process.features.quic || !getOptionValue('--experimental-quic')) {
3636
}
3737

3838
const{ inspect }=require('internal/util/inspect');
39+
const{
40+
BlockList,
41+
kHandle: kBlockListHandle,
42+
}=require('internal/blocklist');
3943

4044
letdebug=require('internal/util/debuglog').debuglog('quic',(fn)=>{
4145
debug=fn;
@@ -318,6 +322,8 @@ const endpointRegistry = new SafeSet();
318322
* @property {number} [immediateCloseBurst] Burst capacity for immediate close rate limiter
319323
* @property {number} [sessionCreationRate] Per-host rate limit for session creation (per second)
320324
* @property {number} [sessionCreationBurst] Per-host burst capacity for session creation rate limiter
325+
* @property {net.BlockList} [blockList] Block list for filtering incoming packets by source address
326+
* @property {'deny'|'allow'} [blockListPolicy='deny'] How to interpret the block list
321327
* @property {ArrayBufferView} [resetTokenSecret] The reset token secret
322328
* @property {bigint|number} [retryTokenExpiration] The retry token expiration
323329
* @property {number} [rxDiagnosticLoss] The receive diagnostic loss probability (range 0.0-1.0)
@@ -4048,6 +4054,8 @@ class QuicEndpoint {
40484054
immediateCloseBurst,
40494055
sessionCreationRate,
40504056
sessionCreationBurst,
4057+
blockList,
4058+
blockListPolicy ='deny',
40514059
rxDiagnosticLoss,
40524060
txDiagnosticLoss,
40534061
udpReceiveBufferSize,
@@ -4062,6 +4070,16 @@ class QuicEndpoint {
40624070
tokenSecret,
40634071
}=options;
40644072

4073+
if(blockList!==undefined){
4074+
if(!BlockList.isBlockList(blockList)){
4075+
thrownewERR_INVALID_ARG_TYPE('options.blockList',
4076+
'net.BlockList',blockList);
4077+
}
4078+
}
4079+
4080+
validateOneOf(blockListPolicy,'options.blockListPolicy',
4081+
['deny','allow']);
4082+
40654083
// All of the other options will be validated internally by the C++ code
40664084
if(address!==undefined&&!SocketAddress.isSocketAddress(address)){
40674085
if(typeofaddress==='string'){
@@ -4093,6 +4111,9 @@ class QuicEndpoint {
40934111
immediateCloseBurst,
40944112
sessionCreationRate,
40954113
sessionCreationBurst,
4114+
// Pass the C++ handle, not the JS BlockList wrapper.
4115+
blockList: blockList?.[kBlockListHandle],
4116+
blockListPolicy,
40964117
rxDiagnosticLoss,
40974118
txDiagnosticLoss,
40984119
udpReceiveBufferSize,

‎lib/internal/quic/stats.js‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const {
6868
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT,
6969
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED,
7070
IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED,
71+
IDX_STATS_ENDPOINT_PACKETS_BLOCKED,
7172

7273
IDX_STATS_SESSION_CREATED_AT,
7374
IDX_STATS_SESSION_DESTROYED_AT,
@@ -136,6 +137,7 @@ assert(IDX_STATS_ENDPOINT_STATELESS_RESET_RATE_LIMITED !== undefined);
136137
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT!==undefined);
137138
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED!==undefined);
138139
assert(IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED!==undefined);
140+
assert(IDX_STATS_ENDPOINT_PACKETS_BLOCKED!==undefined);
139141
assert(IDX_STATS_SESSION_CREATED_AT!==undefined);
140142
assert(IDX_STATS_SESSION_DESTROYED_AT!==undefined);
141143
assert(IDX_STATS_SESSION_CLOSING_AT!==undefined);
@@ -338,6 +340,12 @@ class QuicEndpointStats {
338340
returnthis.#handle[IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED];
339341
}
340342

343+
/** @type {bigint} */
344+
getpacketsBlocked(){
345+
assertIsQuicEndpointStats(this);
346+
returnthis.#handle[IDX_STATS_ENDPOINT_PACKETS_BLOCKED];
347+
}
348+
341349
toString(){
342350
returnJSONStringify(this.toJSON());
343351
}
@@ -363,6 +371,7 @@ class QuicEndpointStats {
363371
immediateCloseCount,
364372
immediateCloseRateLimited,
365373
sessionCreationRateLimited,
374+
packetsBlocked,
366375
}=this;
367376
return{
368377
__proto__: null,
@@ -387,6 +396,7 @@ class QuicEndpointStats {
387396
immediateCloseCount: `${immediateCloseCount}`,
388397
immediateCloseRateLimited: `${immediateCloseRateLimited}`,
389398
sessionCreationRateLimited: `${sessionCreationRateLimited}`,
399+
packetsBlocked: `${packetsBlocked}`,
390400
};
391401
}
392402

@@ -421,6 +431,7 @@ class QuicEndpointStats {
421431
immediateCloseCount,
422432
immediateCloseRateLimited,
423433
sessionCreationRateLimited,
434+
packetsBlocked,
424435
}=this;
425436

426437
return`QuicEndpointStats ${inspect({
@@ -443,6 +454,7 @@ class QuicEndpointStats {
443454
immediateCloseCount,
444455
immediateCloseRateLimited,
445456
sessionCreationRateLimited,
457+
packetsBlocked,
446458
},opts)}`;
447459
}
448460

‎src/node_sockaddr.cc‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,7 @@ void SocketAddressBlockList::AddSocketAddressMask(
434434
rules_.emplace_front(std::move(rule));
435435
}
436436

437-
boolSocketAddressBlockList::Apply(
438-
const std::shared_ptr<SocketAddress>& address) {
437+
boolSocketAddressBlockList::Apply(const SocketAddress& address) {
439438
Mutex::ScopedLock lock(mutex_);
440439
for (constauto& rule : rules_) {
441440
if (rule->Apply(address)) returntrue;
@@ -457,8 +456,8 @@ SocketAddressBlockList::SocketAddressMaskRule::SocketAddressMaskRule(
457456
: network(network_), prefix(prefix_) {}
458457

459458
boolSocketAddressBlockList::SocketAddressRule::Apply(
460-
conststd::shared_ptr<SocketAddress>& address) {
461-
returnthis->address->is_match(*address.get());
459+
const SocketAddress& address) {
460+
returnthis->address->is_match(address);
462461
}
463462

464463
std::string SocketAddressBlockList::SocketAddressRule::ToString() {
@@ -470,8 +469,8 @@ std::string SocketAddressBlockList::SocketAddressRule::ToString() {
470469
}
471470

472471
boolSocketAddressBlockList::SocketAddressRangeRule::Apply(
473-
conststd::shared_ptr<SocketAddress>& address) {
474-
return*address.get() >= *start.get() && *address.get() <= *end.get();
472+
const SocketAddress& address) {
473+
return address >= *start.get() && address <= *end.get();
475474
}
476475

477476
std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
@@ -485,8 +484,8 @@ std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
485484
}
486485

487486
boolSocketAddressBlockList::SocketAddressMaskRule::Apply(
488-
conststd::shared_ptr<SocketAddress>& address) {
489-
return address->is_in_network(*network.get(), prefix);
487+
const SocketAddress& address) {
488+
return address.is_in_network(*network.get(), prefix);
490489
}
491490

492491
std::string SocketAddressBlockList::SocketAddressMaskRule::ToString() {
@@ -656,7 +655,7 @@ void SocketAddressBlockListWrap::Check(
656655
SocketAddressBase* addr;
657656
ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);
658657

659-
args.GetReturnValue().Set(wrap->blocklist_->Apply(addr->address()));
658+
args.GetReturnValue().Set(wrap->blocklist_->Apply(*addr->address()));
660659
}
661660

662661
voidSocketAddressBlockListWrap::GetRules(

‎src/node_sockaddr.h‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,14 @@ class SocketAddressBlockList : public MemoryRetainer {
258258
voidAddSocketAddressMask(const std::shared_ptr<SocketAddress>& address,
259259
int prefix);
260260

261-
boolApply(conststd::shared_ptr<SocketAddress>& address);
261+
boolApply(const SocketAddress& address);
262262

263263
size_tsize() const { return rules_.size(); }
264264

265265
v8::MaybeLocal<v8::Array> ListRules(Environment* env);
266266

267267
structRule : publicMemoryRetainer {
268-
virtualboolApply(conststd::shared_ptr<SocketAddress>& address) = 0;
268+
virtualboolApply(const SocketAddress& address) = 0;
269269
inline v8::MaybeLocal<v8::Value> ToV8String(Environment* env);
270270
virtual std::string ToString() = 0;
271271
};
@@ -275,7 +275,7 @@ class SocketAddressBlockList : public MemoryRetainer {
275275

276276
explicitSocketAddressRule(const std::shared_ptr<SocketAddress>& address);
277277

278-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
278+
boolApply(const SocketAddress& address) override;
279279
std::string ToString() override;
280280

281281
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -290,7 +290,7 @@ class SocketAddressBlockList : public MemoryRetainer {
290290
SocketAddressRangeRule(const std::shared_ptr<SocketAddress>& start,
291291
const std::shared_ptr<SocketAddress>& end);
292292

293-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
293+
boolApply(const SocketAddress& address) override;
294294
std::string ToString() override;
295295

296296
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -305,7 +305,7 @@ class SocketAddressBlockList : public MemoryRetainer {
305305
SocketAddressMaskRule(const std::shared_ptr<SocketAddress>& address,
306306
int prefix);
307307

308-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
308+
boolApply(const SocketAddress& address) override;
309309
std::string ToString() override;
310310

311311
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -353,6 +353,10 @@ class SocketAddressBlockListWrap : public BaseObject {
353353
std::shared_ptr<SocketAddressBlockList> blocklist =
354354
std::make_shared<SocketAddressBlockList>());
355355

356+
inlineconst std::shared_ptr<SocketAddressBlockList>& blocklist() const {
357+
return blocklist_;
358+
}
359+
356360
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
357361
SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap)
358362
SET_SELF_SIZE(SocketAddressBlockListWrap)

‎src/quic/bindingdata.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SessionManager;
7070
V(ack_delay_exponent, "ackDelayExponent") \
7171
V(active_connection_id_limit, "activeConnectionIDLimit") \
7272
V(address_lru_size, "addressLRUSize") \
73+
V(allow, "allow") \
7374
V(application, "application") \
7475
V(authoritative, "authoritative") \
7576
V(bbr, "bbr") \
@@ -81,6 +82,7 @@ class SessionManager;
8182
V(crl, "crl") \
8283
V(cubic, "cubic") \
8384
V(datagram_drop_policy, "datagramDropPolicy") \
85+
V(deny, "deny") \
8486
V(disable_stateless_reset, "disableStatelessReset") \
8587
V(draining_period_multiplier, "drainingPeriodMultiplier") \
8688
V(enable_connect_protocol, "enableConnectProtocol") \
@@ -127,6 +129,8 @@ class SessionManager;
127129
V(immediate_close_burst, "immediateCloseBurst") \
128130
V(session_creation_rate, "sessionCreationRate") \
129131
V(session_creation_burst, "sessionCreationBurst") \
132+
V(block_list, "blockList") \
133+
V(block_list_policy, "blockListPolicy") \
130134
V(max_stream_window, "maxStreamWindow") \
131135
V(max_window, "maxWindow") \
132136
V(min_version, "minVersion") \

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit e493f04

Browse files
jasnelladuh95
authored andcommitted
quic: add block list support for endpoints
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode/Opus 4.6 PR-URL: #63483 Backport-PR-URL: #64675 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 6d6cd45 commit e493f04

14 files changed

Lines changed: 299 additions & 31 deletions

‎doc/api/quic.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,50 @@ object. Each rate limiter has a corresponding counter
189189
were dropped. A non-zero value indicates the rate limiter is actively
190190
protecting the endpoint.
191191

192+
#### Block lists
193+
194+
Endpoints can filter incoming packets by source address using a
195+
[`net.BlockList`][]. The block list is checked before any QUIC processing
196+
occurs, so blocked packets consume no resources beyond the check itself.
197+
198+
In **deny** mode (the default), packets from addresses in the list are dropped:
199+
200+
```mjs
201+
import { BlockList } from'node:net';
202+
import { listen } from'node:quic';
203+
204+
constblocked=newBlockList();
205+
blocked.addSubnet('192.168.1.0', 24); // Block an entire subnet
206+
blocked.addAddress('10.0.0.5'); // Block a specific address
207+
208+
constendpoint=awaitlisten(onSession, {
209+
endpoint: {
210+
blockList: blocked,
211+
blockListPolicy:'deny',
212+
},
213+
// ...
214+
});
215+
```
216+
217+
In **allow** mode, only packets from addresses in the list are accepted:
218+
219+
```mjs
220+
consttrusted=newBlockList();
221+
trusted.addSubnet('10.0.0.0', 8);
222+
223+
constendpoint=awaitlisten(onSession, {
224+
endpoint: {
225+
blockList: trusted,
226+
blockListPolicy:'allow',
227+
},
228+
// ...
229+
});
230+
```
231+
232+
The block list is evaluated live — rules added or removed after the endpoint
233+
is created take effect immediately. The `endpoint.stats.packetsBlocked`
234+
counter tracks how many packets have been dropped by the filter.
235+
192236
### Applications
193237

194238
Every `QuicSession` is associated with a single application protocol, negotiated
@@ -831,6 +875,11 @@ added: v23.8.0
831875
per-host rate limiter. Read only. A non-zero value indicates one or more
832876
remote addresses are creating sessions faster than the configured rate allows.
833877

878+
### `endpointStats.packetsBlocked`
879+
880+
* Type: {bigint} The total number of incoming packets dropped by the
881+
block list filter. Read only.
882+
834883
## Class: `QuicSession`
835884

836885
<!-- YAML
@@ -2429,6 +2478,34 @@ added: v23.8.0
24292478

24302479
If not specified the endpoint will bind to IPv4 `localhost` on a random port.
24312480

2481+
#### `endpointOptions.blockList`
2482+
2483+
* Type: {net.BlockList}
2484+
2485+
An optional [`net.BlockList`][] instance for filtering incoming packets by
2486+
source address. When configured, every received UDP packet is checked against
2487+
the block list before any QUIC processing occurs, minimizing resource
2488+
expenditure on blocked sources. The block list is evaluated live — rules
2489+
added to the `BlockList` object after the endpoint is created take effect
2490+
immediately.
2491+
2492+
See [`endpointOptions.blockListPolicy`][] for how matches are interpreted.
2493+
2494+
#### `endpointOptions.blockListPolicy`
2495+
2496+
* Type: {string} One of `'deny'` or `'allow'`.
2497+
***Default:**`'deny'`
2498+
2499+
Controls how the [`endpointOptions.blockList`][] is interpreted:
2500+
2501+
*`'deny'` — Packets from addresses matching the block list are dropped.
2502+
All other addresses are accepted. This is the typical blocklist mode.
2503+
*`'allow'` — Only packets from addresses matching the block list are
2504+
accepted. All other addresses are dropped. This is an allowlist mode
2505+
for restricting access to known clients.
2506+
2507+
If no block list is configured, this option has no effect.
2508+
24322509
#### `endpointOptions.addressLRUSize`
24332510

24342511
<!-- YAML
@@ -4275,6 +4352,8 @@ throughput issues caused by flow control.
42754352
[`endpoint.busy`]: #endpointbusy
42764353
[`endpoint.maxConnectionsPerHost`]: #endpointmaxconnectionsperhost
42774354
[`endpoint.maxConnectionsTotal`]: #endpointmaxconnectionstotal
4355+
[`endpointOptions.blockListPolicy`]: #endpointoptionsblocklistpolicy
4356+
[`endpointOptions.blockList`]: #endpointoptionsblocklist
42784357
[`endpointOptions.immediateCloseBurst`]: #endpointoptionsimmediatecloseburst
42794358
[`endpointOptions.immediateCloseRate`]: #endpointoptionsimmediatecloserate
42804359
[`endpointOptions.retryBurst`]: #endpointoptionsretryburst
@@ -4288,6 +4367,7 @@ throughput issues caused by flow control.
42884367
[`error.errorCode`]: #errorerrorcode
42894368
[`fs.promises.open(path, 'r')`]: fs.md#fspromisesopenpath-flags-mode
42904369
[`maxDatagramFrameSize`]: #transportparamsmaxdatagramframesize
4370+
[`net.BlockList`]: net.md#class-netblocklist
42914371
[`quic.connect()`]: #quicconnectaddress-options
42924372
[`quic.listen()`]: #quiclistenonsession-options
42934373
[`session.close()`]: #sessioncloseoptions

‎lib/internal/blocklist.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,5 @@ ObjectSetPrototypeOf(InternalBlockList.prototype, BlockList.prototype);
298298
module.exports={
299299
BlockList,
300300
InternalBlockList,
301+
kHandle,
301302
};

‎lib/internal/quic/quic.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ if (!process.features.quic || !getOptionValue('--experimental-quic')) {
3636
}
3737

3838
const{ inspect }=require('internal/util/inspect');
39+
const{
40+
BlockList,
41+
kHandle: kBlockListHandle,
42+
}=require('internal/blocklist');
3943

4044
letdebug=require('internal/util/debuglog').debuglog('quic',(fn)=>{
4145
debug=fn;
@@ -318,6 +322,8 @@ const endpointRegistry = new SafeSet();
318322
* @property {number} [immediateCloseBurst] Burst capacity for immediate close rate limiter
319323
* @property {number} [sessionCreationRate] Per-host rate limit for session creation (per second)
320324
* @property {number} [sessionCreationBurst] Per-host burst capacity for session creation rate limiter
325+
* @property {net.BlockList} [blockList] Block list for filtering incoming packets by source address
326+
* @property {'deny'|'allow'} [blockListPolicy='deny'] How to interpret the block list
321327
* @property {ArrayBufferView} [resetTokenSecret] The reset token secret
322328
* @property {bigint|number} [retryTokenExpiration] The retry token expiration
323329
* @property {number} [rxDiagnosticLoss] The receive diagnostic loss probability (range 0.0-1.0)
@@ -4048,6 +4054,8 @@ class QuicEndpoint {
40484054
immediateCloseBurst,
40494055
sessionCreationRate,
40504056
sessionCreationBurst,
4057+
blockList,
4058+
blockListPolicy ='deny',
40514059
rxDiagnosticLoss,
40524060
txDiagnosticLoss,
40534061
udpReceiveBufferSize,
@@ -4062,6 +4070,16 @@ class QuicEndpoint {
40624070
tokenSecret,
40634071
}=options;
40644072

4073+
if(blockList!==undefined){
4074+
if(!BlockList.isBlockList(blockList)){
4075+
thrownewERR_INVALID_ARG_TYPE('options.blockList',
4076+
'net.BlockList',blockList);
4077+
}
4078+
}
4079+
4080+
validateOneOf(blockListPolicy,'options.blockListPolicy',
4081+
['deny','allow']);
4082+
40654083
// All of the other options will be validated internally by the C++ code
40664084
if(address!==undefined&&!SocketAddress.isSocketAddress(address)){
40674085
if(typeofaddress==='string'){
@@ -4093,6 +4111,9 @@ class QuicEndpoint {
40934111
immediateCloseBurst,
40944112
sessionCreationRate,
40954113
sessionCreationBurst,
4114+
// Pass the C++ handle, not the JS BlockList wrapper.
4115+
blockList: blockList?.[kBlockListHandle],
4116+
blockListPolicy,
40964117
rxDiagnosticLoss,
40974118
txDiagnosticLoss,
40984119
udpReceiveBufferSize,

‎lib/internal/quic/stats.js‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const {
6868
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT,
6969
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED,
7070
IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED,
71+
IDX_STATS_ENDPOINT_PACKETS_BLOCKED,
7172

7273
IDX_STATS_SESSION_CREATED_AT,
7374
IDX_STATS_SESSION_DESTROYED_AT,
@@ -136,6 +137,7 @@ assert(IDX_STATS_ENDPOINT_STATELESS_RESET_RATE_LIMITED !== undefined);
136137
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT!==undefined);
137138
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED!==undefined);
138139
assert(IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED!==undefined);
140+
assert(IDX_STATS_ENDPOINT_PACKETS_BLOCKED!==undefined);
139141
assert(IDX_STATS_SESSION_CREATED_AT!==undefined);
140142
assert(IDX_STATS_SESSION_DESTROYED_AT!==undefined);
141143
assert(IDX_STATS_SESSION_CLOSING_AT!==undefined);
@@ -338,6 +340,12 @@ class QuicEndpointStats {
338340
returnthis.#handle[IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED];
339341
}
340342

343+
/** @type {bigint} */
344+
getpacketsBlocked(){
345+
assertIsQuicEndpointStats(this);
346+
returnthis.#handle[IDX_STATS_ENDPOINT_PACKETS_BLOCKED];
347+
}
348+
341349
toString(){
342350
returnJSONStringify(this.toJSON());
343351
}
@@ -363,6 +371,7 @@ class QuicEndpointStats {
363371
immediateCloseCount,
364372
immediateCloseRateLimited,
365373
sessionCreationRateLimited,
374+
packetsBlocked,
366375
}=this;
367376
return{
368377
__proto__: null,
@@ -387,6 +396,7 @@ class QuicEndpointStats {
387396
immediateCloseCount: `${immediateCloseCount}`,
388397
immediateCloseRateLimited: `${immediateCloseRateLimited}`,
389398
sessionCreationRateLimited: `${sessionCreationRateLimited}`,
399+
packetsBlocked: `${packetsBlocked}`,
390400
};
391401
}
392402

@@ -421,6 +431,7 @@ class QuicEndpointStats {
421431
immediateCloseCount,
422432
immediateCloseRateLimited,
423433
sessionCreationRateLimited,
434+
packetsBlocked,
424435
}=this;
425436

426437
return`QuicEndpointStats ${inspect({
@@ -443,6 +454,7 @@ class QuicEndpointStats {
443454
immediateCloseCount,
444455
immediateCloseRateLimited,
445456
sessionCreationRateLimited,
457+
packetsBlocked,
446458
},opts)}`;
447459
}
448460

‎src/node_sockaddr.cc‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,7 @@ void SocketAddressBlockList::AddSocketAddressMask(
434434
rules_.emplace_front(std::move(rule));
435435
}
436436

437-
boolSocketAddressBlockList::Apply(
438-
const std::shared_ptr<SocketAddress>& address) {
437+
boolSocketAddressBlockList::Apply(const SocketAddress& address) {
439438
Mutex::ScopedLock lock(mutex_);
440439
for (constauto& rule : rules_) {
441440
if (rule->Apply(address)) returntrue;
@@ -457,8 +456,8 @@ SocketAddressBlockList::SocketAddressMaskRule::SocketAddressMaskRule(
457456
: network(network_), prefix(prefix_) {}
458457

459458
boolSocketAddressBlockList::SocketAddressRule::Apply(
460-
conststd::shared_ptr<SocketAddress>& address) {
461-
returnthis->address->is_match(*address.get());
459+
const SocketAddress& address) {
460+
returnthis->address->is_match(address);
462461
}
463462

464463
std::string SocketAddressBlockList::SocketAddressRule::ToString() {
@@ -470,8 +469,8 @@ std::string SocketAddressBlockList::SocketAddressRule::ToString() {
470469
}
471470

472471
boolSocketAddressBlockList::SocketAddressRangeRule::Apply(
473-
conststd::shared_ptr<SocketAddress>& address) {
474-
return*address.get() >= *start.get() && *address.get() <= *end.get();
472+
const SocketAddress& address) {
473+
return address >= *start.get() && address <= *end.get();
475474
}
476475

477476
std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
@@ -485,8 +484,8 @@ std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
485484
}
486485

487486
boolSocketAddressBlockList::SocketAddressMaskRule::Apply(
488-
conststd::shared_ptr<SocketAddress>& address) {
489-
return address->is_in_network(*network.get(), prefix);
487+
const SocketAddress& address) {
488+
return address.is_in_network(*network.get(), prefix);
490489
}
491490

492491
std::string SocketAddressBlockList::SocketAddressMaskRule::ToString() {
@@ -656,7 +655,7 @@ void SocketAddressBlockListWrap::Check(
656655
SocketAddressBase* addr;
657656
ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);
658657

659-
args.GetReturnValue().Set(wrap->blocklist_->Apply(addr->address()));
658+
args.GetReturnValue().Set(wrap->blocklist_->Apply(*addr->address()));
660659
}
661660

662661
voidSocketAddressBlockListWrap::GetRules(

‎src/node_sockaddr.h‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,14 @@ class SocketAddressBlockList : public MemoryRetainer {
258258
voidAddSocketAddressMask(const std::shared_ptr<SocketAddress>& address,
259259
int prefix);
260260

261-
boolApply(conststd::shared_ptr<SocketAddress>& address);
261+
boolApply(const SocketAddress& address);
262262

263263
size_tsize() const { return rules_.size(); }
264264

265265
v8::MaybeLocal<v8::Array> ListRules(Environment* env);
266266

267267
structRule : publicMemoryRetainer {
268-
virtualboolApply(conststd::shared_ptr<SocketAddress>& address) = 0;
268+
virtualboolApply(const SocketAddress& address) = 0;
269269
inline v8::MaybeLocal<v8::Value> ToV8String(Environment* env);
270270
virtual std::string ToString() = 0;
271271
};
@@ -275,7 +275,7 @@ class SocketAddressBlockList : public MemoryRetainer {
275275

276276
explicitSocketAddressRule(const std::shared_ptr<SocketAddress>& address);
277277

278-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
278+
boolApply(const SocketAddress& address) override;
279279
std::string ToString() override;
280280

281281
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -290,7 +290,7 @@ class SocketAddressBlockList : public MemoryRetainer {
290290
SocketAddressRangeRule(const std::shared_ptr<SocketAddress>& start,
291291
const std::shared_ptr<SocketAddress>& end);
292292

293-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
293+
boolApply(const SocketAddress& address) override;
294294
std::string ToString() override;
295295

296296
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -305,7 +305,7 @@ class SocketAddressBlockList : public MemoryRetainer {
305305
SocketAddressMaskRule(const std::shared_ptr<SocketAddress>& address,
306306
int prefix);
307307

308-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
308+
boolApply(const SocketAddress& address) override;
309309
std::string ToString() override;
310310

311311
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -353,6 +353,10 @@ class SocketAddressBlockListWrap : public BaseObject {
353353
std::shared_ptr<SocketAddressBlockList> blocklist =
354354
std::make_shared<SocketAddressBlockList>());
355355

356+
inlineconst std::shared_ptr<SocketAddressBlockList>& blocklist() const {
357+
return blocklist_;
358+
}
359+
356360
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
357361
SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap)
358362
SET_SELF_SIZE(SocketAddressBlockListWrap)

‎src/quic/bindingdata.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SessionManager;
7070
V(ack_delay_exponent, "ackDelayExponent") \
7171
V(active_connection_id_limit, "activeConnectionIDLimit") \
7272
V(address_lru_size, "addressLRUSize") \
73+
V(allow, "allow") \
7374
V(application, "application") \
7475
V(authoritative, "authoritative") \
7576
V(bbr, "bbr") \
@@ -81,6 +82,7 @@ class SessionManager;
8182
V(crl, "crl") \
8283
V(cubic, "cubic") \
8384
V(datagram_drop_policy, "datagramDropPolicy") \
85+
V(deny, "deny") \
8486
V(disable_stateless_reset, "disableStatelessReset") \
8587
V(draining_period_multiplier, "drainingPeriodMultiplier") \
8688
V(enable_connect_protocol, "enableConnectProtocol") \
@@ -127,6 +129,8 @@ class SessionManager;
127129
V(immediate_close_burst, "immediateCloseBurst") \
128130
V(session_creation_rate, "sessionCreationRate") \
129131
V(session_creation_burst, "sessionCreationBurst") \
132+
V(block_list, "blockList") \
133+
V(block_list_policy, "blockListPolicy") \
130134
V(max_stream_window, "maxStreamWindow") \
131135
V(max_window, "maxWindow") \
132136
V(min_version, "minVersion") \

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit e493f04

Browse files
jasnelladuh95
authored andcommitted
quic: add block list support for endpoints
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode/Opus 4.6 PR-URL: #63483 Backport-PR-URL: #64675 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 6d6cd45 commit e493f04

14 files changed

Lines changed: 299 additions & 31 deletions

‎doc/api/quic.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,50 @@ object. Each rate limiter has a corresponding counter
189189
were dropped. A non-zero value indicates the rate limiter is actively
190190
protecting the endpoint.
191191

192+
#### Block lists
193+
194+
Endpoints can filter incoming packets by source address using a
195+
[`net.BlockList`][]. The block list is checked before any QUIC processing
196+
occurs, so blocked packets consume no resources beyond the check itself.
197+
198+
In **deny** mode (the default), packets from addresses in the list are dropped:
199+
200+
```mjs
201+
import { BlockList } from'node:net';
202+
import { listen } from'node:quic';
203+
204+
constblocked=newBlockList();
205+
blocked.addSubnet('192.168.1.0', 24); // Block an entire subnet
206+
blocked.addAddress('10.0.0.5'); // Block a specific address
207+
208+
constendpoint=awaitlisten(onSession, {
209+
endpoint: {
210+
blockList: blocked,
211+
blockListPolicy:'deny',
212+
},
213+
// ...
214+
});
215+
```
216+
217+
In **allow** mode, only packets from addresses in the list are accepted:
218+
219+
```mjs
220+
consttrusted=newBlockList();
221+
trusted.addSubnet('10.0.0.0', 8);
222+
223+
constendpoint=awaitlisten(onSession, {
224+
endpoint: {
225+
blockList: trusted,
226+
blockListPolicy:'allow',
227+
},
228+
// ...
229+
});
230+
```
231+
232+
The block list is evaluated live — rules added or removed after the endpoint
233+
is created take effect immediately. The `endpoint.stats.packetsBlocked`
234+
counter tracks how many packets have been dropped by the filter.
235+
192236
### Applications
193237

194238
Every `QuicSession` is associated with a single application protocol, negotiated
@@ -831,6 +875,11 @@ added: v23.8.0
831875
per-host rate limiter. Read only. A non-zero value indicates one or more
832876
remote addresses are creating sessions faster than the configured rate allows.
833877

878+
### `endpointStats.packetsBlocked`
879+
880+
* Type: {bigint} The total number of incoming packets dropped by the
881+
block list filter. Read only.
882+
834883
## Class: `QuicSession`
835884

836885
<!-- YAML
@@ -2429,6 +2478,34 @@ added: v23.8.0
24292478

24302479
If not specified the endpoint will bind to IPv4 `localhost` on a random port.
24312480

2481+
#### `endpointOptions.blockList`
2482+
2483+
* Type: {net.BlockList}
2484+
2485+
An optional [`net.BlockList`][] instance for filtering incoming packets by
2486+
source address. When configured, every received UDP packet is checked against
2487+
the block list before any QUIC processing occurs, minimizing resource
2488+
expenditure on blocked sources. The block list is evaluated live — rules
2489+
added to the `BlockList` object after the endpoint is created take effect
2490+
immediately.
2491+
2492+
See [`endpointOptions.blockListPolicy`][] for how matches are interpreted.
2493+
2494+
#### `endpointOptions.blockListPolicy`
2495+
2496+
* Type: {string} One of `'deny'` or `'allow'`.
2497+
***Default:**`'deny'`
2498+
2499+
Controls how the [`endpointOptions.blockList`][] is interpreted:
2500+
2501+
*`'deny'` — Packets from addresses matching the block list are dropped.
2502+
All other addresses are accepted. This is the typical blocklist mode.
2503+
*`'allow'` — Only packets from addresses matching the block list are
2504+
accepted. All other addresses are dropped. This is an allowlist mode
2505+
for restricting access to known clients.
2506+
2507+
If no block list is configured, this option has no effect.
2508+
24322509
#### `endpointOptions.addressLRUSize`
24332510

24342511
<!-- YAML
@@ -4275,6 +4352,8 @@ throughput issues caused by flow control.
42754352
[`endpoint.busy`]: #endpointbusy
42764353
[`endpoint.maxConnectionsPerHost`]: #endpointmaxconnectionsperhost
42774354
[`endpoint.maxConnectionsTotal`]: #endpointmaxconnectionstotal
4355+
[`endpointOptions.blockListPolicy`]: #endpointoptionsblocklistpolicy
4356+
[`endpointOptions.blockList`]: #endpointoptionsblocklist
42784357
[`endpointOptions.immediateCloseBurst`]: #endpointoptionsimmediatecloseburst
42794358
[`endpointOptions.immediateCloseRate`]: #endpointoptionsimmediatecloserate
42804359
[`endpointOptions.retryBurst`]: #endpointoptionsretryburst
@@ -4288,6 +4367,7 @@ throughput issues caused by flow control.
42884367
[`error.errorCode`]: #errorerrorcode
42894368
[`fs.promises.open(path, 'r')`]: fs.md#fspromisesopenpath-flags-mode
42904369
[`maxDatagramFrameSize`]: #transportparamsmaxdatagramframesize
4370+
[`net.BlockList`]: net.md#class-netblocklist
42914371
[`quic.connect()`]: #quicconnectaddress-options
42924372
[`quic.listen()`]: #quiclistenonsession-options
42934373
[`session.close()`]: #sessioncloseoptions

‎lib/internal/blocklist.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,5 @@ ObjectSetPrototypeOf(InternalBlockList.prototype, BlockList.prototype);
298298
module.exports={
299299
BlockList,
300300
InternalBlockList,
301+
kHandle,
301302
};

‎lib/internal/quic/quic.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ if (!process.features.quic || !getOptionValue('--experimental-quic')) {
3636
}
3737

3838
const{ inspect }=require('internal/util/inspect');
39+
const{
40+
BlockList,
41+
kHandle: kBlockListHandle,
42+
}=require('internal/blocklist');
3943

4044
letdebug=require('internal/util/debuglog').debuglog('quic',(fn)=>{
4145
debug=fn;
@@ -318,6 +322,8 @@ const endpointRegistry = new SafeSet();
318322
* @property {number} [immediateCloseBurst] Burst capacity for immediate close rate limiter
319323
* @property {number} [sessionCreationRate] Per-host rate limit for session creation (per second)
320324
* @property {number} [sessionCreationBurst] Per-host burst capacity for session creation rate limiter
325+
* @property {net.BlockList} [blockList] Block list for filtering incoming packets by source address
326+
* @property {'deny'|'allow'} [blockListPolicy='deny'] How to interpret the block list
321327
* @property {ArrayBufferView} [resetTokenSecret] The reset token secret
322328
* @property {bigint|number} [retryTokenExpiration] The retry token expiration
323329
* @property {number} [rxDiagnosticLoss] The receive diagnostic loss probability (range 0.0-1.0)
@@ -4048,6 +4054,8 @@ class QuicEndpoint {
40484054
immediateCloseBurst,
40494055
sessionCreationRate,
40504056
sessionCreationBurst,
4057+
blockList,
4058+
blockListPolicy ='deny',
40514059
rxDiagnosticLoss,
40524060
txDiagnosticLoss,
40534061
udpReceiveBufferSize,
@@ -4062,6 +4070,16 @@ class QuicEndpoint {
40624070
tokenSecret,
40634071
}=options;
40644072

4073+
if(blockList!==undefined){
4074+
if(!BlockList.isBlockList(blockList)){
4075+
thrownewERR_INVALID_ARG_TYPE('options.blockList',
4076+
'net.BlockList',blockList);
4077+
}
4078+
}
4079+
4080+
validateOneOf(blockListPolicy,'options.blockListPolicy',
4081+
['deny','allow']);
4082+
40654083
// All of the other options will be validated internally by the C++ code
40664084
if(address!==undefined&&!SocketAddress.isSocketAddress(address)){
40674085
if(typeofaddress==='string'){
@@ -4093,6 +4111,9 @@ class QuicEndpoint {
40934111
immediateCloseBurst,
40944112
sessionCreationRate,
40954113
sessionCreationBurst,
4114+
// Pass the C++ handle, not the JS BlockList wrapper.
4115+
blockList: blockList?.[kBlockListHandle],
4116+
blockListPolicy,
40964117
rxDiagnosticLoss,
40974118
txDiagnosticLoss,
40984119
udpReceiveBufferSize,

‎lib/internal/quic/stats.js‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const {
6868
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT,
6969
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED,
7070
IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED,
71+
IDX_STATS_ENDPOINT_PACKETS_BLOCKED,
7172

7273
IDX_STATS_SESSION_CREATED_AT,
7374
IDX_STATS_SESSION_DESTROYED_AT,
@@ -136,6 +137,7 @@ assert(IDX_STATS_ENDPOINT_STATELESS_RESET_RATE_LIMITED !== undefined);
136137
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT!==undefined);
137138
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED!==undefined);
138139
assert(IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED!==undefined);
140+
assert(IDX_STATS_ENDPOINT_PACKETS_BLOCKED!==undefined);
139141
assert(IDX_STATS_SESSION_CREATED_AT!==undefined);
140142
assert(IDX_STATS_SESSION_DESTROYED_AT!==undefined);
141143
assert(IDX_STATS_SESSION_CLOSING_AT!==undefined);
@@ -338,6 +340,12 @@ class QuicEndpointStats {
338340
returnthis.#handle[IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED];
339341
}
340342

343+
/** @type {bigint} */
344+
getpacketsBlocked(){
345+
assertIsQuicEndpointStats(this);
346+
returnthis.#handle[IDX_STATS_ENDPOINT_PACKETS_BLOCKED];
347+
}
348+
341349
toString(){
342350
returnJSONStringify(this.toJSON());
343351
}
@@ -363,6 +371,7 @@ class QuicEndpointStats {
363371
immediateCloseCount,
364372
immediateCloseRateLimited,
365373
sessionCreationRateLimited,
374+
packetsBlocked,
366375
}=this;
367376
return{
368377
__proto__: null,
@@ -387,6 +396,7 @@ class QuicEndpointStats {
387396
immediateCloseCount: `${immediateCloseCount}`,
388397
immediateCloseRateLimited: `${immediateCloseRateLimited}`,
389398
sessionCreationRateLimited: `${sessionCreationRateLimited}`,
399+
packetsBlocked: `${packetsBlocked}`,
390400
};
391401
}
392402

@@ -421,6 +431,7 @@ class QuicEndpointStats {
421431
immediateCloseCount,
422432
immediateCloseRateLimited,
423433
sessionCreationRateLimited,
434+
packetsBlocked,
424435
}=this;
425436

426437
return`QuicEndpointStats ${inspect({
@@ -443,6 +454,7 @@ class QuicEndpointStats {
443454
immediateCloseCount,
444455
immediateCloseRateLimited,
445456
sessionCreationRateLimited,
457+
packetsBlocked,
446458
},opts)}`;
447459
}
448460

‎src/node_sockaddr.cc‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,7 @@ void SocketAddressBlockList::AddSocketAddressMask(
434434
rules_.emplace_front(std::move(rule));
435435
}
436436

437-
boolSocketAddressBlockList::Apply(
438-
const std::shared_ptr<SocketAddress>& address) {
437+
boolSocketAddressBlockList::Apply(const SocketAddress& address) {
439438
Mutex::ScopedLock lock(mutex_);
440439
for (constauto& rule : rules_) {
441440
if (rule->Apply(address)) returntrue;
@@ -457,8 +456,8 @@ SocketAddressBlockList::SocketAddressMaskRule::SocketAddressMaskRule(
457456
: network(network_), prefix(prefix_) {}
458457

459458
boolSocketAddressBlockList::SocketAddressRule::Apply(
460-
conststd::shared_ptr<SocketAddress>& address) {
461-
returnthis->address->is_match(*address.get());
459+
const SocketAddress& address) {
460+
returnthis->address->is_match(address);
462461
}
463462

464463
std::string SocketAddressBlockList::SocketAddressRule::ToString() {
@@ -470,8 +469,8 @@ std::string SocketAddressBlockList::SocketAddressRule::ToString() {
470469
}
471470

472471
boolSocketAddressBlockList::SocketAddressRangeRule::Apply(
473-
conststd::shared_ptr<SocketAddress>& address) {
474-
return*address.get() >= *start.get() && *address.get() <= *end.get();
472+
const SocketAddress& address) {
473+
return address >= *start.get() && address <= *end.get();
475474
}
476475

477476
std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
@@ -485,8 +484,8 @@ std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
485484
}
486485

487486
boolSocketAddressBlockList::SocketAddressMaskRule::Apply(
488-
conststd::shared_ptr<SocketAddress>& address) {
489-
return address->is_in_network(*network.get(), prefix);
487+
const SocketAddress& address) {
488+
return address.is_in_network(*network.get(), prefix);
490489
}
491490

492491
std::string SocketAddressBlockList::SocketAddressMaskRule::ToString() {
@@ -656,7 +655,7 @@ void SocketAddressBlockListWrap::Check(
656655
SocketAddressBase* addr;
657656
ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);
658657

659-
args.GetReturnValue().Set(wrap->blocklist_->Apply(addr->address()));
658+
args.GetReturnValue().Set(wrap->blocklist_->Apply(*addr->address()));
660659
}
661660

662661
voidSocketAddressBlockListWrap::GetRules(

‎src/node_sockaddr.h‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,14 @@ class SocketAddressBlockList : public MemoryRetainer {
258258
voidAddSocketAddressMask(const std::shared_ptr<SocketAddress>& address,
259259
int prefix);
260260

261-
boolApply(conststd::shared_ptr<SocketAddress>& address);
261+
boolApply(const SocketAddress& address);
262262

263263
size_tsize() const { return rules_.size(); }
264264

265265
v8::MaybeLocal<v8::Array> ListRules(Environment* env);
266266

267267
structRule : publicMemoryRetainer {
268-
virtualboolApply(conststd::shared_ptr<SocketAddress>& address) = 0;
268+
virtualboolApply(const SocketAddress& address) = 0;
269269
inline v8::MaybeLocal<v8::Value> ToV8String(Environment* env);
270270
virtual std::string ToString() = 0;
271271
};
@@ -275,7 +275,7 @@ class SocketAddressBlockList : public MemoryRetainer {
275275

276276
explicitSocketAddressRule(const std::shared_ptr<SocketAddress>& address);
277277

278-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
278+
boolApply(const SocketAddress& address) override;
279279
std::string ToString() override;
280280

281281
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -290,7 +290,7 @@ class SocketAddressBlockList : public MemoryRetainer {
290290
SocketAddressRangeRule(const std::shared_ptr<SocketAddress>& start,
291291
const std::shared_ptr<SocketAddress>& end);
292292

293-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
293+
boolApply(const SocketAddress& address) override;
294294
std::string ToString() override;
295295

296296
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -305,7 +305,7 @@ class SocketAddressBlockList : public MemoryRetainer {
305305
SocketAddressMaskRule(const std::shared_ptr<SocketAddress>& address,
306306
int prefix);
307307

308-
boolApply(conststd::shared_ptr<SocketAddress>& address) override;
308+
boolApply(const SocketAddress& address) override;
309309
std::string ToString() override;
310310

311311
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
@@ -353,6 +353,10 @@ class SocketAddressBlockListWrap : public BaseObject {
353353
std::shared_ptr<SocketAddressBlockList> blocklist =
354354
std::make_shared<SocketAddressBlockList>());
355355

356+
inlineconst std::shared_ptr<SocketAddressBlockList>& blocklist() const {
357+
return blocklist_;
358+
}
359+
356360
voidMemoryInfo(node::MemoryTracker* tracker) constoverride;
357361
SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap)
358362
SET_SELF_SIZE(SocketAddressBlockListWrap)

‎src/quic/bindingdata.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SessionManager;
7070
V(ack_delay_exponent, "ackDelayExponent") \
7171
V(active_connection_id_limit, "activeConnectionIDLimit") \
7272
V(address_lru_size, "addressLRUSize") \
73+
V(allow, "allow") \
7374
V(application, "application") \
7475
V(authoritative, "authoritative") \
7576
V(bbr, "bbr") \
@@ -81,6 +82,7 @@ class SessionManager;
8182
V(crl, "crl") \
8283
V(cubic, "cubic") \
8384
V(datagram_drop_policy, "datagramDropPolicy") \
85+
V(deny, "deny") \
8486
V(disable_stateless_reset, "disableStatelessReset") \
8587
V(draining_period_multiplier, "drainingPeriodMultiplier") \
8688
V(enable_connect_protocol, "enableConnectProtocol") \
@@ -127,6 +129,8 @@ class SessionManager;
127129
V(immediate_close_burst, "immediateCloseBurst") \
128130
V(session_creation_rate, "sessionCreationRate") \
129131
V(session_creation_burst, "sessionCreationBurst") \
132+
V(block_list, "blockList") \
133+
V(block_list_policy, "blockListPolicy") \
130134
V(max_stream_window, "maxStreamWindow") \
131135
V(max_window, "maxWindow") \
132136
V(min_version, "minVersion") \

0 commit comments

Comments
 (0)