@@ -311,10 +311,18 @@ class Endpoint::UDP::Impl final : public HandleWrap {
311311 SET_SELF_SIZE(Impl)
312312
313313 private:
314+ // Pre-allocated receive buffer. Reused across all datagrams because
315+ // ngtcp2_conn_read_pkt is synchronous — it copies what it needs and
316+ // does not retain a reference to the buffer after returning. This
317+ // eliminates a malloc(64KB)/free(64KB) cycle per received datagram.
318+ static constexpr size_t kRecvBufferSize = 65536; // UV__UDP_DGRAM_MAXSIZE
319+ char recv_buf_[kRecvBufferSize ];
320+
314321 static void OnAlloc (uv_handle_t * handle,
315322 size_t suggested_size,
316323 uv_buf_t * buf) {
317- *buf = From (handle)->env ()->allocate_managed_buffer (suggested_size);
324+ auto * impl = From (handle);
325+ *buf = uv_buf_init (impl->recv_buf_ , kRecvBufferSize );
318326 }
319327
320328 static void OnReceive (uv_udp_t * handle,
@@ -326,26 +334,22 @@ class Endpoint::UDP::Impl final : public HandleWrap {
326334 DCHECK_NOT_NULL (impl);
327335 DCHECK_NOT_NULL (impl->endpoint_ );
328336
329- auto release_buf = [&]() {
330- if (buf->base != nullptr ) impl->env ()->release_managed_buffer (*buf);
331- };
332-
333337 // Nothing to do in these cases. Specifically, if the nread
334338 // is zero or we have received a partial packet, we are just
335- // going to ignore it.
339+ // going to ignore it. No buffer release needed — recv_buf_
340+ // is pre-allocated and reused.
336341 if (nread == 0 || flags & UV_UDP_PARTIAL ) {
337- release_buf ();
338342 return ;
339343 }
340344
341345 if (nread < 0 ) {
342- release_buf ();
343346 impl->endpoint_ ->Destroy (CloseContext::RECEIVE_FAILURE ,
344347 static_cast <int >(nread));
345348 return ;
346349 }
347350
348- impl->endpoint_ ->Receive (uv_buf_init (buf->base , static_cast <size_t >(nread)),
351+ impl->endpoint_ ->Receive (reinterpret_cast <const uint8_t *>(buf->base ),
352+ static_cast <size_t >(nread),
349353 SocketAddress (addr));
350354 }
351355
@@ -1264,24 +1268,25 @@ void Endpoint::CloseGracefully() {
12641268 MaybeDestroy ();
12651269}
12661270
1267- void Endpoint::Receive (const uv_buf_t & buf,
1271+ void Endpoint::Receive (const uint8_t * data,
1272+ size_t len,
12681273 const SocketAddress& remote_address) {
12691274 const auto receive = [&](Session* session,
1270- Store&& store,
1275+ const uint8_t * pkt_data,
1276+ size_t pkt_len,
12711277 const SocketAddress& local_address,
12721278 const SocketAddress& remote_address,
12731279 const CID & dcid,
12741280 const CID & scid) {
12751281 DCHECK_NOT_NULL (session);
12761282 if (session->is_destroyed ()) return ;
1277- size_t len = store.length ();
12781283 // Use ReadPacket (no SendPendingDataScope) so that multiple packets
12791284 // received in the same I/O burst are processed before any responses
12801285 // are generated. The deferred flush via BindingData's uv_check
12811286 // callback calls SendPendingData once per dirty session after all
12821287 // packets in the burst have been read.
1283- if (session->ReadPacket (std::move (store) , local_address, remote_address)) {
1284- STAT_INCREMENT_N (Stats, bytes_received, len );
1288+ if (session->ReadPacket (pkt_data, pkt_len , local_address, remote_address)) {
1289+ STAT_INCREMENT_N (Stats, bytes_received, pkt_len );
12851290 STAT_INCREMENT (Stats, packets_received);
12861291 }
12871292 // Schedule the session for deferred SendPendingData if it hasn't
@@ -1293,7 +1298,9 @@ void Endpoint::Receive(const uv_buf_t& buf,
12931298 }
12941299 };
12951300
1296- const auto accept = [&](const Session::Config& config, Store&& store) {
1301+ const auto accept = [&](const Session::Config& config,
1302+ const uint8_t * pkt_data,
1303+ size_t pkt_len) {
12971304 // One final check. If the endpoint is closed, closing, or is not listening
12981305 // as a server, then we cannot accept the initial packet.
12991306 if (is_closed () || is_closing () || !is_listening ()) return ;
@@ -1323,7 +1330,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13231330 return ;
13241331
13251332 receive (session.get (),
1326- std::move (store),
1333+ pkt_data,
1334+ pkt_len,
13271335 config.local_address ,
13281336 config.remote_address ,
13291337 config.dcid ,
@@ -1333,7 +1341,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13331341 const auto acceptInitialPacket = [&](const uint32_t version,
13341342 const CID & dcid,
13351343 const CID & scid,
1336- Store&& store,
1344+ const uint8_t * pkt_data,
1345+ size_t pkt_len,
13371346 const SocketAddress& local_address,
13381347 const SocketAddress& remote_address) {
13391348 // If we're not listening as a server, do not accept an initial packet.
@@ -1343,8 +1352,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
13431352
13441353 // This is our first condition check... A minimal check to see if ngtcp2 can
13451354 // even recognize this packet as a quic packet.
1346- ngtcp2_vec vec = store;
1347- if (ngtcp2_accept (&hd, vec.base , vec.len ) != NGTCP2_SUCCESS ) {
1355+ if (ngtcp2_accept (&hd, pkt_data, pkt_len) != NGTCP2_SUCCESS ) {
13481356 // Per the ngtcp2 docs, ngtcp2_accept returns 0 if the check was
13491357 // successful, or an error code if it was not. Currently there's only one
13501358 // documented error code (NGTCP2_ERR_INVALID_ARGUMENT) but we'll handle
@@ -1582,7 +1590,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
15821590 }
15831591 }
15841592
1585- accept (config, std::move (store) );
1593+ accept (config, pkt_data, pkt_len );
15861594 };
15871595
15881596 // When a received packet contains a QUIC short header but cannot be matched
@@ -1598,35 +1606,37 @@ void Endpoint::Receive(const uv_buf_t& buf,
15981606 // possible to avoid a DOS vector.
15991607 const auto maybeStatelessReset = [&](const CID & dcid,
16001608 const CID & scid,
1601- Store& store,
1609+ const uint8_t * pkt_data,
1610+ size_t pkt_len,
16021611 const SocketAddress& local_address,
16031612 const SocketAddress& remote_address) {
16041613 // Support for stateless resets can be disabled by the application. If that
16051614 // case, or if the packet is too short to contain a reset token, then we
16061615 // skip the remaining checks.
16071616 if (options_.disable_stateless_reset ||
1608- store. length () < NGTCP2_STATELESS_RESET_TOKENLEN ) {
1617+ pkt_len < NGTCP2_STATELESS_RESET_TOKENLEN ) {
16091618 return false ;
16101619 }
16111620
16121621 // The stateless reset token itself is the *final*
16131622 // NGTCP2_STATELESS_RESET_TOKENLEN bytes in the received packet. If it is a
16141623 // stateless reset then then rest of the bytes in the packet are garbage
16151624 // that we'll ignore.
1616- ngtcp2_vec vec = store;
1617- vec. base += (vec. len - NGTCP2_STATELESS_RESET_TOKENLEN );
1625+ const uint8_t * token_pos =
1626+ pkt_data + (pkt_len - NGTCP2_STATELESS_RESET_TOKENLEN );
16181627
16191628 // If a Session has been associated with the token, then it is a valid
16201629 // stateless reset token. We need to dispatch it to the session to be
16211630 // processed.
16221631 auto * session = session_manager ().FindSessionByStatelessResetToken (
1623- StatelessResetToken (vec. base ));
1632+ StatelessResetToken (token_pos ));
16241633 if (session != nullptr ) {
16251634 // If the session happens to have been destroyed already, we'll
16261635 // just ignore the packet.
16271636 if (!session->is_destroyed ()) [[likely]] {
16281637 receive (session,
1629- std::move (store),
1638+ pkt_data,
1639+ pkt_len,
16301640 local_address,
16311641 remote_address,
16321642 dcid,
@@ -1654,22 +1664,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
16541664 // return;
16551665 // }
16561666
1657- Debug (this , " Received %zu-byte packet from %s" , buf.len , remote_address);
1658-
1659- // The managed buffer here contains the received packet. We do not yet know
1660- // at this point if it is a valid QUIC packet. We need to do some basic
1661- // checks. It is critical at this point that we do as little work as possible
1662- // to avoid a DOS vector.
1663- std::shared_ptr<BackingStore> backing = env ()->release_managed_buffer (buf);
1664- if (!backing) [[unlikely]] {
1665- // At this point something bad happened and we need to treat this as a fatal
1666- // case. There's likely no way to test this specific condition reliably.
1667- return Destroy (CloseContext::RECEIVE_FAILURE , UV_ENOMEM );
1668- }
1669-
1670- Store store (std::move (backing), buf.len , 0 );
1667+ Debug (this , " Received %zu-byte packet from %s" , len, remote_address);
16711668
1672- ngtcp2_vec vec = store;
16731669 ngtcp2_version_cid pversion_cid;
16741670
16751671 // This is our first check to see if the received data can be processed as a
@@ -1678,7 +1674,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
16781674 // valid QUIC header but there is still no guarantee that the packet can be
16791675 // successfully processed.
16801676 switch (ngtcp2_pkt_decode_version_cid (
1681- &pversion_cid, vec. base , vec. len , NGTCP2_MAX_CIDLEN )) {
1677+ &pversion_cid, data, len, NGTCP2_MAX_CIDLEN )) {
16821678 case 0 :
16831679 break ; // Supported version, continue processing.
16841680 case NGTCP2_ERR_VERSION_NEGOTIATION : {
@@ -1756,7 +1752,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17561752 // necessary here. We want to return immediately without committing any
17571753 // further resources.
17581754 if (pversion_cid.version == 0 &&
1759- maybeStatelessReset (dcid, scid, store , addr, remote_address)) {
1755+ maybeStatelessReset (dcid, scid, data, len , addr, remote_address)) {
17601756 Debug (this , " Packet was a stateless reset" );
17611757 return ; // Stateless reset! Don't do any further processing.
17621758 }
@@ -1771,17 +1767,13 @@ void Endpoint::Receive(const uv_buf_t& buf,
17711767 SendStatelessReset (
17721768 PathDescriptor{
17731769 pversion_cid.version , dcid, scid, addr, remote_address},
1774- store. length () );
1770+ len );
17751771 return ;
17761772 }
17771773
17781774 // Process the packet as an initial packet...
1779- return acceptInitialPacket (pversion_cid.version ,
1780- dcid,
1781- scid,
1782- std::move (store),
1783- addr,
1784- remote_address);
1775+ return acceptInitialPacket (
1776+ pversion_cid.version , dcid, scid, data, len, addr, remote_address);
17851777 }
17861778
17871779 if (session->is_destroyed ()) [[unlikely]] {
@@ -1793,7 +1785,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17931785 // If we got here, the dcid matched the scid of a known local session. Yay!
17941786 // The session will take over any further processing of the packet.
17951787 Debug (this , " Dispatching packet to known session" );
1796- receive (session.get (), std::move (store) , addr, remote_address, dcid, scid);
1788+ receive (session.get (), data, len , addr, remote_address, dcid, scid);
17971789
17981790 // It is important to note that the session may have been destroyed during
17991791 // the call to receive(...). If that's the case, the session object still
0 commit comments