@@ -174,6 +174,18 @@ Http2Options::Http2Options(Environment* env) { |
174 | 174 | if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS)) { |
175 | 175 | SetMaxOutstandingSettings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS]); |
176 | 176 | } |
| 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 | + } |
177 | 189 | } |
178 | 190 |
|
179 | 191 | void Http2Session::Http2Settings::Init() { |
@@ -482,11 +494,13 @@ Http2Session::Http2Session(Environment* env, |
482 | 494 | // Capture the configuration options for this session |
483 | 495 | Http2Options opts(env); |
484 | 496 |
|
485 | | - int32_t maxHeaderPairs = opts.GetMaxHeaderPairs(); |
| 497 | + max_session_memory_ = opts.GetMaxSessionMemory(); |
| 498 | + |
| 499 | + uint32_t maxHeaderPairs = opts.GetMaxHeaderPairs(); |
486 | 500 | max_header_pairs_ = |
487 | 501 | 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 |
490 | 504 |
|
491 | 505 | max_outstanding_pings_ = opts.GetMaxOutstandingPings(); |
492 | 506 | max_outstanding_settings_ = opts.GetMaxOutstandingSettings(); |
@@ -673,18 +687,21 @@ inline bool Http2Session::CanAddStream() { |
673 | 687 | size_t maxSize = |
674 | 688 | std::min(streams_.max_size(), static_cast<size_t>(maxConcurrentStreams)); |
675 | 689 | // 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)); |
678 | 693 | } |
679 | 694 |
|
680 | 695 | inline void Http2Session::AddStream(Http2Stream* stream) { |
681 | 696 | CHECK_GE(++statistics_.stream_count, 0); |
682 | 697 | streams_[stream->id()] = stream; |
| 698 | + IncrementCurrentSessionMemory(stream->self_size()); |
683 | 699 | } |
684 | 700 |
|
685 | 701 |
|
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()); |
688 | 705 | } |
689 | 706 |
|
690 | 707 | // Used as one of the Padding Strategy functions. Will attempt to ensure |
@@ -1678,7 +1695,7 @@ Http2Stream::Http2Stream( |
1678 | 1695 |
|
1679 | 1696 | Http2Stream::~Http2Stream() { |
1680 | 1697 | if (session_ != nullptr) { |
1681 | | - session_->RemoveStream(id_); |
| 1698 | + session_->RemoveStream(this); |
1682 | 1699 | session_ = nullptr; |
1683 | 1700 | } |
1684 | 1701 |
|
@@ -2008,7 +2025,7 @@ inline int Http2Stream::DoWrite(WriteWrap* req_wrap, |
2008 | 2025 | i == nbufs - 1 ? req_wrap : nullptr, |
2009 | 2026 | bufs[i] |
2010 | 2027 | }); |
2011 | | - available_outbound_length_ += bufs[i].len; |
| 2028 | + IncrementAvailableOutboundLength(bufs[i].len); |
2012 | 2029 | } |
2013 | 2030 | CHECK_NE(nghttp2_session_resume_data(**session_, id_), NGHTTP2_ERR_NOMEM); |
2014 | 2031 | return 0; |
@@ -2030,7 +2047,10 @@ inline bool Http2Stream::AddHeader(nghttp2_rcbuf* name, |
2030 | 2047 | if (this->statistics_.first_header == 0) |
2031 | 2048 | this->statistics_.first_header = uv_hrtime(); |
2032 | 2049 | 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_ || |
2034 | 2054 | current_headers_length_ + length > max_header_length_) { |
2035 | 2055 | return false; |
2036 | 2056 | } |
@@ -2174,7 +2194,7 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle, |
2174 | 2194 | // Just return the length, let Http2Session::OnSendData take care of |
2175 | 2195 | // actually taking the buffers out of the queue. |
2176 | 2196 | *flags |= NGHTTP2_DATA_FLAG_NO_COPY; |
2177 | | - stream->available_outbound_length_ -= amount; |
| 2197 | + stream->DecrementAvailableOutboundLength(amount); |
2178 | 2198 | } |
2179 | 2199 | } |
2180 | 2200 |
|
@@ -2197,6 +2217,15 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle, |
2197 | 2217 | return amount; |
2198 | 2218 | } |
2199 | 2219 |
|
| 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 | +} |
2200 | 2229 |
|
2201 | 2230 |
|
2202 | 2231 | // Implementation of the JavaScript API |
@@ -2690,6 +2719,7 @@ Http2Session::Http2Ping* Http2Session::PopPing() { |
2690 | 2719 | if (!outstanding_pings_.empty()) { |
2691 | 2720 | ping = outstanding_pings_.front(); |
2692 | 2721 | outstanding_pings_.pop(); |
| 2722 | + DecrementCurrentSessionMemory(ping->self_size()); |
2693 | 2723 | } |
2694 | 2724 | return ping; |
2695 | 2725 | } |
@@ -2698,6 +2728,7 @@ bool Http2Session::AddPing(Http2Session::Http2Ping* ping) { |
2698 | 2728 | if (outstanding_pings_.size() == max_outstanding_pings_) |
2699 | 2729 | return false; |
2700 | 2730 | outstanding_pings_.push(ping); |
| 2731 | + IncrementCurrentSessionMemory(ping->self_size()); |
2701 | 2732 | return true; |
2702 | 2733 | } |
2703 | 2734 |
|
@@ -2706,6 +2737,7 @@ Http2Session::Http2Settings* Http2Session::PopSettings() { |
2706 | 2737 | if (!outstanding_settings_.empty()) { |
2707 | 2738 | settings = outstanding_settings_.front(); |
2708 | 2739 | outstanding_settings_.pop(); |
| 2740 | + DecrementCurrentSessionMemory(settings->self_size()); |
2709 | 2741 | } |
2710 | 2742 | return settings; |
2711 | 2743 | } |
@@ -2714,6 +2746,7 @@ bool Http2Session::AddSettings(Http2Session::Http2Settings* settings) { |
2714 | 2746 | if (outstanding_settings_.size() == max_outstanding_settings_) |
2715 | 2747 | return false; |
2716 | 2748 | outstanding_settings_.push(settings); |
| 2749 | + IncrementCurrentSessionMemory(settings->self_size()); |
2717 | 2750 | return true; |
2718 | 2751 | } |
2719 | 2752 |
|
|