quic: use net.BlockList for limiting access to a QuicSocket

PR-URL: https://github.com/nodejs/node/pull/34741
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
James M Snell
2020-08-11 15:46:25 -07:00
parent 1c14810edc
commit c855c3e8ca
6 changed files with 96 additions and 1 deletions

View File

@@ -1445,6 +1445,24 @@ error will be thrown if `quicsock.addEndpoint()` is called either after
the `QuicSocket` has already started binding to the local ports, or after
the `QuicSocket` has been destroyed.
#### `quicsocket.blockList`
<!-- YAML
added: REPLACEME
-->
* Type: {net.BlockList}
A {net.BlockList} instance used to define rules for remote IPv4 or IPv6
addresses that this `QuicSocket` is not permitted to interact with. The
rules can be specified as either specific individual addresses, ranges
of addresses, or CIDR subnet ranges.
When listening as a server, if a packet is received from a blocked address,
the packet will be ignored.
When connecting as a client, if the remote IP address is blocked, the
connection attempt will be rejected.
#### `quicsocket.bound`
<!-- YAML
added: REPLACEME

View File

@@ -54,6 +54,7 @@ const { Duplex } = require('stream');
const {
createSecureContext: _createSecureContext
} = require('tls');
const BlockList = require('internal/blocklist');
const {
translatePeerCertificate
} = require('_tls_common');
@@ -891,6 +892,7 @@ class QuicSocket extends EventEmitter {
[kInternalState] = {
alpn: undefined,
bindPromise: undefined,
blockList: undefined,
client: undefined,
closePromise: undefined,
closePromiseResolve: undefined,
@@ -1007,8 +1009,10 @@ class QuicSocket extends EventEmitter {
this[async_id_symbol] = handle.getAsyncId();
this[kInternalState].sharedState =
new QuicSocketSharedState(handle.state);
this[kInternalState].blockList = new BlockList(handle.blockList);
} else {
this[kInternalState].sharedState = undefined;
this[kInternalState].blockList = undefined;
}
}
@@ -1303,6 +1307,9 @@ class QuicSocket extends EventEmitter {
if (this.closing)
throw new ERR_INVALID_STATE('QuicSocket is closing');
if (this.blockList.check(ip, type === AF_INET6 ? 'ipv6' : 'ipv4'))
throw new ERR_OPERATION_FAILED(`${ip} failed BlockList check`);
return new QuicClientSession(this, options, type, ip);
}
@@ -1458,6 +1465,10 @@ class QuicSocket extends EventEmitter {
return this;
}
get blockList() {
return this[kInternalState]?.blockList;
}
get endpoints() {
return Array.from(this[kInternalState].endpoints);
}

View File

@@ -207,7 +207,7 @@ enum QuicSessionStateFields {
V(SMOOTHED_RTT, smoothed_rtt, "Smoothed RTT") \
V(CWND, cwnd, "Cwnd") \
V(RECEIVE_RATE, receive_rate, "Receive Rate / Sec") \
V(SEND_RATE, send_rate, "Send Rate Sec")
V(SEND_RATE, send_rate, "Send Rate Sec") \
#define V(name, _, __) IDX_QUIC_SESSION_STATS_##name,
enum QuicSessionStatsIdx : int {

View File

@@ -252,6 +252,7 @@ QuicSocket::QuicSocket(
: AsyncWrap(quic_state->env(), wrap, AsyncWrap::PROVIDER_QUICSOCKET),
StatsBase(quic_state->env(), wrap),
alloc_info_(MakeAllocator()),
block_list_(SocketAddressBlockListWrap::New(quic_state->env())),
options_(options),
state_(quic_state->env()->isolate()),
max_connections_(max_connections),
@@ -269,6 +270,12 @@ QuicSocket::QuicSocket(
EntropySource(token_secret_, kTokenSecretLen);
wrap->DefineOwnProperty(
env()->context(),
env()->block_list_string(),
block_list_->object(),
PropertyAttribute::ReadOnly).Check();
wrap->DefineOwnProperty(
env()->context(),
env()->state_string(),
@@ -432,6 +439,12 @@ void QuicSocket::OnReceive(
return;
}
if (UNLIKELY(block_list_->Apply(remote_addr))) {
Debug(this, "Ignoring blocked remote address: %s", remote_addr);
IncrementStat(&QuicSocketStats::packets_ignored);
return;
}
IncrementStat(&QuicSocketStats::bytes_received, nread);
const uint8_t* data = reinterpret_cast<const uint8_t*>(buf.data());

View File

@@ -516,6 +516,7 @@ class QuicSocket : public AsyncWrap,
std::vector<BaseObjectPtr<QuicEndpoint>> endpoints_;
SocketAddress::Map<BaseObjectWeakPtr<QuicEndpoint>> bound_endpoints_;
BaseObjectWeakPtr<QuicEndpoint> preferred_endpoint_;
BaseObjectPtr<SocketAddressBlockListWrap> block_list_;
uint32_t flags_ = 0;
uint32_t options_ = 0;

View File

@@ -0,0 +1,52 @@
// Flags: --no-warnings
'use strict';
const common = require('../common');
if (!common.hasQuic)
common.skip('missing quic');
const { createQuicSocket, BlockList } = require('net');
const assert = require('assert');
const { key, cert, ca } = require('../common/quic');
const { once } = require('events');
const idleTimeout = common.platformTimeout(1);
const options = { key, cert, ca, alpn: 'zzz', idleTimeout };
const client = createQuicSocket({ client: options });
const server = createQuicSocket({ server: options });
assert(client.blockList instanceof BlockList);
assert(server.blockList instanceof BlockList);
client.blockList.addAddress('10.0.0.1');
assert(client.blockList.check('10.0.0.1'));
// Connection fails because the IP address is blocked
assert.rejects(client.connect({ address: '10.0.0.1' }), {
code: 'ERR_OPERATION_FAILED'
}).then(common.mustCall());
server.blockList.addAddress('127.0.0.1');
(async () => {
server.on('session', common.mustNotCall());
await server.listen();
const session = await client.connect({
address: common.localhostIPv4,
port: server.endpoints[0].address.port,
idleTimeout,
});
session.on('secure', common.mustNotCall());
await once(session, 'close');
await Promise.all([server.close(), client.close()]);
})().then(common.mustCall());