%PDF- %PDF-
| Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/quic/ |
| Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/quic/packet.cc |
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#include "packet.h"
#include <base_object-inl.h>
#include <crypto/crypto_util.h>
#include <env-inl.h>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <node_sockaddr-inl.h>
#include <req_wrap-inl.h>
#include <uv.h>
#include <v8.h>
#include <string>
#include "bindingdata.h"
#include "cid.h"
#include "defs.h"
#include "tokens.h"
namespace node {
using v8::FunctionTemplate;
using v8::Local;
using v8::Object;
namespace quic {
namespace {
static constexpr size_t kRandlen = NGTCP2_MIN_STATELESS_RESET_RANDLEN * 5;
static constexpr size_t kMinStatelessResetLen = 41;
static constexpr size_t kMaxFreeList = 100;
} // namespace
std::string PathDescriptor::ToString() const {
DebugIndentScope indent;
auto prefix = indent.Prefix();
std::string res = "{";
res += prefix + "version: " + std::to_string(version);
res += prefix + "dcid: " + dcid.ToString();
res += prefix + "scid: " + scid.ToString();
res += prefix + "local address: " + local_address.ToString();
res += prefix + "remote address: " + remote_address.ToString();
res += indent.Close();
return res;
}
struct Packet::Data final : public MemoryRetainer {
MaybeStackBuffer<uint8_t, kDefaultMaxPacketLength> data_;
// The diagnostic_label_ is used only as a debugging tool when
// logging debug information about the packet. It identifies
// the purpose of the packet.
const std::string diagnostic_label_;
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackFieldWithSize("data", data_.length());
}
SET_MEMORY_INFO_NAME(Data)
SET_SELF_SIZE(Data)
Data(size_t length, std::string_view diagnostic_label)
: diagnostic_label_(diagnostic_label) {
data_.AllocateSufficientStorage(length);
}
size_t length() const { return data_.length(); }
operator uv_buf_t() {
return uv_buf_init(reinterpret_cast<char*>(data_.out()), data_.length());
}
operator ngtcp2_vec() { return ngtcp2_vec{data_.out(), data_.length()}; }
std::string ToString() const {
return diagnostic_label_ + ", " + std::to_string(length());
}
};
const SocketAddress& Packet::destination() const {
return destination_;
}
size_t Packet::length() const {
return data_ ? data_->length() : 0;
}
Packet::operator uv_buf_t() const {
return !data_ ? uv_buf_init(nullptr, 0) : *data_;
}
Packet::operator ngtcp2_vec() const {
return !data_ ? ngtcp2_vec{nullptr, 0} : *data_;
}
void Packet::Truncate(size_t len) {
DCHECK(data_);
DCHECK_LE(len, data_->length());
data_->data_.SetLength(len);
}
Local<FunctionTemplate> Packet::GetConstructorTemplate(Environment* env) {
auto& state = BindingData::Get(env);
Local<FunctionTemplate> tmpl = state.packet_constructor_template();
if (tmpl.IsEmpty()) {
tmpl = NewFunctionTemplate(env->isolate(), IllegalConstructor);
tmpl->Inherit(ReqWrap<uv_udp_send_t>::GetConstructorTemplate(env));
tmpl->InstanceTemplate()->SetInternalFieldCount(
Packet::kInternalFieldCount);
tmpl->SetClassName(state.packetwrap_string());
state.set_packet_constructor_template(tmpl);
}
return tmpl;
}
Packet* Packet::Create(Environment* env,
Listener* listener,
const SocketAddress& destination,
size_t length,
const char* diagnostic_label) {
if (BindingData::Get(env).packet_freelist.empty()) {
Local<Object> obj;
if (UNLIKELY(!GetConstructorTemplate(env)
->InstanceTemplate()
->NewInstance(env->context())
.ToLocal(&obj))) {
return nullptr;
}
return new Packet(
env, listener, obj, destination, length, diagnostic_label);
}
return FromFreeList(env,
std::make_shared<Data>(length, diagnostic_label),
listener,
destination);
}
Packet* Packet::Clone() const {
auto& binding = BindingData::Get(env());
if (binding.packet_freelist.empty()) {
Local<Object> obj;
if (UNLIKELY(!GetConstructorTemplate(env())
->InstanceTemplate()
->NewInstance(env()->context())
.ToLocal(&obj))) {
return nullptr;
}
return new Packet(env(), listener_, obj, destination_, data_);
}
return FromFreeList(env(), data_, listener_, destination_);
}
Packet* Packet::FromFreeList(Environment* env,
std::shared_ptr<Data> data,
Listener* listener,
const SocketAddress& destination) {
auto& binding = BindingData::Get(env);
if (binding.packet_freelist.empty()) return nullptr;
Packet* packet = binding.packet_freelist.back();
binding.packet_freelist.pop_back();
CHECK_NOT_NULL(packet);
CHECK_EQ(env, packet->env());
Debug(packet, "Reusing packet from freelist");
packet->data_ = data;
packet->destination_ = destination;
packet->listener_ = listener;
return packet;
}
Packet::Packet(Environment* env,
Listener* listener,
Local<Object> object,
const SocketAddress& destination,
std::shared_ptr<Data> data)
: ReqWrap<uv_udp_send_t>(env, object, AsyncWrap::PROVIDER_QUIC_PACKET),
listener_(listener),
destination_(destination),
data_(std::move(data)) {
ClearWeak();
Debug(this, "Created a new packet");
}
Packet::Packet(Environment* env,
Listener* listener,
Local<Object> object,
const SocketAddress& destination,
size_t length,
const char* diagnostic_label)
: Packet(env,
listener,
object,
destination,
std::make_shared<Data>(length, diagnostic_label)) {}
void Packet::Done(int status) {
Debug(this, "Packet is done with status %d", status);
if (listener_ != nullptr) {
listener_->PacketDone(status);
}
// As a performance optimization, we add this packet to a freelist
// rather than deleting it but only if the freelist isn't too
// big, we don't want to accumulate these things forever.
auto& binding = BindingData::Get(env());
if (binding.packet_freelist.size() < kMaxFreeList) {
Debug(this, "Returning packet to freelist");
listener_ = nullptr;
data_.reset();
Reset();
binding.packet_freelist.push_back(this);
} else {
delete this;
}
}
std::string Packet::ToString() const {
if (!data_) return "Packet (<empty>)";
return "Packet (" + data_->ToString() + ")";
}
void Packet::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("destination", destination_);
tracker->TrackField("data", data_);
}
Packet* Packet::CreateRetryPacket(Environment* env,
Listener* listener,
const PathDescriptor& path_descriptor,
const TokenSecret& token_secret) {
auto& random = CID::Factory::random();
CID cid = random.Generate();
RetryToken token(path_descriptor.version,
path_descriptor.remote_address,
cid,
path_descriptor.dcid,
token_secret);
if (!token) return nullptr;
const ngtcp2_vec& vec = token;
size_t pktlen =
vec.len + (2 * NGTCP2_MAX_CIDLEN) + path_descriptor.scid.length() + 8;
auto packet =
Create(env, listener, path_descriptor.remote_address, pktlen, "retry");
if (packet == nullptr) return nullptr;
ngtcp2_vec dest = *packet;
ssize_t nwrite = ngtcp2_crypto_write_retry(dest.base,
pktlen,
path_descriptor.version,
path_descriptor.scid,
cid,
path_descriptor.dcid,
vec.base,
vec.len);
if (nwrite <= 0) {
packet->Done(UV_ECANCELED);
return nullptr;
}
packet->Truncate(static_cast<size_t>(nwrite));
return packet;
}
Packet* Packet::CreateConnectionClosePacket(Environment* env,
Listener* listener,
const SocketAddress& destination,
ngtcp2_conn* conn,
const QuicError& error) {
auto packet = Create(
env, listener, destination, kDefaultMaxPacketLength, "connection close");
if (packet == nullptr) return nullptr;
ngtcp2_vec vec = *packet;
ssize_t nwrite = ngtcp2_conn_write_connection_close(
conn, nullptr, nullptr, vec.base, vec.len, error, uv_hrtime());
if (nwrite < 0) {
packet->Done(UV_ECANCELED);
return nullptr;
}
packet->Truncate(static_cast<size_t>(nwrite));
return packet;
}
Packet* Packet::CreateImmediateConnectionClosePacket(
Environment* env,
Listener* listener,
const PathDescriptor& path_descriptor,
const QuicError& reason) {
auto packet = Create(env,
listener,
path_descriptor.remote_address,
kDefaultMaxPacketLength,
"immediate connection close (endpoint)");
if (packet == nullptr) return nullptr;
ngtcp2_vec vec = *packet;
ssize_t nwrite = ngtcp2_crypto_write_connection_close(
vec.base,
vec.len,
path_descriptor.version,
path_descriptor.dcid,
path_descriptor.scid,
reason.code(),
// We do not bother sending a reason string here, even if
// there is one in the QuicError
nullptr,
0);
if (nwrite <= 0) {
packet->Done(UV_ECANCELED);
return nullptr;
}
packet->Truncate(static_cast<size_t>(nwrite));
return packet;
}
Packet* Packet::CreateStatelessResetPacket(
Environment* env,
Listener* listener,
const PathDescriptor& path_descriptor,
const TokenSecret& token_secret,
size_t source_len) {
// Per the QUIC spec, a stateless reset token must be strictly smaller than
// the packet that triggered it. This is one of the mechanisms to prevent
// infinite looping exchange of stateless tokens with the peer. An endpoint
// should never send a stateless reset token smaller than 41 bytes per the
// QUIC spec. The reason is that packets less than 41 bytes may allow an
// observer to reliably determine that it's a stateless reset.
size_t pktlen = source_len - 1;
if (pktlen < kMinStatelessResetLen) return nullptr;
StatelessResetToken token(token_secret, path_descriptor.dcid);
uint8_t random[kRandlen];
CHECK(crypto::CSPRNG(random, kRandlen).is_ok());
auto packet = Create(env,
listener,
path_descriptor.remote_address,
kDefaultMaxPacketLength,
"stateless reset");
if (packet == nullptr) return nullptr;
ngtcp2_vec vec = *packet;
ssize_t nwrite = ngtcp2_pkt_write_stateless_reset(
vec.base, pktlen, token, random, kRandlen);
if (nwrite <= static_cast<ssize_t>(kMinStatelessResetLen)) {
packet->Done(UV_ECANCELED);
return nullptr;
}
packet->Truncate(static_cast<size_t>(nwrite));
return packet;
}
Packet* Packet::CreateVersionNegotiationPacket(
Environment* env,
Listener* listener,
const PathDescriptor& path_descriptor) {
const auto generateReservedVersion = [&] {
socklen_t addrlen = path_descriptor.remote_address.length();
uint32_t h = 0x811C9DC5u;
uint32_t ver = htonl(path_descriptor.version);
const uint8_t* p = path_descriptor.remote_address.raw();
const uint8_t* ep = p + addrlen;
for (; p != ep; ++p) {
h ^= *p;
h *= 0x01000193u;
}
p = reinterpret_cast<const uint8_t*>(&ver);
ep = p + sizeof(path_descriptor.version);
for (; p != ep; ++p) {
h ^= *p;
h *= 0x01000193u;
}
h &= 0xf0f0f0f0u;
h |= NGTCP2_RESERVED_VERSION_MASK;
return h;
};
uint32_t sv[3] = {
generateReservedVersion(), NGTCP2_PROTO_VER_MIN, NGTCP2_PROTO_VER_MAX};
size_t pktlen = path_descriptor.dcid.length() +
path_descriptor.scid.length() + (sizeof(sv)) + 7;
auto packet = Create(env,
listener,
path_descriptor.remote_address,
kDefaultMaxPacketLength,
"version negotiation");
if (packet == nullptr) return nullptr;
ngtcp2_vec vec = *packet;
ssize_t nwrite =
ngtcp2_pkt_write_version_negotiation(vec.base,
pktlen,
0,
path_descriptor.dcid,
path_descriptor.dcid.length(),
path_descriptor.scid,
path_descriptor.scid.length(),
sv,
arraysize(sv));
if (nwrite <= 0) {
packet->Done(UV_ECANCELED);
return nullptr;
}
packet->Truncate(static_cast<size_t>(nwrite));
return packet;
}
} // namespace quic
} // namespace node
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC