summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
Diffstat (limited to 'src/network')
-rw-r--r--src/network/access/http2/http2frames.cpp3
-rw-r--r--src/network/access/http2/http2protocol.cpp3
-rw-r--r--src/network/access/qhttp2connection.cpp118
-rw-r--r--src/network/access/qhttp2connection_p.h6
-rw-r--r--src/network/access/qhttpnetworkheader.cpp2
-rw-r--r--src/network/access/qnetworkrequest.cpp1
6 files changed, 98 insertions, 35 deletions
diff --git a/src/network/access/http2/http2frames.cpp b/src/network/access/http2/http2frames.cpp
index 3b52204c7d3..e6a3474d7b0 100644
--- a/src/network/access/http2/http2frames.cpp
+++ b/src/network/access/http2/http2frames.cpp
@@ -34,7 +34,8 @@ FrameType Frame::type() const
quint32 Frame::streamID() const
{
Q_ASSERT(buffer.size() >= frameHeaderSize);
- return qFromBigEndian<quint32>(&buffer[5]);
+ // RFC 9113, 4.1: 31-bit Stream ID; lastValidStreamID(0x7FFFFFFF) masks out the reserved MSB
+ return qFromBigEndian<quint32>(&buffer[5]) & lastValidStreamID;
}
FrameFlags Frame::flags() const
diff --git a/src/network/access/http2/http2protocol.cpp b/src/network/access/http2/http2protocol.cpp
index 050beacb31c..314be07f952 100644
--- a/src/network/access/http2/http2protocol.cpp
+++ b/src/network/access/http2/http2protocol.cpp
@@ -188,8 +188,9 @@ bool is_protocol_upgraded(const QHttpNetworkReply &reply)
if (reply.statusCode() != 101)
return false;
+ const auto values = reply.header().values(QHttpHeaders::WellKnownHeader::Upgrade);
// Do some minimal checks here - we expect 'Upgrade: h2c' to be found.
- for (const auto &v : reply.header().values(QHttpHeaders::WellKnownHeader::Upgrade)) {
+ for (const auto &v : values) {
if (v.compare("h2c", Qt::CaseInsensitive) == 0)
return true;
}
diff --git a/src/network/access/qhttp2connection.cpp b/src/network/access/qhttp2connection.cpp
index 1d5c0d92b63..2895e8335d2 100644
--- a/src/network/access/qhttp2connection.cpp
+++ b/src/network/access/qhttp2connection.cpp
@@ -454,7 +454,7 @@ void QHttp2Stream::internalSendDATA()
"[%p] stream %u, exhausted device %p, sent END_STREAM? %d, %ssending end stream "
"after DATA",
connection, m_streamID, m_uploadByteDevice, sentEND_STREAM,
- m_endStreamAfterDATA ? "" : "not ");
+ !sentEND_STREAM && m_endStreamAfterDATA ? "" : "not ");
if (!sentEND_STREAM && m_endStreamAfterDATA) {
// We need to send an empty DATA frame with END_STREAM since we
// have exhausted the device, but we haven't sent END_STREAM yet.
@@ -690,8 +690,9 @@ void QHttp2Stream::handleDATA(const Frame &inboundFrame)
m_recvWindow -= qint32(inboundFrame.payloadSize());
const bool endStream = inboundFrame.flags().testFlag(FrameFlag::END_STREAM);
+ const bool ignoreData = connection->streamIsIgnored(m_streamID);
// Uncompress data if needed and append it ...
- if (inboundFrame.dataSize() > 0 || endStream) {
+ if ((inboundFrame.dataSize() > 0 || endStream) && !ignoreData) {
QByteArray fragment(reinterpret_cast<const char *>(inboundFrame.dataBegin()),
inboundFrame.dataSize());
if (endStream)
@@ -1245,16 +1246,12 @@ void QHttp2Connection::connectionError(Http2Error errorCode, const char *message
{
Q_ASSERT(message);
// RFC 9113, 6.8: An endpoint MAY send multiple GOAWAY frames if circumstances change.
- // Anyway, we do not send multiple GOAWAY frames.
- if (m_goingAway)
- return;
qCCritical(qHttp2ConnectionLog, "[%p] Connection error: %s (%d)", this, message,
int(errorCode));
// RFC 9113, 6.8: Endpoints SHOULD always send a GOAWAY frame before closing a connection so
// that the remote peer can know whether a stream has been partially processed or not.
- m_goingAway = true;
sendGOAWAY(errorCode);
auto messageView = QLatin1StringView(message);
@@ -1295,6 +1292,20 @@ bool QHttp2Connection::isInvalidStream(quint32 streamID) noexcept
return (!stream || stream->wasResetbyPeer()) && !streamWasResetLocally(streamID);
}
+/*!
+ When we send a GOAWAY we also send the ID of the last stream we know about
+ at the time. Any stream that starts after this one is ignored, but we still
+ have to process HEADERS due to compression state, and DATA due to stream and
+ connection window size changes.
+ Other than that - any \a streamID for which this returns true should be
+ ignored, and deleted at the earliest convenience.
+*/
+bool QHttp2Connection::streamIsIgnored(quint32 streamID) const noexcept
+{
+ const bool streamIsRemote = (streamID & 1) == (m_connectionType == Type::Client ? 0 : 1);
+ return Q_UNLIKELY(streamIsRemote && m_lastStreamToProcess < streamID);
+}
+
bool QHttp2Connection::sendClientPreface()
{
QIODevice *socket = getSocket();
@@ -1359,9 +1370,16 @@ bool QHttp2Connection::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
bool QHttp2Connection::sendGOAWAY(Http2::Http2Error errorCode)
{
+ m_goingAway = true;
+ // If this is the first time, start the timer:
+ if (m_lastStreamToProcess == Http2::lastValidStreamID)
+ m_goawayGraceTimer.setRemainingTime(GoawayGracePeriod);
+ m_lastStreamToProcess = std::min(m_lastIncomingStreamID, m_lastStreamToProcess);
+ qCDebug(qHttp2ConnectionLog, "[%p] Sending GOAWAY frame, error code %u, last stream %u", this,
+ errorCode, m_lastStreamToProcess);
frameWriter.start(FrameType::GOAWAY, FrameFlag::EMPTY,
Http2PredefinedParameters::connectionStreamID);
- frameWriter.append(quint32(m_lastIncomingStreamID));
+ frameWriter.append(m_lastStreamToProcess);
frameWriter.append(quint32(errorCode));
return frameWriter.write(*getSocket());
}
@@ -1411,8 +1429,20 @@ void QHttp2Connection::handleDATA()
if (stream)
stream->handleDATA(inboundFrame);
- if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM))
- emit receivedEND_STREAM(streamID);
+
+ if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) {
+ const bool ignoreData = stream && streamIsIgnored(stream->streamID());
+ if (!ignoreData) {
+ emit receivedEND_STREAM(streamID);
+ } else {
+ // Stream opened after our GOAWAY cut-off. We would just drop the
+ // data, but needed to handle it enough to track sizes of streams and
+ // connection windows. Since we've now taken care of that, we can
+ // at last close and delete it.
+ stream->setState(QHttp2Stream::State::Closed);
+ delete stream;
+ }
+ }
if (sessionReceiveWindowSize < maxSessionReceiveWindowSize / 2) {
// @future[consider]: emit signal instead
@@ -1454,8 +1484,15 @@ void QHttp2Connection::handleHEADERS()
return;
}
- qCDebug(qHttp2ConnectionLog, "[%p] Created new incoming stream %d", this, streamID);
- emit newIncomingStream(newStream);
+ qCDebug(qHttp2ConnectionLog, "[%p] New incoming stream %d", this, streamID);
+ if (!streamIsIgnored(newStream->streamID())) {
+ emit newIncomingStream(newStream);
+ } else if (m_goawayGraceTimer.hasExpired()) {
+ // We gave the peer some time to handle the GOAWAY message, but they have started a new
+ // stream, so we error out.
+ connectionError(Http2Error::PROTOCOL_ERROR, "Peer refused to GOAWAY.");
+ return;
+ }
} else if (streamWasResetLocally(streamID)) {
qCDebug(qHttp2ConnectionLog,
"[%p] Received HEADERS on previously locally reset stream %d (must process but ignore)",
@@ -1500,6 +1537,9 @@ void QHttp2Connection::handlePRIORITY()
|| inboundFrame.type() == FrameType::HEADERS);
const auto streamID = inboundFrame.streamID();
+ if (streamIsIgnored(streamID))
+ return;
+
// RFC 9913, 6.3: If a PRIORITY frame is received with a stream identifier of 0x00, the
// recipient MUST respond with a connection error
if (streamID == connectionStreamID)
@@ -1534,11 +1574,14 @@ void QHttp2Connection::handleRST_STREAM()
{
Q_ASSERT(inboundFrame.type() == FrameType::RST_STREAM);
+ const auto streamID = inboundFrame.streamID();
+ if (streamIsIgnored(streamID))
+ return;
+
// RFC 9113, 6.4: RST_STREAM frames MUST be associated with a stream.
// If a RST_STREAM frame is received with a stream identifier of 0x0,
// the recipient MUST treat this as a connection error (Section 5.4.1)
// of type PROTOCOL_ERROR.
- const auto streamID = inboundFrame.streamID();
if (streamID == connectionStreamID)
return connectionError(PROTOCOL_ERROR, "RST_STREAM on 0x0");
@@ -1764,31 +1807,33 @@ void QHttp2Connection::handleGOAWAY()
Q_ASSERT(inboundFrame.payloadSize() >= 8);
const uchar *const src = inboundFrame.dataBegin();
- quint32 lastStreamID = qFromBigEndian<quint32>(src);
+ // RFC 9113, 4.1: 31-bit Stream ID; lastValidStreamID(0x7FFFFFFF) masks out the reserved MSB
+ const quint32 lastStreamID = qFromBigEndian<quint32>(src) & lastValidStreamID;
const Http2Error errorCode = Http2Error(qFromBigEndian<quint32>(src + 4));
- if (!lastStreamID) {
- // "The last stream identifier can be set to 0 if no
- // streams were processed."
- lastStreamID = 1;
- } else if (!(lastStreamID & 0x1)) {
- // 5.1.1 - we (client) use only odd numbers as stream identifiers.
+ // 6.8 "the GOAWAY contains the stream identifier of the last peer-initiated stream that was
+ // or might be processed on the sending endpoint in this connection."
+ // Alternatively, they can specify 0 as the last stream ID, meaning they are not intending to
+ // process any remaining stream(s).
+ const quint32 LocalMask = m_connectionType == Type::Client ? 1 : 0;
+ // The stream must match the LocalMask, meaning we initiated it, for the last stream ID to make
+ // sense - they are not processing their own streams.
+ if (lastStreamID != 0 && (lastStreamID & 0x1) != LocalMask)
return connectionError(PROTOCOL_ERROR, "GOAWAY with invalid last stream ID");
- } else if (lastStreamID >= m_nextStreamID) {
- // "A server that is attempting to gracefully shut down a connection SHOULD
- // send an initial GOAWAY frame with the last stream identifier set to 2^31-1
- // and a NO_ERROR code."
- if (lastStreamID != lastValidStreamID || errorCode != HTTP2_NO_ERROR)
- return connectionError(PROTOCOL_ERROR, "GOAWAY invalid stream/error code");
- } else {
- lastStreamID += 2;
- }
+ qCDebug(qHttp2ConnectionLog, "[%p] Received GOAWAY frame, error code %u, last stream %u",
+ this, errorCode, lastStreamID);
m_goingAway = true;
emit receivedGOAWAY(errorCode, lastStreamID);
- for (quint32 id = lastStreamID; id < m_nextStreamID; id += 2) {
+ // Since the embedded stream ID is the last one that was or _might be_ processed,
+ // we cancel anything that comes after it. 0 can be used in the special case that
+ // no streams at all were or will be processed.
+ const quint32 firstPossibleStream = m_connectionType == Type::Client ? 1 : 2;
+ const quint32 firstCancelledStream = lastStreamID ? lastStreamID + 2 : firstPossibleStream;
+ Q_ASSERT((firstCancelledStream & 0x1) == LocalMask);
+ for (quint32 id = firstCancelledStream; id < m_nextStreamID; id += 2) {
QHttp2Stream *stream = m_streams.value(id, nullptr);
if (stream && stream->isActive())
stream->finishWithError(errorCode, "Received GOAWAY"_L1);
@@ -1809,7 +1854,8 @@ void QHttp2Connection::handleWINDOW_UPDATE()
// errors on the connection flow-control window MUST be treated as a connection error
const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
const auto streamID = inboundFrame.streamID();
-
+ if (streamIsIgnored(streamID))
+ return;
// RFC 9113, 6.9: A WINDOW_UPDATE frame with a length other than 4 octets MUST be treated
// as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
@@ -1939,6 +1985,18 @@ void QHttp2Connection::handleContinuedHEADERS()
if (streamWasResetLocally(streamID) || streamIt == m_streams.cend())
return; // No more processing without a stream from here on.
+ if (streamIsIgnored(streamID)) {
+ // Stream was established after GOAWAY cut-off, we ignore it, but we
+ // have to process things that alter state. That already happened, so we
+ // stop here.
+ if (continuedFrames[0].flags().testFlag(Http2::FrameFlag::END_STREAM)) {
+ if (QHttp2Stream *stream = streamIt.value()) {
+ stream->setState(QHttp2Stream::State::Closed);
+ delete stream;
+ }
+ }
+ return;
+ }
switch (firstFrameType) {
case FrameType::HEADERS:
diff --git a/src/network/access/qhttp2connection_p.h b/src/network/access/qhttp2connection_p.h
index dcdc0f91318..f3f14145278 100644
--- a/src/network/access/qhttp2connection_p.h
+++ b/src/network/access/qhttp2connection_p.h
@@ -283,6 +283,8 @@ private:
bool isInvalidStream(quint32 streamID) noexcept;
bool streamWasResetLocally(quint32 streamID) noexcept;
+ Q_ALWAYS_INLINE
+ bool streamIsIgnored(quint32 streamID) const noexcept;
void connectionError(Http2::Http2Error errorCode,
const char *message); // Connection failed to be established?
@@ -400,6 +402,10 @@ private:
bool m_goingAway = false;
bool pushPromiseEnabled = false;
quint32 m_lastIncomingStreamID = Http2::connectionStreamID;
+ // Gets lowered when/if we send GOAWAY:
+ quint32 m_lastStreamToProcess = Http2::lastValidStreamID;
+ static constexpr std::chrono::duration GoawayGracePeriod = std::chrono::seconds(60);
+ QDeadlineTimer m_goawayGraceTimer;
bool m_prefaceSent = false;
diff --git a/src/network/access/qhttpnetworkheader.cpp b/src/network/access/qhttpnetworkheader.cpp
index acb4fcfa8e1..d09d856e441 100644
--- a/src/network/access/qhttpnetworkheader.cpp
+++ b/src/network/access/qhttpnetworkheader.cpp
@@ -4,8 +4,6 @@
#include "qhttpnetworkheader_p.h"
-#include <algorithm>
-
QT_BEGIN_NAMESPACE
QHttpNetworkHeader::~QHttpNetworkHeader()
diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp
index 87f113be5dc..5047fc77bd5 100644
--- a/src/network/access/qnetworkrequest.cpp
+++ b/src/network/access/qnetworkrequest.cpp
@@ -21,7 +21,6 @@
#include "QtCore/private/qduplicatetracker_p.h"
#include "QtCore/private/qtools_p.h"
-#include <ctype.h>
#if QT_CONFIG(datestring)
# include <stdio.h>
#endif