Skip to content

Commit

Permalink
quic: use net.BlockList for limiting access to a QuicSocket
Browse files Browse the repository at this point in the history
PR-URL: #34741
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Rich Trott <[email protected]>
  • Loading branch information
jasnell committed Aug 17, 2020
1 parent 1c14810 commit c855c3e
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 1 deletion.
18 changes: 18 additions & 0 deletions doc/api/quic.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions lib/internal/quic/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const { Duplex } = require('stream');
const {
createSecureContext: _createSecureContext
} = require('tls');
const BlockList = require('internal/blocklist');
const {
translatePeerCertificate
} = require('_tls_common');
Expand Down Expand Up @@ -891,6 +892,7 @@ class QuicSocket extends EventEmitter {
[kInternalState] = {
alpn: undefined,
bindPromise: undefined,
blockList: undefined,
client: undefined,
closePromise: undefined,
closePromiseResolve: undefined,
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -1458,6 +1465,10 @@ class QuicSocket extends EventEmitter {
return this;
}

get blockList() {
return this[kInternalState]?.blockList;
}

get endpoints() {
return Array.from(this[kInternalState].endpoints);
}
Expand Down
2 changes: 1 addition & 1 deletion src/quic/node_quic_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 13 additions & 0 deletions src/quic/node_quic_socket.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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(),
Expand Down Expand Up @@ -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());
Expand Down
1 change: 1 addition & 0 deletions src/quic/node_quic_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
52 changes: 52 additions & 0 deletions test/parallel/test-quic-blocklist.js
Original file line number Diff line number Diff line change
@@ -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());

0 comments on commit c855c3e

Please sign in to comment.