@@ -174,6 +174,18 @@ Http2Options::Http2Options(Environment* env) {
174174 if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS)) {
175175 SetMaxOutstandingSettings (buffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS]);
176176 }
177+
178+ // The HTTP2 specification places no limits on the amount of memory
179+ // that a session can consume. In order to prevent abuse, we place a
180+ // cap on the amount of memory a session can consume at any given time.
181+ // this is a credit based system. Existing streams may cause the limit
182+ // to be temporarily exceeded but once over the limit, new streams cannot
183+ // created.
184+ // Important: The maxSessionMemory option in javascript is expressed in
185+ // terms of MB increments (i.e. the value 1 == 1 MB)
186+ if (flags & (1 << IDX_OPTIONS_MAX_SESSION_MEMORY)) {
187+ SetMaxSessionMemory (buffer[IDX_OPTIONS_MAX_SESSION_MEMORY] * 1e6 );
188+ }
177189}
178190
179191void Http2Session::Http2Settings::Init () {
@@ -482,11 +494,13 @@ Http2Session::Http2Session(Environment* env,
482494 // Capture the configuration options for this session
483495 Http2Options opts (env);
484496
485- int32_t maxHeaderPairs = opts.GetMaxHeaderPairs ();
497+ max_session_memory_ = opts.GetMaxSessionMemory ();
498+
499+ uint32_t maxHeaderPairs = opts.GetMaxHeaderPairs ();
486500 max_header_pairs_ =
487501 type == NGHTTP2_SESSION_SERVER
488- ? std::max (maxHeaderPairs, 4 ) // minimum # of request headers
489- : std::max (maxHeaderPairs, 1 ); // minimum # of response headers
502+ ? std::max (maxHeaderPairs, 4U ) // minimum # of request headers
503+ : std::max (maxHeaderPairs, 1U ); // minimum # of response headers
490504
491505 max_outstanding_pings_ = opts.GetMaxOutstandingPings ();
492506 max_outstanding_settings_ = opts.GetMaxOutstandingSettings ();
@@ -673,18 +687,21 @@ inline bool Http2Session::CanAddStream() {
673687 size_t maxSize =
674688 std::min (streams_.max_size (), static_cast <size_t >(maxConcurrentStreams));
675689 // We can add a new stream so long as we are less than the current
676- // maximum on concurrent streams
677- return streams_.size () < maxSize;
690+ // maximum on concurrent streams and there's enough available memory
691+ return streams_.size () < maxSize &&
692+ IsAvailableSessionMemory (sizeof (Http2Stream));
678693}
679694
680695inline void Http2Session::AddStream (Http2Stream* stream) {
681696 CHECK_GE (++statistics_.stream_count , 0 );
682697 streams_[stream->id ()] = stream;
698+ IncrementCurrentSessionMemory (stream->self_size ());
683699}
684700
685701
686- inline void Http2Session::RemoveStream (int32_t id) {
687- streams_.erase (id);
702+ inline void Http2Session::RemoveStream (Http2Stream* stream) {
703+ streams_.erase (stream->id ());
704+ DecrementCurrentSessionMemory (stream->self_size ());
688705}
689706
690707// Used as one of the Padding Strategy functions. Will attempt to ensure
@@ -1678,7 +1695,7 @@ Http2Stream::Http2Stream(
16781695
16791696Http2Stream::~Http2Stream () {
16801697 if (session_ != nullptr ) {
1681- session_->RemoveStream (id_ );
1698+ session_->RemoveStream (this );
16821699 session_ = nullptr ;
16831700 }
16841701
@@ -2008,7 +2025,7 @@ inline int Http2Stream::DoWrite(WriteWrap* req_wrap,
20082025 i == nbufs - 1 ? req_wrap : nullptr ,
20092026 bufs[i]
20102027 });
2011- available_outbound_length_ += bufs[i].len ;
2028+ IncrementAvailableOutboundLength ( bufs[i].len ) ;
20122029 }
20132030 CHECK_NE (nghttp2_session_resume_data (**session_, id_), NGHTTP2_ERR_NOMEM);
20142031 return 0 ;
@@ -2030,7 +2047,10 @@ inline bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
20302047 if (this ->statistics_ .first_header == 0 )
20312048 this ->statistics_ .first_header = uv_hrtime ();
20322049 size_t length = GetBufferLength (name) + GetBufferLength (value) + 32 ;
2033- if (current_headers_.size () == max_header_pairs_ ||
2050+ // A header can only be added if we have not exceeded the maximum number
2051+ // of headers and the session has memory available for it.
2052+ if (!session_->IsAvailableSessionMemory (length) ||
2053+ current_headers_.size () == max_header_pairs_ ||
20342054 current_headers_length_ + length > max_header_length_) {
20352055 return false ;
20362056 }
@@ -2174,7 +2194,7 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
21742194 // Just return the length, let Http2Session::OnSendData take care of
21752195 // actually taking the buffers out of the queue.
21762196 *flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2177- stream->available_outbound_length_ -= amount;
2197+ stream->DecrementAvailableOutboundLength ( amount) ;
21782198 }
21792199 }
21802200
@@ -2197,6 +2217,15 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
21972217 return amount;
21982218}
21992219
2220+ inline void Http2Stream::IncrementAvailableOutboundLength (size_t amount) {
2221+ available_outbound_length_ += amount;
2222+ session_->IncrementCurrentSessionMemory (amount);
2223+ }
2224+
2225+ inline void Http2Stream::DecrementAvailableOutboundLength (size_t amount) {
2226+ available_outbound_length_ -= amount;
2227+ session_->DecrementCurrentSessionMemory (amount);
2228+ }
22002229
22012230
22022231// Implementation of the JavaScript API
@@ -2690,6 +2719,7 @@ Http2Session::Http2Ping* Http2Session::PopPing() {
26902719 if (!outstanding_pings_.empty ()) {
26912720 ping = outstanding_pings_.front ();
26922721 outstanding_pings_.pop ();
2722+ DecrementCurrentSessionMemory (ping->self_size ());
26932723 }
26942724 return ping;
26952725}
@@ -2698,6 +2728,7 @@ bool Http2Session::AddPing(Http2Session::Http2Ping* ping) {
26982728 if (outstanding_pings_.size () == max_outstanding_pings_)
26992729 return false ;
27002730 outstanding_pings_.push (ping);
2731+ IncrementCurrentSessionMemory (ping->self_size ());
27012732 return true ;
27022733}
27032734
@@ -2706,6 +2737,7 @@ Http2Session::Http2Settings* Http2Session::PopSettings() {
27062737 if (!outstanding_settings_.empty ()) {
27072738 settings = outstanding_settings_.front ();
27082739 outstanding_settings_.pop ();
2740+ DecrementCurrentSessionMemory (settings->self_size ());
27092741 }
27102742 return settings;
27112743}
@@ -2714,6 +2746,7 @@ bool Http2Session::AddSettings(Http2Session::Http2Settings* settings) {
27142746 if (outstanding_settings_.size () == max_outstanding_settings_)
27152747 return false ;
27162748 outstanding_settings_.push (settings);
2749+ IncrementCurrentSessionMemory (settings->self_size ());
27172750 return true ;
27182751}
27192752
0 commit comments