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
18 namespace node {
19
20 using v8::FunctionTemplate;
21 using v8::Local;
22 using v8::Object;
23
24 namespace quic {
25
26 namespace {
27 static constexpr size_t kRandlen = NGTCP2_MIN_STATELESS_RESET_RANDLEN * 5;
28 static constexpr size_t kMinStatelessResetLen = 41;
29 static constexpr size_t kMaxFreeList = 100;
30 } // namespace
31
32 struct 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
MemoryInfonode::quic::Packet::Data40 void MemoryInfo(MemoryTracker* tracker) const override {
41 tracker->TrackFieldWithSize("data", data_.length());
42 }
43 SET_MEMORY_INFO_NAME(Data)
SET_SELF_SIZEnode::quic::Packet::Data44 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
lengthnode::quic::Packet::Data51 size_t length() const { return data_.length(); }
operator uv_buf_tnode::quic::Packet::Data52 operator uv_buf_t() {
53 return uv_buf_init(reinterpret_cast<char*>(data_.out()), data_.length());
54 }
operator ngtcp2_vecnode::quic::Packet::Data55 operator ngtcp2_vec() { return ngtcp2_vec{data_.out(), data_.length()}; }
56
ToStringnode::quic::Packet::Data57 std::string ToString() const {
58 return diagnostic_label_ + ", " + std::to_string(length());
59 }
60 };
61
destination() const62 const SocketAddress& Packet::destination() const {
63 return destination_;
64 }
65
is_sending() const66 bool Packet::is_sending() const {
67 return !!handle_;
68 }
69
length() const70 size_t Packet::length() const {
71 return data_ ? data_->length() : 0;
72 }
73
operator uv_buf_t() const74 Packet::operator uv_buf_t() const {
75 return !data_ ? uv_buf_init(nullptr, 0) : *data_;
76 }
77
operator ngtcp2_vec() const78 Packet::operator ngtcp2_vec() const {
79 return !data_ ? ngtcp2_vec{nullptr, 0} : *data_;
80 }
81
Truncate(size_t len)82 void Packet::Truncate(size_t len) {
83 DCHECK(data_);
84 DCHECK_LE(len, data_->length());
85 data_->data_.SetLength(len);
86 }
87
GetConstructorTemplate(Environment * env)88 Local<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
Create(Environment * env,Listener * listener,const SocketAddress & destination,size_t length,const char * diagnostic_label)102 BaseObjectPtr<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
Clone() const127 BaseObjectPtr<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
FromFreeList(Environment * env,std::shared_ptr<Data> data,Listener * listener,const SocketAddress & destination)144 BaseObjectPtr<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
Packet(Environment * env,Listener * listener,Local<Object> object,const SocketAddress & destination,std::shared_ptr<Data> data)159 Packet::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
Packet(Environment * env,Listener * listener,Local<Object> object,const SocketAddress & destination,size_t length,const char * diagnostic_label)169 Packet::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
Send(uv_udp_t * handle,BaseObjectPtr<BaseObject> ref)181 int 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
Done(int status)201 void 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
ToString() const220 std::string Packet::ToString() const {
221 if (!data_) return "Packet (<empty>)";
222 return "Packet (" + data_->ToString() + ")";
223 }
224
MemoryInfo(MemoryTracker * tracker) const225 void Packet::MemoryInfo(MemoryTracker* tracker) const {
226 tracker->TrackField("destination", destination_);
227 tracker->TrackField("data", data_);
228 tracker->TrackField("handle", handle_);
229 }
230
CreateRetryPacket(Environment * env,Listener * listener,const PathDescriptor & path_descriptor,const TokenSecret & token_secret)231 BaseObjectPtr<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
CreateConnectionClosePacket(Environment * env,Listener * listener,const SocketAddress & destination,ngtcp2_conn * conn,const QuicError & error)269 BaseObjectPtr<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
CreateImmediateConnectionClosePacket(Environment * env,Listener * listener,const SocketAddress & destination,const PathDescriptor & path_descriptor,const QuicError & reason)286 BaseObjectPtr<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
CreateStatelessResetPacket(Environment * env,Listener * listener,const PathDescriptor & path_descriptor,const TokenSecret & token_secret,size_t source_len)314 BaseObjectPtr<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
CreateVersionNegotiationPacket(Environment * env,Listener * listener,const PathDescriptor & path_descriptor)350 BaseObjectPtr<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