Bug 1726528 - Add a pref to control whether to enable echConfig for http3, r=necko-reviewers,dragana

Differential Revision: https://phabricator.services.mozilla.com/D123125
This commit is contained in:
Kershaw Chang 2021-09-20 14:24:14 +00:00
parent 2b5f1f4393
commit 21d1774ea9
21 changed files with 754 additions and 199 deletions

View File

@ -9886,6 +9886,12 @@
value: false
mirror: always
# Whether to enable echconfig for http3.
- name: network.dns.http3_echconfig.enabled
type: RelaxedAtomicBool
value: false
mirror: always
# This pref needs to be worked together with network.dns.echconfig.enabled
# being true and there is no record without ECHConfig.
# When we try all records with ECHConfig in HTTPS RRs and still can't connect,

View File

@ -83,6 +83,10 @@ struct IPDLParamTraits<mozilla::net::SVCB> {
const paramType& aParam) {
WriteIPDLParam(aMsg, aActor, aParam.mSvcFieldPriority);
WriteIPDLParam(aMsg, aActor, aParam.mSvcDomainName);
WriteIPDLParam(aMsg, aActor, aParam.mEchConfig);
WriteIPDLParam(aMsg, aActor, aParam.mODoHConfig);
WriteIPDLParam(aMsg, aActor, aParam.mHasIPHints);
WriteIPDLParam(aMsg, aActor, aParam.mHasEchConfig);
WriteIPDLParam(aMsg, aActor, aParam.mSvcFieldValue);
}
@ -94,6 +98,18 @@ struct IPDLParamTraits<mozilla::net::SVCB> {
if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSvcDomainName)) {
return false;
}
if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mEchConfig)) {
return false;
}
if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mODoHConfig)) {
return false;
}
if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mHasIPHints)) {
return false;
}
if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mHasEchConfig)) {
return false;
}
if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSvcFieldValue)) {
return false;
}

View File

