xref: /third_party/node/src/quic/packet.cc (revision 1cb0ef41)
1#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
2
3#include "packet.h"
4#include <base_object-inl.h>
5#include <crypto/crypto_util.h>
6#include <env-inl.h>
7#include <ngtcp2/ngtcp2.h>
8#include <ngtcp2/ngtcp2_crypto.h>
9#include <node_sockaddr-inl.h>
10#include <req_wrap-inl.h>
11#include <uv.h>
12#include <v8.h>
13#include <string>
14#include "bindingdata.h"
15#include "cid.h"
16#include "tokens.h"
17
18namespace node {
19
20using v8::FunctionTemplate;
21using v8::Local;
22using v8::Object;
23
24namespace quic {
25
26namespace {
27static constexpr size_t kRandlen = NGTCP2_MIN_STATELESS_RESET_RANDLEN * 5;
28static constexpr size_t kMinStatelessResetLen = 41;
29static constexpr size_t kMaxFreeList = 100;
30}  // namespace
31
32struct Packet::Data final : public MemoryRetainer {
33  MaybeStackBuffer<uint8_t, kDefaultMaxPacketLength> data_;
34
35  // The diagnostic_label_ is used only as a debugging tool when
36  // logging debug information about the packet. It identifies
37  // the purpose of the packet.
38  const std::string diagnostic_label_;
39
40  void MemoryInfo(MemoryTracker* tracker) const override {
41    tracker->TrackFieldWithSize("data", data_.length());
42  }
43  SET_MEMORY_INFO_NAME(Data)
44  SET_SELF_SIZE(Data)
45
46  Data(size_t length, std::string_view diagnostic_label)
47      : diagnostic_label_(diagnostic_label) {
48    data_.AllocateSufficientStorage(length);
49  }
50
51  size_t length() const { return data_.length(); }
52  operator uv_buf_t() {
53    return uv_buf_init(reinterpret_cast<char*>(data_.out()), data_.length());
54  }
55  operator ngtcp2_vec() { return ngtcp2_vec{data_.out(), data_.length()}; }
56
57  std::string ToString() const {
58    return diagnostic_label_ + ", " + std::to_string(length());
59  }
60};
61
62const SocketAddress& Packet::destination() const {
63  return destination_;
64}
65
66bool Packet::is_sending() const {
67  return !!handle_;
68}
69
70size_t Packet::length() const {
71  return data_ ? data_->length() : 0;
72}
73
74Packet::operator uv_buf_t() const {
75  return !data_ ? uv_buf_init(nullptr, 0) : *data_;
76}
77
78Packet::operator ngtcp2_vec() const {
79  return !data_ ? ngtcp2_vec{nullptr, 0} : *data_;
80}
81
82void Packet::Truncate(size_t len) {
83  DCHECK(data_);
84  DCHECK_LE(len, data_->length());
85  data_->data_.SetLength(len);
86}
87
88Local<FunctionTemplate> Packet::GetConstructorTemplate(Environment* env) {
89  auto& state = BindingData::Get(env);
90  Local<FunctionTemplate> tmpl = state.packet_constructor_template();
91  if (tmpl.IsEmpty()) {
92    tmpl = NewFunctionTemplate(env->isolate(), IllegalConstructor);
93    tmpl->Inherit(ReqWrap<uv_udp_send_t>::GetConstructorTemplate(env));
94    tmpl->InstanceTemplate()->SetInternalFieldCount(
95        Packet::kInternalFieldCount);
96    tmpl->SetClassName(state.packetwrap_string());
97    state.set_packet_constructor_template(tmpl);
98  }
99  return tmpl;
100}
101
102BaseObjectPtr<Packet> Packet::Create(Environment* env,
103                                     Listener* listener,
104                                     const SocketAddress& destination,
105                                     size_t length,
106                                     const char* diagnostic_label) {
107  auto& binding = BindingData::Get(env);
108  if (binding.packet_freelist.empty()) {
109    Local<Object> obj;
110    if (UNLIKELY(!GetConstructorTemplate(env)
111                      ->InstanceTemplate()
112                      ->NewInstance(env->context())
113                      .ToLocal(&obj))) {
114      return BaseObjectPtr<Packet>();
115    }
116
117    return MakeBaseObject<Packet>(
118        env, listener, obj, destination, length, diagnostic_label);
119  }
120
121  return FromFreeList(env,
122                      std::make_shared<Data>(length, diagnostic_label),
123                      listener,
124                      destination);
125}
126
127BaseObjectPtr<Packet> Packet::Clone() const {
128  auto& binding = BindingData::Get(env());
129  if (binding.packet_freelist.empty()) {
130    Local<Object> obj;
131    if (UNLIKELY(!GetConstructorTemplate(env())
132                      ->InstanceTemplate()
133                      ->NewInstance(env()->context())
134                      .ToLocal(&obj))) {
135      return BaseObjectPtr<Packet>();
136    }
137
138    return MakeBaseObject<Packet>(env(), listener_, obj, destination_, data_);
139  }
140
141  return FromFreeList(env(), data_, listener_, destination_);
142}
143
144BaseObjectPtr<Packet> Packet::FromFreeList(Environment* env,
145                                           std::shared_ptr<Data> data,
146                                           Listener* listener,
147                                           const SocketAddress& destination) {
148  auto& binding = BindingData::Get(env);
149  auto obj = binding.packet_freelist.back();
150  binding.packet_freelist.pop_back();
151  DCHECK_EQ(env, obj->env());
152  auto packet = static_cast<Packet*>(obj.get());
153  packet->data_ = std::move(data);
154  packet->destination_ = destination;
155  packet->listener_ = listener;
156  return BaseObjectPtr<Packet>(packet);
157}
158
159Packet::Packet(Environment* env,
160               Listener* listener,
161               Local<Object> object,
162               const SocketAddress& destination,
163               std::shared_ptr<Data> data)
164    : ReqWrap<uv_udp_send_t>(env, object, AsyncWrap::PROVIDER_QUIC_PACKET),
165      listener_(listener),
166      destination_(destination),
167      data_(std::move(data)) {}
168
169Packet::Packet(Environment* env,
170               Listener* listener,
171               Local<Object> object,
172               const SocketAddress& destination,
173               size_t length,
174               const char* diagnostic_label)
175    : Packet(env,
176             listener,
177             object,
178             destination,
179             std::make_shared<Data>(length, diagnostic_label)) {}
180
181int Packet::Send(uv_udp_t* handle, BaseObjectPtr<BaseObject> ref) {
182  if (is_sending()) return UV_EALREADY;
183  if (data_ == nullptr) return UV_EINVAL;
184  DCHECK(!is_sending());
185  handle_ = std::move(ref);
186  uv_buf_t buf = *this;
187  return Dispatch(
188      uv_udp_send,
189      handle,
190      &buf,
191      1,
192      destination().data(),
193      uv_udp_send_cb{[](uv_udp_send_t* req, int status) {
194        auto ptr = static_cast<Packet*>(ReqWrap<uv_udp_send_t>::from_req(req));
195        ptr->Done(status);
196        // Do not try accessing ptr after this. We don't know if it
197        // was freelisted or destroyed. Either way, done means done.
198      }});
199}
200
201void Packet::Done(int status) {
202  DCHECK_NOT_NULL(listener_);
203  listener_->PacketDone(status);
204  handle_.reset();
205  data_.reset();
206  listener_ = nullptr;
207  Reset();
208
209  // As a performance optimization, we add this packet to a freelist
210  // rather than deleting it but only if the freelist isn't too
211  // big, we don't want to accumulate these things forever.
212  auto& binding = BindingData::Get(env());
213  if (binding.packet_freelist.size() < kMaxFreeList) {
214    binding.packet_freelist.emplace_back(this);
215  } else {
216    delete this;
217  }
218}
219
220std::string Packet::ToString() const {
221  if (!data_) return "Packet (<empty>)";
222  return "Packet (" + data_->ToString() + ")";
223}
224
225void Packet::MemoryInfo(MemoryTracker* tracker) const {
226  tracker->TrackField("destination", destination_);
227  tracker->TrackField("data", data_);
228  tracker->TrackField("handle", handle_);
229}
230
231BaseObjectPtr<Packet> Packet::CreateRetryPacket(
232    Environment* env,
233    Listener* listener,
234    const PathDescriptor& path_descriptor,
235    const TokenSecret& token_secret) {
236  auto& random = CID::Factory::random();
237  CID cid = random.Generate();
238  RetryToken token(path_descriptor.version,
239                   path_descriptor.remote_address,
240                   cid,
241                   path_descriptor.dcid,
242                   token_secret);
243  if (!token) return BaseObjectPtr<Packet>();
244
245  const ngtcp2_vec& vec = token;
246
247  size_t pktlen =
248      vec.len + (2 * NGTCP2_MAX_CIDLEN) + path_descriptor.scid.length() + 8;
249
250  auto packet =
251      Create(env, listener, path_descriptor.remote_address, pktlen, "retry");
252  if (!packet) return BaseObjectPtr<Packet>();
253
254  ngtcp2_vec dest = *packet;
255
256  ssize_t nwrite = ngtcp2_crypto_write_retry(dest.base,
257                                             pktlen,
258                                             path_descriptor.version,
259                                             path_descriptor.scid,
260                                             cid,
261                                             path_descriptor.dcid,
262                                             vec.base,
263                                             vec.len);
264  if (nwrite <= 0) return BaseObjectPtr<Packet>();
265  packet->Truncate(static_cast<size_t>(nwrite));
266  return packet;
267}
268
269BaseObjectPtr<Packet> Packet::CreateConnectionClosePacket(
270    Environment* env,
271    Listener* listener,
272    const SocketAddress& destination,
273    ngtcp2_conn* conn,
274    const QuicError& error) {
275  auto packet = Packet::Create(
276      env, listener, destination, kDefaultMaxPacketLength, "connection close");
277  ngtcp2_vec vec = *packet;
278
279  ssize_t nwrite = ngtcp2_conn_write_connection_close(
280      conn, nullptr, nullptr, vec.base, vec.len, error, uv_hrtime());
281  if (nwrite < 0) return BaseObjectPtr<Packet>();
282  packet->Truncate(static_cast<size_t>(nwrite));
283  return packet;
284}
285
286BaseObjectPtr<Packet> Packet::CreateImmediateConnectionClosePacket(
287    Environment* env,
288    Listener* listener,
289    const SocketAddress& destination,
290    const PathDescriptor& path_descriptor,
291    const QuicError& reason) {
292  auto packet = Packet::Create(env,
293                               listener,
294                               path_descriptor.remote_address,
295                               kDefaultMaxPacketLength,
296                               "immediate connection close (endpoint)");
297  ngtcp2_vec vec = *packet;
298  ssize_t nwrite = ngtcp2_crypto_write_connection_close(
299      vec.base,
300      vec.len,
301      path_descriptor.version,
302      path_descriptor.dcid,
303      path_descriptor.scid,
304      reason.code(),
305      // We do not bother sending a reason string here, even if
306      // there is one in the QuicError
307      nullptr,
308      0);
309  if (nwrite <= 0) return BaseObjectPtr<Packet>();
310  packet->Truncate(static_cast<size_t>(nwrite));
311  return packet;
312}
313
314BaseObjectPtr<Packet> Packet::CreateStatelessResetPacket(
315    Environment* env,
316    Listener* listener,
317    const PathDescriptor& path_descriptor,
318    const TokenSecret& token_secret,
319    size_t source_len) {
320  // Per the QUIC spec, a stateless reset token must be strictly smaller than
321  // the packet that triggered it. This is one of the mechanisms to prevent
322  // infinite looping exchange of stateless tokens with the peer. An endpoint
323  // should never send a stateless reset token smaller than 41 bytes per the
324  // QUIC spec. The reason is that packets less than 41 bytes may allow an
325  // observer to reliably determine that it's a stateless reset.
326  size_t pktlen = source_len - 1;
327  if (pktlen < kMinStatelessResetLen) return BaseObjectPtr<Packet>();
328
329  StatelessResetToken token(token_secret, path_descriptor.dcid);
330  uint8_t random[kRandlen];
331  CHECK(crypto::CSPRNG(random, kRandlen).is_ok());
332
333  auto packet = Packet::Create(env,
334                               listener,
335                               path_descriptor.remote_address,
336                               kDefaultMaxPacketLength,
337                               "stateless reset");
338  ngtcp2_vec vec = *packet;
339
340  ssize_t nwrite = ngtcp2_pkt_write_stateless_reset(
341      vec.base, pktlen, token, random, kRandlen);
342  if (nwrite <= static_cast<ssize_t>(kMinStatelessResetLen)) {
343    return BaseObjectPtr<Packet>();
344  }
345
346  packet->Truncate(static_cast<size_t>(nwrite));
347  return packet;
348}
349
350BaseObjectPtr<Packet> Packet::CreateVersionNegotiationPacket(
351    Environment* env,
352    Listener* listener,
353    const PathDescriptor& path_descriptor) {
354  const auto generateReservedVersion = [&] {
355    socklen_t addrlen = path_descriptor.remote_address.length();
356    uint32_t h = 0x811C9DC5u;
357    uint32_t ver = htonl(path_descriptor.version);
358    const uint8_t* p = path_descriptor.remote_address.raw();
359    const uint8_t* ep = p + addrlen;
360    for (; p != ep; ++p) {
361      h ^= *p;
362      h *= 0x01000193u;
363    }
364    p = reinterpret_cast<const uint8_t*>(&ver);
365    ep = p + sizeof(path_descriptor.version);
366    for (; p != ep; ++p) {
367      h ^= *p;
368      h *= 0x01000193u;
369    }
370    h &= 0xf0f0f0f0u;
371    h |= NGTCP2_RESERVED_VERSION_MASK;
372    return h;
373  };
374
375  uint32_t sv[3] = {
376      generateReservedVersion(), NGTCP2_PROTO_VER_MIN, NGTCP2_PROTO_VER_MAX};
377
378  size_t pktlen = path_descriptor.dcid.length() +
379                  path_descriptor.scid.length() + (sizeof(sv)) + 7;
380
381  auto packet = Packet::Create(env,
382                               listener,
383                               path_descriptor.remote_address,
384                               kDefaultMaxPacketLength,
385                               "version negotiation");
386  ngtcp2_vec vec = *packet;
387
388  ssize_t nwrite =
389      ngtcp2_pkt_write_version_negotiation(vec.base,
390                                           pktlen,
391                                           0,
392                                           path_descriptor.dcid,
393                                           path_descriptor.dcid.length(),
394                                           path_descriptor.scid,
395                                           path_descriptor.scid.length(),
396                                           sv,
397                                           arraysize(sv));
398  if (nwrite <= 0) return BaseObjectPtr<Packet>();
399  packet->Truncate(static_cast<size_t>(nwrite));
400  return packet;
401}
402
403}  // namespace quic
404}  // namespace node
405
406#endif  // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
407