Skip to content
This repository has been archived by the owner on Aug 11, 2020. It is now read-only.

quic: add option to use an LRU for validateAddress #48

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/api/quic.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ added: REPLACEME
* `validateAddress` {boolean} When `true`, the `QuicSocket` will use explicit
address validation using a QUIC `RETRY` frame when listening for new server
sessions. Default: `false`.
* `validateAddressLRU` {boolean} When `true`, validation will be skipped if
the address has been recently validated. Currently, only the 10 most
recently validated addresses are remembered. Setting `validateAddressLRU`
to `true`, will enable the `validateAddress` option as well. Default:
`false`.

Creates a new `QuicSocket` instance.

Expand Down
5 changes: 4 additions & 1 deletion lib/internal/quic/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ const {
QUICCLIENTSESSION_OPTION_REQUEST_OCSP,
QUICCLIENTSESSION_OPTION_VERIFY_HOSTNAME_IDENTITY,
QUICSOCKET_OPTIONS_VALIDATE_ADDRESS,
QUICSOCKET_OPTIONS_VALIDATE_ADDRESS_LRU,
}
} = internalBinding('quic');

Expand Down Expand Up @@ -633,10 +634,12 @@ class QuicSocket extends EventEmitter {
server, // Default configuration for QuicServerSessions
type, // 'udp4' or 'udp6'
validateAddress, // True if address verification should be used.
validateAddressLRU, // True if an LRU should be used for add validation
} = validateQuicSocketOptions(options || {});
super();
const socketOptions =
validateAddress ? QUICSOCKET_OPTIONS_VALIDATE_ADDRESS : 0;
(validateAddress ? QUICSOCKET_OPTIONS_VALIDATE_ADDRESS : 0) |
(validateAddressLRU ? QUICSOCKET_OPTIONS_VALIDATE_ADDRESS_LRU : 0);
const handle =
new QuicSocketHandle(
socketOptions,
Expand Down
10 changes: 9 additions & 1 deletion lib/internal/quic/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ function validateQuicSocketOptions(options) {
server,
type = 'udp4',
validateAddress = false,
validateAddressLRU = false,
retryTokenTimeout = DEFAULT_RETRYTOKEN_EXPIRATION,
} = { ...options };
validateBindOptions(port, address);
Expand All @@ -345,6 +346,12 @@ function validateQuicSocketOptions(options) {
'boolean',
validateAddress);
}
if (typeof validateAddressLRU !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE(
'options.validateAddressLRU',
'boolean',
validateAddressLRU);
}
validateNumberInBoundedRange(
retryTokenTimeout,
'options.retryTokenTimeout',
Expand All @@ -365,7 +372,8 @@ function validateQuicSocketOptions(options) {
reuseAddr,
server,
type: getSocketType(type),
validateAddress
validateAddress: validateAddress || validateAddressLRU,
validateAddressLRU,
};
}

Expand Down
3 changes: 3 additions & 0 deletions src/node_quic.cc
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ void Initialize(Local<Object> target,
NODE_DEFINE_CONSTANT(
constants,
QUICSOCKET_OPTIONS_VALIDATE_ADDRESS);
NODE_DEFINE_CONSTANT(
constants,
QUICSOCKET_OPTIONS_VALIDATE_ADDRESS_LRU);

target->Set(context,
env->constants_string(),
Expand Down
69 changes: 47 additions & 22 deletions src/node_quic_socket.cc
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,29 @@ ssize_t QuicSocket::SendRetry(
return req->Send();
}

namespace {
SocketAddress::Hash addr_hash;
};

void QuicSocket::SetValidatedAddress(const sockaddr* addr) {
if (IsOptionSet(QUICSOCKET_OPTIONS_VALIDATE_ADDRESS_LRU)) {
// Remove the oldest item if we've hit the LRU limit
validated_addrs_.push_back(addr_hash(addr));
if (validated_addrs_.size() > MAX_VALIDATE_ADDRESS_LRU)
validated_addrs_.pop_front();
}
}

bool QuicSocket::IsValidatedAddress(const sockaddr* addr) {
if (IsOptionSet(QUICSOCKET_OPTIONS_VALIDATE_ADDRESS_LRU)) {
auto res = std::find(std::begin(validated_addrs_),
std::end(validated_addrs_),
addr_hash((addr)));
return res != std::end(validated_addrs_);
}
return false;
}

std::shared_ptr<QuicSession> QuicSocket::AcceptInitialPacket(
uint32_t version,
QuicCID* dcid,
Expand Down Expand Up @@ -627,31 +650,33 @@ std::shared_ptr<QuicSession> QuicSocket::AcceptInitialPacket(
// retry token in the packet. If one does not exist, we send a retry with
// a new token. If it does exist, and if it's valid, we grab the original
// cid and continue.
// TODO(@jasnell): Currently, this performs the address validation on every
// initial packet, even if the path for a given address has already been
// validated. This is good default behavior because network paths change
// and just because a path is valid at one moment, it doesn't make it valid
// the next. However, it does introduce an additional bit of latency. In the
// future, we may allow for an option to validate a path once per QuicSocket
// instance.
if (IsOptionSet(QUICSOCKET_OPTIONS_VALIDATE_ADDRESS) &&
hd.type == NGTCP2_PKT_INITIAL) {
Debug(this, "Performing explicit address validation.");
if (hd.tokenlen == 0 ||
VerifyRetryToken(
env(),
&ocid,
&hd,
addr,
&token_crypto_ctx_,
&token_secret_,
retry_token_expiration_) != 0) {
Debug(this, "A valid retry token was not found. Sending retry.");
SendRetry(version, dcid, scid, addr);
return session;
// If the VALIDATE_ADDRESS_LRU option is set, IsValidatedAddress
// will check to see if the given address is in the validated_addrs_
// LRU cache. If it is, we'll skip the validation step entirely.
// The VALIDATE_ADDRESS_LRU option is disable by default.
if (!IsValidatedAddress(addr)) {
Debug(this, "Performing explicit address validation.");
if (hd.tokenlen == 0 ||
VerifyRetryToken(
env(),
&ocid,
&hd,
addr,
&token_crypto_ctx_,
&token_secret_,
retry_token_expiration_) != 0) {
Debug(this, "A valid retry token was not found. Sending retry.");
SendRetry(version, dcid, scid, addr);
return session;
}
Debug(this, "A valid retry token was found. Continuing.");
SetValidatedAddress(addr);
ocid_ptr = &ocid;
} else {
Debug(this, "Skipping validation for recently validated address.");
}
Debug(this, "A valid retry token was found. Continuing.");
ocid_ptr = &ocid;
}

session =
Expand Down
23 changes: 22 additions & 1 deletion src/node_quic_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "v8.h"
#include "uv.h"

#include <deque>
#include <map>
#include <string>
#include <vector>
Expand All @@ -28,8 +29,18 @@ using v8::Value;

namespace quic {

static constexpr size_t MAX_VALIDATE_ADDRESS_LRU = 10;

typedef enum QuicSocketOptions : uint32_t {
QUICSOCKET_OPTIONS_VALIDATE_ADDRESS = 0x1
// When enabled the QuicSocket will validate the address
// using a RETRY packet to the peer.
QUICSOCKET_OPTIONS_VALIDATE_ADDRESS = 0x1,

// When enabled, and the VALIDATE_ADDRESS option is also
// set, the QuicSocket will use an LRU cache to track
// validated addresses. Address validation will be skipped
// if the address is currently in the cache.
QUICSOCKET_OPTIONS_VALIDATE_ADDRESS_LRU = 0x2,
} QuicSocketOptions;

class QuicSocket : public HandleWrap,
Expand Down Expand Up @@ -149,6 +160,10 @@ class QuicSocket : public HandleWrap,
QuicCID* scid,
const sockaddr* addr);

void SetValidatedAddress(const sockaddr* addr);

bool IsValidatedAddress(const sockaddr* addr);

std::shared_ptr<QuicSession> AcceptInitialPacket(
uint32_t version,
QuicCID* dcid,
Expand Down Expand Up @@ -247,6 +262,12 @@ class QuicSocket : public HandleWrap,
std::unordered_map<const sockaddr*, size_t, SocketAddress::Hash>
addr_counts_;

// The validated_addrs_ vector is used as an LRU cache for
// validated addresses only when the VALIDATE_ADDRESS_LRU
// option is set.
typedef size_t SocketAddressHash;
std::deque<SocketAddressHash> validated_addrs_;

struct socket_stats {
// The timestamp at which the socket was created
uint64_t created_at;
Expand Down
5 changes: 5 additions & 0 deletions src/node_quic_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,11 @@ class SocketAddress {
sockaddr_storage address_;
};

class SocketAddressLRU : public MemoryRetainer {
public:
private:
};

class QuicPath {
public:
QuicPath(
Expand Down