Bug 1866218 - Use the latest version of HTTP/3 from Alt-Svc headers, r=necko-reviewers,valentin

Make Firefox use the latest HTTP/3 version listed in Alt-Svc headers, rather than the first one.

Differential Revision: https://phabricator.services.mozilla.com/D203235
This commit is contained in:
Kershaw Chang 2024-03-04 12:49:19 +00:00
parent 72866aa04b
commit 8d17acbe60
4 changed files with 84 additions and 40 deletions

View File

@ -7,6 +7,7 @@
#include "HttpLog.h"
#include "AlternateServices.h"
#include <algorithm>
#include "LoadInfo.h"
#include "mozilla/Atomics.h"
#include "mozilla/StaticPrefs_network.h"
@ -87,16 +88,15 @@ void AltSvcMapping::ProcessHeader(
ParsedHeaderValueListList parsedAltSvc(buf);
int32_t numEntriesInHeader = parsedAltSvc.mValues.Length();
// Only use one http3 version.
bool http3Found = false;
nsTArray<RefPtr<AltSvcMapping>> h3Mappings;
nsTArray<RefPtr<AltSvcMapping>> otherMappings;
for (uint32_t index = 0; index < parsedAltSvc.mValues.Length(); ++index) {
uint32_t maxage = 86400; // default
nsAutoCString hostname;
nsAutoCString npnToken;
int32_t portno = originPort;
bool clearEntry = false;
bool isHttp3 = false;
SupportedAlpnRank alpnRank = SupportedAlpnRank::NOT_SUPPORTED;
for (uint32_t pairIndex = 0;
pairIndex < parsedAltSvc.mValues[index].mValues.Length();
@ -116,7 +116,7 @@ void AltSvcMapping::ProcessHeader(
// h2=[hostname]:443 or h3-xx=[hostname]:port
// XX is current version we support and it is define in nsHttp.h.
isHttp3 = gHttpHandler->IsHttp3VersionSupported(currentName);
alpnRank = IsAlpnSupported(currentName);
npnToken = currentName;
int32_t colonIndex = currentValue.FindChar(':');
@ -153,12 +153,7 @@ void AltSvcMapping::ProcessHeader(
// update nsCString length
nsUnescape(npnToken.BeginWriting());
npnToken.SetLength(strlen(npnToken.BeginReading()));
if (http3Found && isHttp3) {
LOG(("Alt Svc ignore multiple Http3 options (%s)", npnToken.get()));
continue;
}
bool isHttp3 = net::IsHttp3(alpnRank);
SpdyInformation* spdyInfo = gHttpHandler->SpdyInfo();
if (!(npnToken.Equals(spdyInfo->VersionString) &&
StaticPrefs::network_http_http2_enabled()) &&
@ -169,16 +164,13 @@ void AltSvcMapping::ProcessHeader(
continue;
}
if (isHttp3) {
http3Found = true;
}
RefPtr<AltSvcMapping> mapping =
new AltSvcMapping(gHttpHandler->AltServiceCache()->GetStoragePtr(),
gHttpHandler->AltServiceCache()->StorageEpoch(),
originScheme, originHost, originPort, username,
privateBrowsing, NowInSeconds() + maxage, hostname,
portno, npnToken, originAttributes, isHttp3);
LOG(("AltSvcMapping created npnToken=%s", npnToken.get()));
RefPtr<AltSvcMapping> mapping = new AltSvcMapping(
gHttpHandler->AltServiceCache()->GetStoragePtr(),
gHttpHandler->AltServiceCache()->StorageEpoch(), originScheme,
originHost, originPort, username, privateBrowsing,
NowInSeconds() + maxage, hostname, portno, npnToken, originAttributes,
isHttp3, alpnRank);
if (mapping->TTL() <= 0) {
LOG(("Alt Svc invalid map"));
mapping = nullptr;
@ -186,15 +178,39 @@ void AltSvcMapping::ProcessHeader(
// as that would have happened if we had accepted the parameters.
gHttpHandler->AltServiceCache()->ClearHostMapping(originHost, originPort,
originAttributes);
} else if (!aDontValidate) {
gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, callbacks, caps,
originAttributes);
} else {
gHttpHandler->UpdateAltServiceMappingWithoutValidation(
mapping, proxyInfo, callbacks, caps, originAttributes);
if (isHttp3) {
h3Mappings.AppendElement(std::move(mapping));
} else {
otherMappings.AppendElement(std::move(mapping));
}
}
}
auto doUpdateAltSvcMapping = [&](AltSvcMapping* aMapping) {
if (!aDontValidate) {
gHttpHandler->UpdateAltServiceMapping(aMapping, proxyInfo, callbacks,
caps, originAttributes);
} else {
gHttpHandler->UpdateAltServiceMappingWithoutValidation(
aMapping, proxyInfo, callbacks, caps, originAttributes);
}
};
if (!h3Mappings.IsEmpty()) {
// Select the HTTP/3 (h3) AltSvcMapping with the highest ALPN rank from
// h3Mappings.
RefPtr<AltSvcMapping> latestH3Mapping = *std::max_element(
h3Mappings.begin(), h3Mappings.end(),
[](const RefPtr<AltSvcMapping>& a, const RefPtr<AltSvcMapping>& b) {
return a->AlpnRank() < b->AlpnRank();
});
doUpdateAltSvcMapping(latestH3Mapping);
}
std::for_each(otherMappings.begin(), otherMappings.end(),
doUpdateAltSvcMapping);
if (numEntriesInHeader) { // Ignore headers that were just "alt-svc: clear"
Telemetry::Accumulate(Telemetry::HTTP_ALTSVC_ENTRIES_PER_HEADER,
numEntriesInHeader);
@ -209,7 +225,7 @@ AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch,
const nsACString& alternateHost,
int32_t alternatePort, const nsACString& npnToken,
const OriginAttributes& originAttributes,
bool aIsHttp3)
bool aIsHttp3, SupportedAlpnRank aRank)
: mStorage(storage),
mStorageEpoch(epoch),
mAlternateHost(alternateHost),
@ -221,7 +237,8 @@ AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch,
mExpiresAt(expiresAt),
mNPNToken(npnToken),
mOriginAttributes(originAttributes),
mIsHttp3(aIsHttp3) {
mIsHttp3(aIsHttp3),
mAlpnRank(aRank) {
MOZ_ASSERT(NS_IsMainThread());
if (NS_FAILED(SchemeIsHTTPS(originScheme, mHttps))) {

View File

@ -23,6 +23,7 @@ https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06
#ifndef mozilla_net_AlternateServices_h
#define mozilla_net_AlternateServices_h
#include "nsHttp.h"
#include "nsRefPtrHashtable.h"
#include "nsString.h"
#include "nsIDataStorage.h"
@ -53,7 +54,8 @@ class AltSvcMapping {
bool privateBrowsing, uint32_t expiresAt,
const nsACString& alternateHost, int32_t alternatePort,
const nsACString& npnToken,
const OriginAttributes& originAttributes, bool aIsHttp3);
const OriginAttributes& originAttributes, bool aIsHttp3,
SupportedAlpnRank aRank);
public:
AltSvcMapping(nsIDataStorage* storage, int32_t storageEpoch,
@ -103,6 +105,7 @@ class AltSvcMapping {
bool IsHttp3() { return mIsHttp3; }
const nsCString& NPNToken() const { return mNPNToken; }
SupportedAlpnRank AlpnRank() const { return mAlpnRank; }
private:
virtual ~AltSvcMapping() = default;
@ -138,6 +141,7 @@ class AltSvcMapping {
bool mSyncOnlyOnSuccess{false};
bool mIsHttp3{false};
SupportedAlpnRank mAlpnRank{SupportedAlpnRank::NOT_SUPPORTED};
};
class AltSvcOverride : public nsIInterfaceRequestor,

View File

@ -5,7 +5,7 @@ let h3AltSvc;
let h3Route;
let prefs;
let tests = [test_https_alt_svc, testsDone];
let tests = [test_https_alt_svc, test_https_alt_svc_1, testsDone];
let current_test = 0;
@ -68,7 +68,9 @@ function makeChan(uri) {
return chan;
}
let WaitForHttp3Listener = function () {};
let WaitForHttp3Listener = function (expectedH3Version) {
this._expectedH3Version = expectedH3Version;
};
WaitForHttp3Listener.prototype = {
onDataAvailableFired: false,
@ -96,13 +98,18 @@ WaitForHttp3Listener.prototype = {
try {
httpVersion = request.protocolVersion;
} catch (e) {}
Assert.equal(httpVersion, "h3-29");
Assert.equal(httpVersion, this._expectedH3Version);
run_next_test();
} else {
dump("poll later for alt svc mapping\n");
do_test_pending();
do_timeout(500, () => {
doTest(this.uri, this.expectedRoute, this.h3AltSvc);
doTest(
this.uri,
this.expectedRoute,
this.h3AltSvc,
this._expectedH3Version
);
});
}
@ -110,9 +117,9 @@ WaitForHttp3Listener.prototype = {
},
};
function doTest(uri, expectedRoute, altSvc) {
function doTest(uri, expectedRoute, altSvc, expectedH3Version) {
let chan = makeChan(uri);
let listener = new WaitForHttp3Listener();
let listener = new WaitForHttp3Listener(expectedH3Version);
listener.uri = uri;
listener.expectedRoute = expectedRoute;
listener.h3AltSvc = altSvc;
@ -121,11 +128,23 @@ function doTest(uri, expectedRoute, altSvc) {
}
// Test Alt-Svc for http3.
// H2 server returns alt-svc=h2=foo2.example.com:8000,h3-29=:h3port,h3-30=foo2.example.com:8443
// H2 server returns alt-svc=h2=foo2.example.com:8000,h3-29=:h3port
function test_https_alt_svc() {
dump("test_https_alt_svc()\n");
do_test_pending();
doTest(httpsOrigin + "http3-test2", h3Route, h3AltSvc);
doTest(httpsOrigin + "http3-test2", h3Route, h3AltSvc, "h3-29");
}
// Test if we use the latest version of HTTP/3.
// H2 server returns alt-svc=h3-29=:h3port,h3=:h3port
function test_https_alt_svc_1() {
// Close the previous connection and clear alt-svc mappings.
Services.obs.notifyObservers(null, "last-pb-context-exited");
Services.obs.notifyObservers(null, "net:cancel-all-connections");
do_test_pending();
do_timeout(1000, () => {
doTest(httpsOrigin + "http3-test3", h3Route, h3AltSvc, "h3");
});
}
function testsDone() {

View File

@ -879,9 +879,13 @@ function handleRequest(req, res) {
res.setHeader("Cache-Control", "no-cache");
res.setHeader(
"Alt-Svc",
"h2=foo2.example.com:8000,h3-29=" +
req.headers["x-altsvc"] +
",h3-30=foo2.example.com:8443"
"h2=foo2.example.com:8000,h3-29=" + req.headers["x-altsvc"]
);
} else if (u.pathname === "/http3-test3") {
res.setHeader("Cache-Control", "no-cache");
res.setHeader(
"Alt-Svc",
"h3-29=" + req.headers["x-altsvc"] + ",h3=" + req.headers["x-altsvc"]
);
}
// for use with test_trr.js