@ -185,24 +185,6 @@ bool SVCB::NoDefaultAlpn() const {
return false;
}
Maybe<Tuple<nsCString, bool>> SVCB::GetAlpn(bool aNoHttp2,
bool aNoHttp3) const {
Maybe<Tuple<nsCString, bool>> alpn;
for (const auto& value : mSvcFieldValue) {
if (value.mValue.is<SvcParamAlpn>()) {
nsTArray<nsCString> alpnList;
alpnList.AppendElements(value.mValue.as<SvcParamAlpn>().mValue);
if (!alpnList.IsEmpty()) {
alpn.emplace();
alpn = Some(SelectAlpnFromAlpnList(alpnList, aNoHttp2, aNoHttp3));
}
return alpn;
}
}
return Nothing();
}
void SVCB::GetIPHints(CopyableTArray<mozilla::net::NetAddr>& aAddresses) const {
if (mSvcFieldPriority == 0) {
return;
@ -217,6 +199,22 @@ void SVCB::GetIPHints(CopyableTArray<mozilla::net::NetAddr>& aAddresses) const {
}
}
nsTArray<nsCString> SVCB::GetAllAlpn() const {
nsTArray<nsCString> alpnList;
for (const auto& value : mSvcFieldValue) {
if (value.mValue.is<SvcParamAlpn>()) {
alpnList.AppendElements(value.mValue.as<SvcParamAlpn>().mValue);
}
}
return alpnList;
}
SVCBRecord::SVCBRecord(const SVCB& data,
Maybe<Tuple<nsCString, SupportedAlpnType>> aAlpn)
: mData(data), mAlpn(aAlpn) {
mPort = mData.GetPort();
}
NS_IMETHODIMP SVCBRecord::GetPriority(uint16_t* aPriority) {
*aPriority = mData.mSvcFieldPriority;
return NS_OK;
@ -229,7 +227,18 @@ NS_IMETHODIMP SVCBRecord::GetName(nsACString& aName) {
Maybe<uint16_t> SVCBRecord::GetPort() { return mPort; }
Maybe<Tuple<nsCString, bool>> SVCBRecord::GetAlpn() { return mAlpn; }
Maybe<Tuple<nsCString, SupportedAlpnType>> SVCBRecord::GetAlpn() {
return mAlpn;
}
NS_IMETHODIMP SVCBRecord::GetSelectedAlpn(nsACString& aAlpn) {
if (!mAlpn) {
return NS_ERROR_NOT_AVAILABLE;
}
aAlpn = Get<0>(*mAlpn);
return NS_OK;
}
NS_IMETHODIMP SVCBRecord::GetEchConfig(nsACString& aEchConfig) {
aEchConfig = mData.mEchConfig;
@ -254,95 +263,159 @@ NS_IMETHODIMP SVCBRecord::GetHasIPHintAddress(bool* aHasIPHintAddress) {
return NS_OK;
}
static bool CheckAlpnIsUsable(const nsACString& aTargetName,
const nsACString& aAlpn, bool aIsHttp3,
uint32_t& aExcludedCount) {
if (aAlpn.IsEmpty()) {
return false;
static bool CheckRecordIsUsable(const SVCB& aRecord, nsIDNSService* aDNSService,
const nsACString& aHost,
uint32_t& aExcludedCount) {
if (!aHost.IsEmpty()) {
bool excluded = false;
if (NS_SUCCEEDED(aDNSService->IsSVCDomainNameFailed(
aHost, aRecord.mSvcDomainName, &excluded)) &&
excluded) {
// Skip if the domain name of this record was failed to connect before.
++aExcludedCount;
return false;
}
}
if (aIsHttp3 && gHttpHandler->IsHttp3Excluded(aTargetName)) {
aExcludedCount++;
Maybe<uint16_t> port = aRecord.GetPort();
if (port && *port == 0) {
// Found an unsafe port, skip this record.
return false;
}
return true;
}
static bool CheckAlpnIsUsable(SupportedAlpnType aAlpnType, bool aNoHttp2,
bool aNoHttp3, bool aCheckHttp3ExcludedList,
const nsACString& aTargetName,
uint32_t& aExcludedCount) {
// Skip if this alpn is not supported.
if (aAlpnType == SupportedAlpnType::NOT_SUPPORTED) {
return false;
}
// Skip if we don't want to use http2.
if (aNoHttp2 && aAlpnType == SupportedAlpnType::HTTP_2) {
return false;
}
if (aAlpnType == SupportedAlpnType::HTTP_3) {
if (aCheckHttp3ExcludedList && gHttpHandler->IsHttp3Excluded(aTargetName)) {
aExcludedCount++;
return false;
}
if (aNoHttp3) {
return false;
}
}
return true;
}
static nsTArray<SVCBWrapper> FlattenRecords(const nsTArray<SVCB>& aRecords) {
nsTArray<SVCBWrapper> result;
for (const auto& record : aRecords) {
nsTArray<nsCString> alpnList = record.GetAllAlpn();
if (alpnList.IsEmpty()) {
result.AppendElement(SVCBWrapper(record));
} else {
for (const auto& alpn : alpnList) {
SVCBWrapper wrapper(record);
wrapper.mAlpn.emplace(MakeTuple(alpn, IsAlpnSupported(alpn)));
result.AppendElement(wrapper);
}
}
}
return result;
}
already_AddRefed<nsISVCBRecord>
DNSHTTPSSVCRecordBase::GetServiceModeRecordInternal(
bool aNoHttp2, bool aNoHttp3, const nsTArray<SVCB>& aRecords,
bool& aRecordsAllExcluded, bool aCheckHttp3ExcludedList) {
nsCOMPtr<nsISVCBRecord> selectedRecord;
RefPtr<SVCBRecord> selectedRecord;
RefPtr<SVCBRecord> h3RecordWithEchConfig;
uint32_t recordHasNoDefaultAlpnCount = 0;
uint32_t recordExcludedCount = 0;
aRecordsAllExcluded = false;
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
bool RRSetHasEchConfig = false;
uint32_t h3ExcludedCount = 0;
for (const SVCB& record : aRecords) {
if (record.mSvcFieldPriority == 0) {
nsTArray<SVCBWrapper> records = FlattenRecords(aRecords);
for (const auto& record : records) {
if (record.mRecord.mSvcFieldPriority == 0) {
// In ServiceMode, the SvcPriority should never be 0.
return nullptr;
}
if (record.NoDefaultAlpn()) {
if (record.mRecord.NoDefaultAlpn()) {
++recordHasNoDefaultAlpnCount;
}
RRSetHasEchConfig |= record.mHasEchConfig;
bool excluded = false;
if (NS_SUCCEEDED(dns->IsSVCDomainNameFailed(mHost, record.mSvcDomainName,
&excluded)) &&
excluded) {
// Skip if the domain name of this record was failed to connect before.
++recordExcludedCount;
if (!CheckRecordIsUsable(record.mRecord, dns, mHost, recordExcludedCount)) {
// Skip if this record is not usable.
continue;
}
Maybe<uint16_t> port = record.GetPort();
if (port && *port == 0) {
// Found an unsafe port, skip this record.
continue;
}
Maybe<Tuple<nsCString, bool>> alpn = record.GetAlpn(aNoHttp2, aNoHttp3);
if (alpn) {
if (!CheckAlpnIsUsable(record.mSvcDomainName, Get<0>(*alpn),
aCheckHttp3ExcludedList && Get<1>(*alpn),
h3ExcludedCount)) {
if (record.mAlpn) {
if (!CheckAlpnIsUsable(Get<1>(*(record.mAlpn)), aNoHttp2, aNoHttp3,
aCheckHttp3ExcludedList,
record.mRecord.mSvcDomainName, h3ExcludedCount)) {
continue;
}
if (Get<1>(*(record.mAlpn)) == SupportedAlpnType::HTTP_3) {
// If the selected alpn is h3 and ech for h3 is disabled, we want
// to find out if there is another non-h3 record that has
// echConfig. If yes, we'll use the non-h3 record with echConfig
// to connect. If not, we'll use h3 to connect without echConfig.
if (record.mRecord.mHasEchConfig &&
(gHttpHandler->EchConfigEnabled() &&
!gHttpHandler->EchConfigEnabled(true))) {
if (!h3RecordWithEchConfig) {
// Save this h3 record for later use.
h3RecordWithEchConfig =
new SVCBRecord(record.mRecord, record.mAlpn);
// Make sure the next record is not h3.
aNoHttp3 = true;
continue;
}
}
}
}
if (gHttpHandler->EchConfigEnabled() && RRSetHasEchConfig &&
!record.mHasEchConfig) {
// Don't use this record if this record has no echConfig, but others have.
continue;
}
if (!selectedRecord) {
selectedRecord = new SVCBRecord(record, std::move(port), std::move(alpn));
selectedRecord = new SVCBRecord(record.mRecord, record.mAlpn);
}
}
// If all records indicate "no-default-alpn", we should not use this RRSet.
if (recordHasNoDefaultAlpnCount == aRecords.Length()) {
return nullptr;
if (!selectedRecord && !h3RecordWithEchConfig) {
// If all records indicate "no-default-alpn", we should not use this RRSet.
if (recordHasNoDefaultAlpnCount == records.Length()) {
return nullptr;
}
if (recordExcludedCount == records.Length()) {
aRecordsAllExcluded = true;
return nullptr;
}
// If all records are in http3 excluded list, try again without checking the
// excluded list. This is better than returning nothing.
if (h3ExcludedCount == records.Length() && aCheckHttp3ExcludedList) {
return GetServiceModeRecordInternal(aNoHttp2, aNoHttp3, aRecords,
aRecordsAllExcluded, false);
}
}
if (recordExcludedCount == aRecords.Length()) {
aRecordsAllExcluded = true;
return nullptr;
}
if (h3RecordWithEchConfig) {
if (selectedRecord && selectedRecord->mData.mHasEchConfig) {
return selectedRecord.forget();
}
// If all records are in http3 excluded list, try again without checking the
// excluded list. This is better than returning nothing.
if (h3ExcludedCount == aRecords.Length() && aCheckHttp3ExcludedList) {
return GetServiceModeRecordInternal(aNoHttp2, aNoHttp3, aRecords,
aRecordsAllExcluded, false);
return h3RecordWithEchConfig.forget();
}
return selectedRecord.forget();
@ -364,8 +437,9 @@ void DNSHTTPSSVCRecordBase::GetAllRecordsWithEchConfigInternal(
}
uint32_t h3ExcludedCount = 0;
for (const SVCB& record : aRecords) {
if (record.mSvcFieldPriority == 0) {
nsTArray<SVCBWrapper> records = FlattenRecords(aRecords);
for (const auto& record : records) {
if (record.mRecord.mSvcFieldPriority == 0) {
// This should not happen, since GetAllRecordsWithEchConfigInternal()
// should be called only if GetServiceModeRecordInternal() returns a
// non-null record.
@ -375,35 +449,34 @@ void DNSHTTPSSVCRecordBase::GetAllRecordsWithEchConfigInternal(
// Records with echConfig are in front of records without echConfig, so we
// don't have to continue.
*aAllRecordsHaveEchConfig &= record.mHasEchConfig;
*aAllRecordsHaveEchConfig &= record.mRecord.mHasEchConfig;
if (!(*aAllRecordsHaveEchConfig)) {
aResult.Clear();
return;
}
Maybe<uint16_t> port = record.GetPort();
Maybe<uint16_t> port = record.mRecord.GetPort();
if (port && *port == 0) {
// Found an unsafe port, skip this record.
continue;
}
Maybe<Tuple<nsCString, bool>> alpn = record.GetAlpn(aNoHttp2, aNoHttp3);
if (alpn) {
if (!CheckAlpnIsUsable(record.mSvcDomainName, Get<0>(*alpn),
aCheckHttp3ExcludedList && Get<1>(*alpn),
h3ExcludedCount)) {
if (record.mAlpn) {
if (!CheckAlpnIsUsable(Get<1>(*(record.mAlpn)), aNoHttp2, aNoHttp3,
aCheckHttp3ExcludedList,
record.mRecord.mSvcDomainName, h3ExcludedCount)) {
continue;
}
}
RefPtr<nsISVCBRecord> svcbRecord =
new SVCBRecord(record, std::move(port), std::move(alpn));
new SVCBRecord(record.mRecord, record.mAlpn);
aResult.AppendElement(svcbRecord);
}
// If all records are in http3 excluded list, try again without checking the
// excluded list. This is better than returning nothing.
if (h3ExcludedCount == aRecords.Length() && aCheckHttp3ExcludedList) {
if (h3ExcludedCount == records.Length() && aCheckHttp3ExcludedList) {
GetAllRecordsWithEchConfigInternal(
aNoHttp2, aNoHttp3, aRecords, aAllRecordsHaveEchConfig,
aAllRecordsInH3ExcludedList, aResult, false);

View File

@ -9,10 +9,13 @@
#include "mozilla/net/DNS.h"
#include "mozilla/Variant.h"
#include "mozilla/Maybe.h"
#include "nsHttp.h"
namespace mozilla {
namespace net {
class DNSHTTPSSVCRecordBase;
enum SvcParamKey : uint16_t {
SvcParamKeyMandatory = 0,
SvcParamKeyAlpn = 1,
@ -96,8 +99,8 @@ struct SVCB {
bool operator<(const SVCB& aOther) const;
Maybe<uint16_t> GetPort() const;
bool NoDefaultAlpn() const;
Maybe<Tuple<nsCString, bool>> GetAlpn(bool aNoHttp2, bool aNoHttp3) const;
void GetIPHints(CopyableTArray<mozilla::net::NetAddr>& aAddresses) const;
nsTArray<nsCString> GetAllAlpn() const;
uint16_t mSvcFieldPriority = 0;
nsCString mSvcDomainName;
nsCString mEchConfig;
@ -107,21 +110,29 @@ struct SVCB {
CopyableTArray<SvcFieldValue> mSvcFieldValue;
};
struct SVCBWrapper {
explicit SVCBWrapper(const SVCB& aRecord) : mRecord(aRecord) {}
Maybe<Tuple<nsCString, SupportedAlpnType>> mAlpn;
const SVCB& mRecord;
};
class SVCBRecord : public nsISVCBRecord {
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSISVCBRECORD
public:
explicit SVCBRecord(const SVCB& data)
: mData(data), mPort(Nothing()), mAlpn(Nothing()) {}
explicit SVCBRecord(const SVCB& data, Maybe<uint16_t>&& aPort,
Maybe<Tuple<nsCString, bool>>&& aAlpn)
: mData(data), mPort(std::move(aPort)), mAlpn(std::move(aAlpn)) {}
explicit SVCBRecord(const SVCB& data,
Maybe<Tuple<nsCString, SupportedAlpnType>> aAlpn);
private:
friend class DNSHTTPSSVCRecordBase;
virtual ~SVCBRecord() = default;
SVCB mData;
Maybe<uint16_t> mPort;
Maybe<Tuple<nsCString, bool>> mAlpn;
Maybe<Tuple<nsCString, SupportedAlpnType>> mAlpn;
};
class DNSHTTPSSVCRecordBase {

View File

@ -9,6 +9,7 @@
#include "mozilla/Maybe.h"
#include "mozilla/Tuple.h"
#include "nsTArrayForwardDeclare.h"
#include "nsHttp.h"
#include "nsStringFwd.h"
namespace mozilla {
@ -29,7 +30,7 @@ namespace net {
native TypeResult(mozilla::net::TypeRecordResultType);
native MaybePort(mozilla::Maybe<uint16_t>);
native MaybeAlpnTuple(mozilla::Maybe<mozilla::Tuple<nsCString, bool>>);
native MaybeAlpnTuple(mozilla::Maybe<mozilla::Tuple<nsCString, mozilla::net::SupportedAlpnType>>);
[scriptable, uuid(5d13241b-9d46-448a-90d8-77c418491026)]
interface nsIDNSByTypeRecord : nsIDNSRecord
@ -98,6 +99,7 @@ interface nsISVCBRecord : nsISupports {
readonly attribute ACString name;
[noscript, nostdcall, notxpcom] readonly attribute MaybePort port;
[noscript, nostdcall, notxpcom] readonly attribute MaybeAlpnTuple alpn;
readonly attribute ACString selectedAlpn;
readonly attribute ACString echConfig;
readonly attribute ACString ODoHConfig;
readonly attribute bool hasIPHintAddress;

View File

@ -1195,6 +1195,7 @@ nsresult DnsAndConnectSocket::TransportSetup::SetupStreams(
NS_ENSURE_SUCCESS(rv, rv);
if (gHttpHandler->EchConfigEnabled()) {
MOZ_ASSERT(!ci->IsHttp3());
rv = socketTransport->SetEchConfig(ci->GetEchConfig());
NS_ENSURE_SUCCESS(rv, rv);
}

View File

@ -195,7 +195,7 @@ nsresult Http3Session::Init(const nsHttpConnectionInfo* aConnInfo,
ZeroRttTelemetry(ZeroRttOutcome::NOT_USED);
}
if (gHttpHandler->EchConfigEnabled()) {
if (gHttpHandler->EchConfigEnabled(true)) {
mSocketControl->SetEchConfig(mConnInfo->GetEchConfig());
}

View File

@ -1001,43 +1001,23 @@ nsresult HttpProxyResponseToErrorCode(uint32_t aStatusCode) {
return rv;
}
Tuple<nsCString, bool> SelectAlpnFromAlpnList(
const nsTArray<nsCString>& aAlpnList, bool aNoHttp2, bool aNoHttp3) {
nsCString h3Value;
nsCString h2Value;
nsCString h1Value;
for (const auto& npnToken : aAlpnList) {
bool isHttp3 = gHttpHandler->IsHttp3VersionSupported(npnToken);
if (isHttp3 && h3Value.IsEmpty()) {
h3Value.Assign(npnToken);
}
uint32_t spdyIndex;
SpdyInformation* spdyInfo = gHttpHandler->SpdyInfo();
if (NS_SUCCEEDED(spdyInfo->GetNPNIndex(npnToken, &spdyIndex)) &&
spdyInfo->ProtocolEnabled(spdyIndex) && h2Value.IsEmpty()) {
h2Value.Assign(npnToken);
}
if (npnToken.LowerCaseEqualsASCII("http/1.1") && h1Value.IsEmpty()) {
h1Value.Assign(npnToken);
}
SupportedAlpnType IsAlpnSupported(const nsACString& aAlpn) {
if (gHttpHandler->IsHttp3VersionSupported(aAlpn)) {
return SupportedAlpnType::HTTP_3;
}
if (!h3Value.IsEmpty() && gHttpHandler->IsHttp3Enabled() && !aNoHttp3) {
return MakeTuple(h3Value, true);
uint32_t spdyIndex;
SpdyInformation* spdyInfo = gHttpHandler->SpdyInfo();
if (NS_SUCCEEDED(spdyInfo->GetNPNIndex(aAlpn, &spdyIndex)) &&
spdyInfo->ProtocolEnabled(spdyIndex)) {
return SupportedAlpnType::HTTP_2;
}
if (!h2Value.IsEmpty() && gHttpHandler->IsSpdyEnabled() && !aNoHttp2) {
return MakeTuple(h2Value, false);
if (aAlpn.LowerCaseEqualsASCII("http/1.1")) {
return SupportedAlpnType::HTTP_1_1;
}
if (!h1Value.IsEmpty()) {
return MakeTuple(h1Value, false);
}
// If we are here, there is no supported alpn can be used.
return MakeTuple(EmptyCString(), false);
return SupportedAlpnType::NOT_SUPPORTED;
}
} // namespace net

View File

@ -37,6 +37,13 @@ enum class HttpVersion {
enum class SpdyVersion { NONE = 0, HTTP_2 = 5 };
enum class SupportedAlpnType : uint8_t {
HTTP_3 = 0,
HTTP_2,
HTTP_1_1,
NOT_SUPPORTED
};
extern const uint32_t kHttp3VersionCount;
extern const nsCString kHttp3Versions[];
@ -383,13 +390,8 @@ void LogHeaders(const char* lineStart);
// CONNECT method.
nsresult HttpProxyResponseToErrorCode(uint32_t aStatusCode);
// Given a list of alpn-id, this function returns a supported alpn-id. If both
// h3 and h2 are enabled, h3 alpn is preferred. This function returns a
// Tuple<alpn-id, isHttp3>. The first element is the alpn-id and the second one
// is a boolean to indicate if this alpn-id is for http3. If no supported
// alpn-id is found, the first element would be a n empty string.
Tuple<nsCString, bool> SelectAlpnFromAlpnList(
const nsTArray<nsCString>& aAlpnList, bool aNoHttp2, bool aNoHttp3);
// Convert an alpn string to SupportedAlpnType.
SupportedAlpnType IsAlpnSupported(const nsACString& aAlpn);
static inline bool AllowedErrorForHTTPSRRFallback(nsresult aError) {
return psm::IsNSSErrorCode(-1 * NS_ERROR_GET_CODE(aError)) ||

View File

@ -6977,8 +6977,8 @@ static void ReportHTTPSRRTelemetry(
getter_AddRefs(svcbRecord)))) {
MOZ_ASSERT(svcbRecord);
Maybe<Tuple<nsCString, bool>> alpn = svcbRecord->GetAlpn();
bool isHttp3 = alpn ? Get<1>(*alpn) : false;
Maybe<Tuple<nsCString, SupportedAlpnType>> alpn = svcbRecord->GetAlpn();
bool isHttp3 = alpn ? Get<1>(*alpn) == SupportedAlpnType::HTTP_3 : false;
Telemetry::Accumulate(Telemetry::HTTPS_RR_WITH_HTTP3_PRESENTED, isHttp3);
}
}

View File

@ -334,10 +334,10 @@ nsHttpConnectionInfo::CloneAndAdoptHTTPSSVCRecord(
// Try to get the port and Alpn. If this record has SvcParamKeyPort defined,
// the new port will be used as mRoutedPort.
Maybe<uint16_t> port = aRecord->GetPort();
Maybe<Tuple<nsCString, bool>> alpn = aRecord->GetAlpn();
Maybe<Tuple<nsCString, SupportedAlpnType>> alpn = aRecord->GetAlpn();
// Let the new conn info learn h3 will be used.
bool isHttp3 = alpn ? Get<1>(*alpn) : false;
bool isHttp3 = alpn ? Get<1>(*alpn) == SupportedAlpnType::HTTP_3 : false;
LOG(("HTTPSSVC: use new routed host (%s) and new npnToken (%s)", name.get(),
alpn ? Get<0>(*alpn).get() : "None"));

View File

@ -1647,7 +1647,7 @@ nsresult nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction* trans) {
trans->Caps() & NS_HTTP_DISALLOW_HTTP3);
MOZ_ASSERT(ent);
if (gHttpHandler->EchConfigEnabled()) {
if (gHttpHandler->EchConfigEnabled(ci->IsHttp3())) {
ent->MaybeUpdateEchConfig(ci);
}

View File

@ -2892,8 +2892,13 @@ bool nsHttpHandler::UseHTTPSRRAsAltSvcEnabled() const {
return StaticPrefs::network_dns_use_https_rr_as_altsvc();
}
bool nsHttpHandler::EchConfigEnabled() const {
return StaticPrefs::network_dns_echconfig_enabled();
bool nsHttpHandler::EchConfigEnabled(bool aIsHttp3) const {
if (!aIsHttp3) {
return StaticPrefs::network_dns_echconfig_enabled();
}
return StaticPrefs::network_dns_echconfig_enabled() &&
StaticPrefs::network_dns_http3_echconfig_enabled();
}
bool nsHttpHandler::FallbackToOriginIfConfigsAreECHAndAllFailed() const {

View File

@ -489,7 +489,7 @@ class nsHttpHandler final : public nsIHttpProtocolHandler,
bool UseHTTPSRRAsAltSvcEnabled() const;
bool EchConfigEnabled() const;
bool EchConfigEnabled(bool aIsHttp3 = false) const;
// When EchConfig is enabled and all records with echConfig are failed, this
// functon indicate whether we can fallback to the origin server.
// In the case an HTTPS RRSet contains some RRs with echConfig and some

View File

@ -1172,8 +1172,9 @@ void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
this, static_cast<uint32_t>(aReason)));
RefPtr<nsHttpConnectionInfo> failedConnInfo = mConnInfo->Clone();
mConnInfo = nullptr;
bool echConfigUsed = gHttpHandler->EchConfigEnabled() &&
!failedConnInfo->GetEchConfig().IsEmpty();
bool echConfigUsed =
gHttpHandler->EchConfigEnabled(failedConnInfo->IsHttp3()) &&
!failedConnInfo->GetEchConfig().IsEmpty();
if (mFastFallbackTriggered) {
mFastFallbackTriggered = false;
@ -3264,8 +3265,8 @@ void nsHttpTransaction::OnFastFallbackTimer() {
return;
}
bool echConfigUsed =
gHttpHandler->EchConfigEnabled() && !mConnInfo->GetEchConfig().IsEmpty();
bool echConfigUsed = gHttpHandler->EchConfigEnabled(mConnInfo->IsHttp3()) &&
!mConnInfo->GetEchConfig().IsEmpty();
mBackupConnInfo = PrepareFastFallbackConnInfo(echConfigUsed);
if (!mBackupConnInfo) {
return;

View File

@ -1,61 +0,0 @@
#include "gtest/gtest.h"
#include "mozilla/Preferences.h"
#include "nsIHttpProtocolHandler.h"
#include "nsHttp.h"
namespace mozilla {
namespace net {
TEST(TestSupportAlpn, testSvcParamKeyAlpn)
{
Preferences::SetBool("network.http.http3.enabled", true);
// initialize gHttphandler.
nsCOMPtr<nsIHttpProtocolHandler> http =
do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http");
// h2 and h3 are enabled, so first appearance h3 alpn-id is returned.
nsTArray<nsCString> alpn = {"h3-29"_ns, "h3-30"_ns, "h2"_ns, "http/1.1"_ns};
Tuple<nsCString, bool> result = SelectAlpnFromAlpnList(alpn, false, false);
ASSERT_EQ(Get<0>(result), "h3-29"_ns);
ASSERT_EQ(Get<1>(result), true);
// We don't support h3-26, so we should choose h2.
alpn = {"h3-26"_ns, "h2"_ns, "http/1.1"_ns};
result = SelectAlpnFromAlpnList(alpn, false, false);
ASSERT_EQ(Get<0>(result), "h2"_ns);
ASSERT_EQ(Get<1>(result), false);
// h3 is disabled, so we will use h2.
alpn = {"h3-29"_ns, "h3-30"_ns, "h2"_ns, "http/1.1"_ns};
result = SelectAlpnFromAlpnList(alpn, false, true);
ASSERT_EQ(Get<0>(result), "h2"_ns);
ASSERT_EQ(Get<1>(result), false);
// h3 is disabled and h2 is not found, so we will select http/1.1.
alpn = {"h3-29"_ns, "h3-30"_ns, "http/1.1"_ns};
result = SelectAlpnFromAlpnList(alpn, false, true);
ASSERT_EQ(Get<0>(result), "http/1.1"_ns);
ASSERT_EQ(Get<1>(result), false);
// h3 and h2 are disabled, so we will select http/1.1.
alpn = {"h3-29"_ns, "h3-30"_ns, "h2"_ns, "http/1.1"_ns};
result = SelectAlpnFromAlpnList(alpn, true, true);
ASSERT_EQ(Get<0>(result), "http/1.1"_ns);
ASSERT_EQ(Get<1>(result), false);
// h3 and h2 are disabled and http1.1 is not found, we return an empty string.
alpn = {"h3-29"_ns, "h3-30"_ns, "h2"_ns};
result = SelectAlpnFromAlpnList(alpn, true, true);
ASSERT_EQ(Get<0>(result), ""_ns);
ASSERT_EQ(Get<1>(result), false);
// No supported alpn.
alpn = {"ftp"_ns, "h2c"_ns};
result = SelectAlpnFromAlpnList(alpn, true, true);
ASSERT_EQ(Get<0>(result), ""_ns);
ASSERT_EQ(Get<1>(result), false);
}
} // namespace net
} // namespace mozilla

View File

@ -23,7 +23,6 @@ UNIFIED_SOURCES += [
"TestServerTimingHeader.cpp",
"TestSocketTransportService.cpp",
"TestStandardURL.cpp",
"TestSupportAlpn.cpp",
"TestUDPSocket.cpp",
]

View File

@ -51,6 +51,7 @@ registerCleanupFunction(async () => {
Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
Services.prefs.clearUserPref("network.dns.echconfig.enabled");
Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
Services.prefs.clearUserPref("network.dns.echconfig.fallback_to_origin");
Services.prefs.clearUserPref("network.dns.httpssvc.reset_exclustion_list");
Services.prefs.clearUserPref("network.http.http3.enabled");
@ -367,6 +368,7 @@ add_task(async function testFastfallbackWithEchConfig() {
Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setCharPref(
@ -832,3 +834,64 @@ add_task(async function testH3FastFallbackWithMultipleTransactions() {
await trrServer.stop();
});
add_task(async function testFastfallbackToTheSameRecord() {
trrServer = new TRRServer();
await trrServer.start();
Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${trrServer.port}/dns-query`
);
Services.prefs.setBoolPref("network.http.http3.enabled", true);
Services.prefs.setIntPref(
"network.dns.httpssvc.http3_fast_fallback_timeout",
1000
);
await trrServer.registerDoHAnswers("test.ech.org", "HTTPS", {
answers: [
{
name: "test.ech.org",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 1,
name: "test.ech1.org",
values: [
{ key: "alpn", value: ["h3-29", "h2"] },
{ key: "port", value: h2Port },
{ key: "echconfig", value: "456..." },
],
},
},
],
});
await trrServer.registerDoHAnswers("test.ech1.org", "A", {
answers: [
{
name: "test.ech1.org",
ttl: 55,
type: "A",
flush: false,
data: "127.0.0.1",
},
],
});
let chan = makeChan(`https://test.ech.org/server-timing`);
let [req] = await channelOpenPromise(chan);
Assert.equal(req.protocolVersion, "h2");
let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
Assert.equal(internal.remotePort, h2Port);
await trrServer.stop();
});

View File

@ -0,0 +1,452 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
var { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
let trrServer;
const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
Ci.nsIDNSService
);
function setup() {
trr_test_setup();
Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
}
setup();
registerCleanupFunction(async () => {
trr_clear_prefs();
Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
Services.prefs.clearUserPref("network.dns.echconfig.enabled");
Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
if (trrServer) {
await trrServer.stop();
}
});
function checkResult(inRecord, noHttp2, noHttp3, result) {
if (!result) {
Assert.throws(
() => {
inRecord
.QueryInterface(Ci.nsIDNSHTTPSSVCRecord)
.GetServiceModeRecord(noHttp2, noHttp3);
},
/NS_ERROR_NOT_AVAILABLE/,
"Should get an error"
);
return;
}
let record = inRecord
.QueryInterface(Ci.nsIDNSHTTPSSVCRecord)
.GetServiceModeRecord(noHttp2, noHttp3);
Assert.equal(record.priority, result.expectedPriority);
Assert.equal(record.name, result.expectedName);
Assert.equal(record.selectedAlpn, result.expectedAlpn);
}
// Test configuration:
// There are two records: one has a echConfig and the other doesn't.
// We want to test if the record with echConfig is preferred.
add_task(async function testEchConfigEnabled() {
let trrServer = new TRRServer();
await trrServer.start();
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${trrServer.port}/dns-query`
);
Services.prefs.setBoolPref("network.dns.echconfig.enabled", false);
await trrServer.registerDoHAnswers("test.bar.com", "HTTPS", {
answers: [
{
name: "test.bar.com",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 1,
name: "test.bar_1.com",
values: [{ key: "alpn", value: ["h3-29"] }],
},
},
{
name: "test.bar.com",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 2,
name: "test.bar_2.com",
values: [
{ key: "alpn", value: ["h2"] },
{ key: "echconfig", value: "456..." },
],
},
},
],
});
let [, inRecord] = await new TRRDNSListener("test.bar.com", {
type: dns.RESOLVE_TYPE_HTTPSSVC,
});
checkResult(inRecord, false, false, {
expectedPriority: 1,
expectedName: "test.bar_1.com",
expectedAlpn: "h3-29",
});
checkResult(inRecord, false, true, {
expectedPriority: 2,
expectedName: "test.bar_2.com",
expectedAlpn: "h2",
});
checkResult(inRecord, true, false, {
expectedPriority: 1,
expectedName: "test.bar_1.com",
expectedAlpn: "h3-29",
});
checkResult(inRecord, true, true);
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
dns.clearCache(true);
[, inRecord] = await new TRRDNSListener("test.bar.com", {
type: dns.RESOLVE_TYPE_HTTPSSVC,
});
checkResult(inRecord, false, false, {
expectedPriority: 2,
expectedName: "test.bar_2.com",
expectedAlpn: "h2",
});
checkResult(inRecord, false, true, {
expectedPriority: 2,
expectedName: "test.bar_2.com",
expectedAlpn: "h2",
});
checkResult(inRecord, true, false, {
expectedPriority: 1,
expectedName: "test.bar_1.com",
expectedAlpn: "h3-29",
});
checkResult(inRecord, true, true);
await trrServer.stop();
trrServer = null;
});
// Test configuration:
// There are two records: both have echConfigs, and only one supports h3.
// This test is about testing which record should we get when
// network.dns.http3_echconfig.enabled is true and false.
// When network.dns.http3_echconfig.enabled is false, we should try to
// connect with h2 and echConfig.
add_task(async function testTwoRecordsHaveEchConfig() {
dns.clearCache(true);
let trrServer = new TRRServer();
await trrServer.start();
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${trrServer.port}/dns-query`
);
await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
answers: [
{
name: "test.foo.com",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 1,
name: "test.foo_h3.com",
values: [
{ key: "alpn", value: ["h3"] },
{ key: "echconfig", value: "456..." },
],
},
},
{
name: "test.foo.com",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 2,
name: "test.foo_h2.com",
values: [
{ key: "alpn", value: ["h2"] },
{ key: "echconfig", value: "456..." },
],
},
},
],
});
let [, inRecord] = await new TRRDNSListener("test.foo.com", {
type: dns.RESOLVE_TYPE_HTTPSSVC,
});
checkResult(inRecord, false, false, {
expectedPriority: 2,
expectedName: "test.foo_h2.com",
expectedAlpn: "h2",
});
checkResult(inRecord, false, true, {
expectedPriority: 2,
expectedName: "test.foo_h2.com",
expectedAlpn: "h2",
});
checkResult(inRecord, true, false, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h3",
});
checkResult(inRecord, true, true);
Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
dns.clearCache(true);
[, inRecord] = await new TRRDNSListener("test.foo.com", {
type: dns.RESOLVE_TYPE_HTTPSSVC,
});
checkResult(inRecord, false, false, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h3",
});
checkResult(inRecord, false, true, {
expectedPriority: 2,
expectedName: "test.foo_h2.com",
expectedAlpn: "h2",
});
checkResult(inRecord, true, false, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h3",
});
checkResult(inRecord, true, true);
await trrServer.stop();
trrServer = null;
});
// Test configuration:
// There are two records: both have echConfigs, and one supports h3 and h2.
// When network.dns.http3_echconfig.enabled is false, we should use the record
// that supports h3 and h2 (the alpn is h2).
add_task(async function testTwoRecordsHaveEchConfig1() {
dns.clearCache(true);
let trrServer = new TRRServer();
await trrServer.start();
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${trrServer.port}/dns-query`
);
await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
answers: [
{
name: "test.foo.com",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 1,
name: "test.foo_h3.com",
values: [
{ key: "alpn", value: ["h3", "h2"] },
{ key: "echconfig", value: "456..." },
],
},
},
{
name: "test.foo.com",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 2,
name: "test.foo_h2.com",
values: [
{ key: "alpn", value: ["h2", "http/1.1"] },
{ key: "echconfig", value: "456..." },
],
},
},
],
});
let [, inRecord] = await new TRRDNSListener("test.foo.com", {
type: dns.RESOLVE_TYPE_HTTPSSVC,
});
checkResult(inRecord, false, false, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h2",
});
checkResult(inRecord, false, true, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h2",
});
checkResult(inRecord, true, false, {
expectedPriority: 2,
expectedName: "test.foo_h2.com",
expectedAlpn: "http/1.1",
});
checkResult(inRecord, true, true, {
expectedPriority: 2,
expectedName: "test.foo_h2.com",
expectedAlpn: "http/1.1",
});
Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
dns.clearCache(true);
[, inRecord] = await new TRRDNSListener("test.foo.com", {
type: dns.RESOLVE_TYPE_HTTPSSVC,
});
checkResult(inRecord, false, false, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h3",
});
checkResult(inRecord, false, true, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h2",
});
checkResult(inRecord, true, false, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h3",
});
checkResult(inRecord, true, true, {
expectedPriority: 2,
expectedName: "test.foo_h2.com",
expectedAlpn: "http/1.1",
});
await trrServer.stop();
trrServer = null;
});
// Test configuration:
// There are two records: only one support h3 and only one has echConfig.
// This test is about never usng the record without echConfig.
add_task(async function testOneRecordsHasEchConfig() {
dns.clearCache(true);
let trrServer = new TRRServer();
await trrServer.start();
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${trrServer.port}/dns-query`
);
await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
answers: [
{
name: "test.foo.com",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 1,
name: "test.foo_h3.com",
values: [
{ key: "alpn", value: ["h3"] },
{ key: "echconfig", value: "456..." },
],
},
},
{
name: "test.foo.com",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 2,
name: "test.foo_h2.com",
values: [{ key: "alpn", value: ["h2"] }],
},
},
],
});
let [, inRecord] = await new TRRDNSListener("test.foo.com", {
type: dns.RESOLVE_TYPE_HTTPSSVC,
});
checkResult(inRecord, false, false, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h3",
});
checkResult(inRecord, false, true, {
expectedPriority: 2,
expectedName: "test.foo_h2.com",
expectedAlpn: "h2",
});
checkResult(inRecord, true, false, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h3",
});
checkResult(inRecord, true, true);
Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
dns.clearCache(true);
[, inRecord] = await new TRRDNSListener("test.foo.com", {
type: dns.RESOLVE_TYPE_HTTPSSVC,
});
checkResult(inRecord, false, false, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h3",
});
checkResult(inRecord, false, true, {
expectedPriority: 2,
expectedName: "test.foo_h2.com",
expectedAlpn: "h2",
});
checkResult(inRecord, true, false, {
expectedPriority: 1,
expectedName: "test.foo_h3.com",
expectedAlpn: "h3",
});
checkResult(inRecord, true, true);
await trrServer.stop();
trrServer = null;
});

View File

@ -25,6 +25,7 @@ function setup() {
Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
add_tls_server_setup(
"EncryptedClientHelloServer",
@ -49,6 +50,7 @@ registerCleanupFunction(async () => {
Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
Services.prefs.clearUserPref("network.dns.echconfig.enabled");
Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
Services.prefs.clearUserPref("network.dns.echconfig.fallback_to_origin");
if (trrServer) {
await trrServer.stop();

View File

@ -540,3 +540,6 @@ skip-if = tsan || os =='android'
run-sequentially = node server exceptions dont replay well
[test_bug1725766.js]
skip-if = os == "android" # skip because of bug 1589327
[test_https_rr_ech_prefs.js]
skip-if = os == "android"
run-sequentially = node server exceptions dont replay well