From 7ba32109b0883076cbb443b3057de1c14b1c6a78 Mon Sep 17 00:00:00 2001 From: Valentin Gosu Date: Wed, 31 May 2023 15:05:06 +0000 Subject: [PATCH] Bug 1835805 - Remove odoh code r=necko-reviewers,jesup Differential Revision: https://phabricator.services.mozilla.com/D179456 --- .prettierignore | 1 - modules/libpref/init/StaticPrefList.yaml | 36 -- netwerk/base/nsLoadGroup.cpp | 17 - netwerk/dns/ChildDNSService.cpp | 17 - netwerk/dns/ChildDNSService.h | 2 - netwerk/dns/DNS.h | 39 +- netwerk/dns/DNSPacket.cpp | 548 ------------------ netwerk/dns/DNSPacket.h | 33 -- netwerk/dns/DNSUtils.h | 2 - netwerk/dns/ODoH.cpp | 112 ---- netwerk/dns/ODoH.h | 60 -- netwerk/dns/ODoHService.cpp | 521 ----------------- netwerk/dns/ODoHService.h | 80 --- netwerk/dns/TRR.cpp | 5 +- netwerk/dns/TRRQuery.cpp | 37 +- netwerk/dns/TRRQuery.h | 9 +- netwerk/dns/TRRService.cpp | 5 - netwerk/dns/TRRService.h | 3 - netwerk/dns/moz.build | 2 - netwerk/dns/nsDNSService2.cpp | 19 +- netwerk/dns/nsDNSService2.h | 1 - netwerk/dns/nsHostRecord.cpp | 17 +- netwerk/dns/nsHostRecord.h | 2 +- netwerk/dns/nsHostResolver.cpp | 11 +- netwerk/dns/nsIDNSService.idl | 8 - netwerk/ipc/PSocketProcess.ipdl | 1 - netwerk/ipc/SocketProcessParent.cpp | 12 - netwerk/ipc/SocketProcessParent.h | 2 - netwerk/protocol/http/TRRServiceChannel.cpp | 7 +- netwerk/protocol/http/nsHttpChannel.cpp | 12 +- netwerk/socket/nsISocketProvider.idl | 2 +- netwerk/test/unit/test_odoh.js | 295 ---------- netwerk/test/unit/test_trr.js | 1 - netwerk/test/unit/test_trr_telemetry.js | 1 - netwerk/test/unit/test_trr_with_proxy.js | 1 - netwerk/test/unit/trr_common.js | 53 +- netwerk/test/unit/xpcshell.ini | 5 - .../mozbuild/mozbuild/action/test_archive.py | 1 - testing/xpcshell/moz-http2/moz-http2.js | 169 ------ testing/xpcshell/odoh-wasm/Cargo.toml | 42 -- testing/xpcshell/odoh-wasm/LICENSE_APACHE | 176 ------ testing/xpcshell/odoh-wasm/LICENSE_MIT | 25 - testing/xpcshell/odoh-wasm/README.md | 75 --- testing/xpcshell/odoh-wasm/pkg/README.md | 69 --- testing/xpcshell/odoh-wasm/pkg/odoh_wasm.d.ts | 16 - testing/xpcshell/odoh-wasm/pkg/odoh_wasm.js | 132 ----- .../xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm | Bin 165175 -> 0 bytes .../odoh-wasm/pkg/odoh_wasm_bg.wasm.d.ts | 7 - testing/xpcshell/odoh-wasm/pkg/package.json | 15 - testing/xpcshell/odoh-wasm/src/lib.rs | 158 ----- toolkit/components/telemetry/Histograms.json | 53 -- tools/rewriting/ThirdPartyPaths.txt | 1 - 52 files changed, 35 insertions(+), 2883 deletions(-) delete mode 100644 netwerk/dns/ODoH.cpp delete mode 100644 netwerk/dns/ODoH.h delete mode 100644 netwerk/dns/ODoHService.cpp delete mode 100644 netwerk/dns/ODoHService.h delete mode 100644 netwerk/test/unit/test_odoh.js delete mode 100644 testing/xpcshell/odoh-wasm/Cargo.toml delete mode 100644 testing/xpcshell/odoh-wasm/LICENSE_APACHE delete mode 100644 testing/xpcshell/odoh-wasm/LICENSE_MIT delete mode 100644 testing/xpcshell/odoh-wasm/README.md delete mode 100644 testing/xpcshell/odoh-wasm/pkg/README.md delete mode 100644 testing/xpcshell/odoh-wasm/pkg/odoh_wasm.d.ts delete mode 100644 testing/xpcshell/odoh-wasm/pkg/odoh_wasm.js delete mode 100644 testing/xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm delete mode 100644 testing/xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm.d.ts delete mode 100644 testing/xpcshell/odoh-wasm/pkg/package.json delete mode 100644 testing/xpcshell/odoh-wasm/src/lib.rs diff --git a/.prettierignore b/.prettierignore index da211ddbb823..94faab9d4795 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1424,7 +1424,6 @@ testing/xpcshell/dns-packet/ testing/xpcshell/node_ip/ testing/xpcshell/node-http2/ testing/xpcshell/node-ws/ -testing/xpcshell/odoh-wasm/ third_party/ toolkit/components/certviewer/content/vendor/ toolkit/components/jsoncpp/ diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index a96c430d3ab9..44a39c8239f5 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -12199,42 +12199,6 @@ value: true mirror: always -# Whether to enable odoh. -- name: network.trr.odoh.enabled - type: RelaxedAtomicBool - value: false - mirror: always - -# The uri of Oblivious Proxy. -- name: network.trr.odoh.proxy_uri - type: String - value: "" - mirror: never - -# The host name of Oblivious Target. -- name: network.trr.odoh.target_host - type: String - value: "" - mirror: never - -# The uri path of the odoh uri. -- name: network.trr.odoh.target_path - type: String - value: "" - mirror: never - -# The minimum ttl of the DNS record that contains ODoHConfigs. -- name: network.trr.odoh.min_ttl - type: RelaxedAtomicUint32 - value: 60 - mirror: always - -# The uri indicates where to get ODoHConfigs. -- name: network.trr.odoh.configs_uri - type: String - value: "" - mirror: never - # Whether to add padding in the doh dns queries (rfc 7830) - name: network.trr.padding type: RelaxedAtomicBool diff --git a/netwerk/base/nsLoadGroup.cpp b/netwerk/base/nsLoadGroup.cpp index cd6c89fb5590..e3f88fc55467 100644 --- a/netwerk/base/nsLoadGroup.cpp +++ b/netwerk/base/nsLoadGroup.cpp @@ -1001,23 +1001,6 @@ void nsLoadGroup::TelemetryReportChannel(nsITimedChannel* aTimedChannel, asyncOpen, requestStart); } - if (StaticPrefs::network_trr_odoh_enabled() && !domainLookupStart.IsNull() && - !domainLookupEnd.IsNull()) { - nsCOMPtr dns = do_GetService(NS_DNSSERVICE_CONTRACTID); - bool ODoHActivated = false; - if (dns && NS_SUCCEEDED(dns->GetODoHActivated(&ODoHActivated)) && - ODoHActivated) { - if (aDefaultRequest) { - Telemetry::AccumulateTimeDelta( - Telemetry::HTTP_PAGE_DNS_ODOH_LOOKUP_TIME, domainLookupStart, - domainLookupEnd); - } else { - Telemetry::AccumulateTimeDelta(Telemetry::HTTP_SUB_DNS_ODOH_LOOKUP_TIME, - domainLookupStart, domainLookupEnd); - } - } - } - #undef HTTP_REQUEST_HISTOGRAMS } diff --git a/netwerk/dns/ChildDNSService.cpp b/netwerk/dns/ChildDNSService.cpp index b171e8047306..8ef9f3332934 100644 --- a/netwerk/dns/ChildDNSService.cpp +++ b/netwerk/dns/ChildDNSService.cpp @@ -386,14 +386,6 @@ ChildDNSService::GetMyHostName(nsACString& result) { return NS_ERROR_NOT_AVAILABLE; } -NS_IMETHODIMP -ChildDNSService::GetODoHActivated(bool* aResult) { - NS_ENSURE_ARG(aResult); - - *aResult = mODoHActivated; - return NS_OK; -} - void ChildDNSService::NotifyRequestDone(DNSRequestSender* aDnsRequest) { // We need the original flags and listener for the pending requests hash. nsIDNSService::DNSFlags originalFlags = @@ -438,12 +430,6 @@ nsresult ChildDNSService::Init() { AddPrefObserver(prefs); } - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - if (observerService) { - observerService->AddObserver(this, "odoh-service-activated", false); - } - return NS_OK; } @@ -489,10 +475,7 @@ ChildDNSService::Observe(nsISupports* subject, const char* topic, if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { // Reread prefs ReadPrefs(NS_ConvertUTF16toUTF8(data).get()); - } else if (!strcmp(topic, "odoh-service-activated")) { - mODoHActivated = u"true"_ns.Equals(data); } - return NS_OK; } diff --git a/netwerk/dns/ChildDNSService.h b/netwerk/dns/ChildDNSService.h index 35b2bf30f9dd..611f8b871f49 100644 --- a/netwerk/dns/ChildDNSService.h +++ b/netwerk/dns/ChildDNSService.h @@ -60,8 +60,6 @@ class ChildDNSService final : public DNSServiceBase, public nsPIDNSService { nsIDNSListener* aListener, nsresult aReason, const OriginAttributes& aOriginAttributes); - bool mODoHActivated = false; - // We need to remember pending dns requests to be able to cancel them. nsClassHashtable>> mPendingRequests; diff --git a/netwerk/dns/DNS.h b/netwerk/dns/DNS.h index 838e16980183..a0dbbaa17db7 100644 --- a/netwerk/dns/DNS.h +++ b/netwerk/dns/DNS.h @@ -156,39 +156,7 @@ union NetAddr { nsCString ToString() const; }; -#define ODOH_VERSION 0x0001 -static const char kODoHQuery[] = "odoh query"; -static const char hODoHConfigID[] = "odoh key id"; -static const char kODoHResponse[] = "odoh response"; -static const char kODoHKey[] = "odoh key"; -static const char kODoHNonce[] = "odoh nonce"; - -struct ObliviousDoHConfigContents { - uint16_t mKemId{}; - uint16_t mKdfId{}; - uint16_t mAeadId{}; - nsTArray mPublicKey; -}; - -struct ObliviousDoHConfig { - uint16_t mVersion{}; - uint16_t mLength{}; - ObliviousDoHConfigContents mContents; - nsTArray mConfigId; -}; - -enum ObliviousDoHMessageType : uint8_t { - ODOH_QUERY = 1, - ODOH_RESPONSE = 2, -}; - -struct ObliviousDoHMessage { - ObliviousDoHMessageType mType{ODOH_QUERY}; - nsTArray mKeyId; - nsTArray mEncryptedMessage; -}; - -enum class DNSResolverType : uint32_t { Native = 0, TRR, ODoH }; +enum class DNSResolverType : uint32_t { Native = 0, TRR }; class AddrInfo { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AddrInfo) @@ -216,10 +184,7 @@ class AddrInfo { size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; - bool IsTRROrODoH() const { - return mResolverType == DNSResolverType::TRR || - mResolverType == DNSResolverType::ODoH; - } + bool IsTRR() const { return mResolverType == DNSResolverType::TRR; } DNSResolverType ResolverType() const { return mResolverType; } unsigned int TRRType() { return mTRRType; } diff --git a/netwerk/dns/DNSPacket.cpp b/netwerk/dns/DNSPacket.cpp index 8d470cf00776..b41403214aa0 100644 --- a/netwerk/dns/DNSPacket.cpp +++ b/netwerk/dns/DNSPacket.cpp @@ -8,7 +8,6 @@ #include "mozilla/EndianUtils.h" #include "mozilla/ScopeExit.h" #include "mozilla/StaticPrefs_network.h" -#include "ODoHService.h" // Put DNSLogging.h at the end to avoid LOG being overwritten by other headers. #include "DNSLogging.h" @@ -1077,552 +1076,5 @@ nsresult DNSPacket::Decode( return rv; } -static SECItem* CreateRawConfig(const ObliviousDoHConfig& aConfig) { - SECItem* item(::SECITEM_AllocItem(nullptr, nullptr, - 8 + aConfig.mContents.mPublicKey.Length())); - if (!item) { - return nullptr; - } - - uint16_t index = 0; - NetworkEndian::writeUint16(&item->data[index], aConfig.mContents.mKemId); - index += 2; - NetworkEndian::writeUint16(&item->data[index], aConfig.mContents.mKdfId); - index += 2; - NetworkEndian::writeUint16(&item->data[index], aConfig.mContents.mAeadId); - index += 2; - uint16_t keyLength = aConfig.mContents.mPublicKey.Length(); - NetworkEndian::writeUint16(&item->data[index], keyLength); - index += 2; - memcpy(&item->data[index], aConfig.mContents.mPublicKey.Elements(), - aConfig.mContents.mPublicKey.Length()); - return item; -} - -static bool CreateConfigId(ObliviousDoHConfig& aConfig) { - SECStatus rv; - CK_HKDF_PARAMS params = {0}; - SECItem paramsi = {siBuffer, (unsigned char*)¶ms, sizeof(params)}; - - UniquePK11SlotInfo slot(PK11_GetInternalSlot()); - if (!slot) { - return false; - } - - UniqueSECItem rawConfig(CreateRawConfig(aConfig)); - if (!rawConfig) { - return false; - } - - UniquePK11SymKey configKey(PK11_ImportDataKey(slot.get(), CKM_HKDF_DATA, - PK11_OriginUnwrap, CKA_DERIVE, - rawConfig.get(), nullptr)); - if (!configKey) { - return false; - } - - params.bExtract = CK_TRUE; - params.bExpand = CK_TRUE; - params.prfHashMechanism = CKM_SHA256; - params.ulSaltType = CKF_HKDF_SALT_NULL; - params.pInfo = (unsigned char*)&hODoHConfigID[0]; - params.ulInfoLen = strlen(hODoHConfigID); - UniquePK11SymKey derived(PK11_DeriveWithFlags( - configKey.get(), CKM_HKDF_DATA, ¶msi, CKM_HKDF_DERIVE, CKA_DERIVE, - SHA256_LENGTH, CKF_SIGN | CKF_VERIFY)); - - rv = PK11_ExtractKeyValue(derived.get()); - if (rv != SECSuccess) { - return false; - } - - SECItem* derivedItem = PK11_GetKeyData(derived.get()); - if (!derivedItem) { - return false; - } - - if (derivedItem->len != SHA256_LENGTH) { - return false; - } - - aConfig.mConfigId.AppendElements(derivedItem->data, derivedItem->len); - return true; -} - -// static -bool ODoHDNSPacket::ParseODoHConfigs(Span aData, - nsTArray& aOut) { - // struct { - // uint16 kem_id; - // uint16 kdf_id; - // uint16 aead_id; - // opaque public_key<1..2^16-1>; - // } ObliviousDoHConfigContents; - // - // struct { - // uint16 version; - // uint16 length; - // select (ObliviousDoHConfig.version) { - // case 0xff03: ObliviousDoHConfigContents contents; - // } - // } ObliviousDoHConfig; - // - // ObliviousDoHConfig ObliviousDoHConfigs<1..2^16-1>; - - Span::const_iterator it = aData.begin(); - uint16_t length = 0; - if (!get16bit(aData, it, length)) { - return false; - } - - if (length != aData.Length() - 2) { - return false; - } - - nsTArray result; - static const int kMinimumConfigContentLength = 12; - while (std::distance(it, aData.cend()) > kMinimumConfigContentLength) { - ObliviousDoHConfig config; - if (!get16bit(aData, it, config.mVersion)) { - return false; - } - - if (!get16bit(aData, it, config.mLength)) { - return false; - } - - if (std::distance(it, aData.cend()) < config.mLength) { - return false; - } - - if (!get16bit(aData, it, config.mContents.mKemId)) { - return false; - } - - if (!get16bit(aData, it, config.mContents.mKdfId)) { - return false; - } - - if (!get16bit(aData, it, config.mContents.mAeadId)) { - return false; - } - - uint16_t keyLength = 0; - if (!get16bit(aData, it, keyLength)) { - return false; - } - - if (!keyLength || std::distance(it, aData.cend()) < keyLength) { - return false; - } - - config.mContents.mPublicKey.AppendElements(Span(it, it + keyLength)); - it += keyLength; - - CreateConfigId(config); - - // Check if the version of the config is supported and validate its content. - if (config.mVersion == ODOH_VERSION && - PK11_HPKE_ValidateParameters( - static_cast(config.mContents.mKemId), - static_cast(config.mContents.mKdfId), - static_cast(config.mContents.mAeadId)) == SECSuccess) { - result.AppendElement(std::move(config)); - } else { - LOG(("ODoHDNSPacket::ParseODoHConfigs got an invalid config")); - } - } - - aOut = std::move(result); - return true; -} - -ODoHDNSPacket::~ODoHDNSPacket() { PK11_HPKE_DestroyContext(mContext, true); } - -nsresult ODoHDNSPacket::EncodeRequest(nsCString& aBody, const nsACString& aHost, - uint16_t aType, bool aDisableECS) { - nsAutoCString queryBody; - nsresult rv = DNSPacket::EncodeRequest(queryBody, aHost, aType, aDisableECS); - if (NS_FAILED(rv)) { - SetDNSPacketStatus(DNSPacketStatus::EncodeError); - return rv; - } - - if (!gODoHService->ODoHConfigs()) { - SetDNSPacketStatus(DNSPacketStatus::KeyNotAvailable); - return NS_ERROR_FAILURE; - } - - if (gODoHService->ODoHConfigs()->IsEmpty()) { - SetDNSPacketStatus(DNSPacketStatus::KeyNotUsable); - return NS_ERROR_FAILURE; - } - - // We only use the first ODoHConfig. - const ObliviousDoHConfig& config = (*gODoHService->ODoHConfigs())[0]; - - ObliviousDoHMessage message; - // The spec didn't recommand padding length for encryption, let's use 0 here. - if (!EncryptDNSQuery(queryBody, 0, config, message)) { - SetDNSPacketStatus(DNSPacketStatus::EncryptError); - return NS_ERROR_FAILURE; - } - - aBody.Truncate(); - aBody += message.mType; - uint16_t keyIdLength = message.mKeyId.Length(); - aBody += static_cast(keyIdLength >> 8); - aBody += static_cast(keyIdLength); - aBody.Append(reinterpret_cast(message.mKeyId.Elements()), - keyIdLength); - uint16_t messageLen = message.mEncryptedMessage.Length(); - aBody += static_cast(messageLen >> 8); - aBody += static_cast(messageLen); - aBody.Append( - reinterpret_cast(message.mEncryptedMessage.Elements()), - messageLen); - - SetDNSPacketStatus(DNSPacketStatus::Success); - return NS_OK; -} - -/* - * def encrypt_query_body(pkR, key_id, Q_plain): - * enc, context = SetupBaseS(pkR, "odoh query") - * aad = 0x01 || len(key_id) || key_id - * ct = context.Seal(aad, Q_plain) - * Q_encrypted = enc || ct - * return Q_encrypted - */ -bool ODoHDNSPacket::EncryptDNSQuery(const nsACString& aQuery, - uint16_t aPaddingLen, - const ObliviousDoHConfig& aConfig, - ObliviousDoHMessage& aOut) { - mContext = PK11_HPKE_NewContext( - static_cast(aConfig.mContents.mKemId), - static_cast(aConfig.mContents.mKdfId), - static_cast(aConfig.mContents.mAeadId), nullptr, nullptr); - if (!mContext) { - LOG(("ODoHDNSPacket::EncryptDNSQuery create context failed")); - return false; - } - - SECKEYPublicKey* pkR; - SECStatus rv = - PK11_HPKE_Deserialize(mContext, aConfig.mContents.mPublicKey.Elements(), - aConfig.mContents.mPublicKey.Length(), &pkR); - if (rv != SECSuccess) { - return false; - } - - UniqueSECItem hpkeInfo( - ::SECITEM_AllocItem(nullptr, nullptr, strlen(kODoHQuery))); - if (!hpkeInfo) { - return false; - } - - memcpy(hpkeInfo->data, kODoHQuery, strlen(kODoHQuery)); - - rv = PK11_HPKE_SetupS(mContext, nullptr, nullptr, pkR, hpkeInfo.get()); - if (rv != SECSuccess) { - LOG(("ODoHDNSPacket::EncryptDNSQuery setupS failed")); - return false; - } - - const SECItem* hpkeEnc = PK11_HPKE_GetEncapPubKey(mContext); - if (!hpkeEnc) { - return false; - } - - // aad = 0x01 || len(key_id) || key_id - UniqueSECItem aad(::SECITEM_AllocItem(nullptr, nullptr, - 1 + 2 + aConfig.mConfigId.Length())); - if (!aad) { - return false; - } - - aad->data[0] = ODOH_QUERY; - NetworkEndian::writeUint16(&aad->data[1], aConfig.mConfigId.Length()); - memcpy(&aad->data[3], aConfig.mConfigId.Elements(), - aConfig.mConfigId.Length()); - - // struct { - // opaque dns_message<1..2^16-1>; - // opaque padding<0..2^16-1>; - // } ObliviousDoHMessagePlaintext; - SECItem* odohPlainText(::SECITEM_AllocItem( - nullptr, nullptr, 2 + aQuery.Length() + 2 + aPaddingLen)); - if (!odohPlainText) { - return false; - } - - mPlainQuery.reset(odohPlainText); - memset(mPlainQuery->data, 0, mPlainQuery->len); - NetworkEndian::writeUint16(&mPlainQuery->data[0], aQuery.Length()); - memcpy(&mPlainQuery->data[2], aQuery.BeginReading(), aQuery.Length()); - NetworkEndian::writeUint16(&mPlainQuery->data[2 + aQuery.Length()], - aPaddingLen); - - SECItem* chCt = nullptr; - rv = PK11_HPKE_Seal(mContext, aad.get(), mPlainQuery.get(), &chCt); - if (rv != SECSuccess) { - LOG(("ODoHDNSPacket::EncryptDNSQuery seal failed")); - return false; - } - - UniqueSECItem ct(chCt); - - aOut.mType = ODOH_QUERY; - aOut.mKeyId.AppendElements(aConfig.mConfigId); - aOut.mEncryptedMessage.AppendElements(Span(hpkeEnc->data, hpkeEnc->len)); - aOut.mEncryptedMessage.AppendElements(Span(ct->data, ct->len)); - - return true; -} - -nsresult ODoHDNSPacket::Decode( - nsCString& aHost, enum TrrType aType, nsCString& aCname, bool aAllowRFC1918, - DOHresp& aResp, TypeRecordResultType& aTypeResult, - nsClassHashtable& aAdditionalRecords, - uint32_t& aTTL) { - // This function could be called multiple times when we are checking CNAME - // records, but we only need to decrypt the response once. - if (!mDecryptedResponseRange) { - if (!DecryptDNSResponse()) { - SetDNSPacketStatus(DNSPacketStatus::DecryptError); - return NS_ERROR_FAILURE; - } - - uint32_t index = 0; - uint16_t responseLength = get16bit(mResponse, index); - index += 2; - - if (mBodySize < (index + responseLength)) { - SetDNSPacketStatus(DNSPacketStatus::DecryptError); - return NS_ERROR_ILLEGAL_VALUE; - } - - DecryptedResponseRange range; - range.mStart = index; - range.mLength = responseLength; - - index += responseLength; - uint16_t paddingLen = get16bit(mResponse, index); - - if (static_cast(4 + responseLength + paddingLen) != - mBodySize) { - SetDNSPacketStatus(DNSPacketStatus::DecryptError); - return NS_ERROR_ILLEGAL_VALUE; - } - - mDecryptedResponseRange.emplace(range); - } - - nsresult rv = DecodeInternal(aHost, aType, aCname, aAllowRFC1918, aResp, - aTypeResult, aAdditionalRecords, aTTL, - &mResponse[mDecryptedResponseRange->mStart], - mDecryptedResponseRange->mLength); - SetDNSPacketStatus(NS_SUCCEEDED(rv) ? DNSPacketStatus::Success - : DNSPacketStatus::DecodeError); - return rv; -} - -static bool CreateObliviousDoHMessage(const unsigned char* aData, - unsigned int aLength, - ObliviousDoHMessage& aOut) { - if (aLength < 5) { - return false; - } - - unsigned int index = 0; - aOut.mType = static_cast(aData[index++]); - - uint16_t keyIdLength = get16bit(aData, index); - index += 2; - if (aLength < (index + keyIdLength)) { - return false; - } - - aOut.mKeyId.AppendElements(Span(aData + index, keyIdLength)); - index += keyIdLength; - - uint16_t messageLen = get16bit(aData, index); - index += 2; - if (aLength < (index + messageLen)) { - return false; - } - - aOut.mEncryptedMessage.AppendElements(Span(aData + index, messageLen)); - return true; -} - -static SECStatus HKDFExtract(SECItem* aSalt, PK11SymKey* aIkm, - UniquePK11SymKey& aOutKey) { - CK_HKDF_PARAMS params = {0}; - SECItem paramsItem = {siBuffer, (unsigned char*)¶ms, sizeof(params)}; - - params.bExtract = CK_TRUE; - params.bExpand = CK_FALSE; - params.prfHashMechanism = CKM_SHA256; - params.ulSaltType = aSalt ? CKF_HKDF_SALT_DATA : CKF_HKDF_SALT_NULL; - params.pSalt = aSalt ? (CK_BYTE_PTR)aSalt->data : nullptr; - params.ulSaltLen = aSalt ? aSalt->len : 0; - - UniquePK11SymKey prk(PK11_Derive(aIkm, CKM_HKDF_DERIVE, ¶msItem, - CKM_HKDF_DERIVE, CKA_DERIVE, 0)); - if (!prk) { - return SECFailure; - } - - aOutKey.swap(prk); - return SECSuccess; -} - -static SECStatus HKDFExpand(PK11SymKey* aPrk, const SECItem* aInfo, int aLen, - bool aKey, UniquePK11SymKey& aOutKey) { - CK_HKDF_PARAMS params = {0}; - SECItem paramsItem = {siBuffer, (unsigned char*)¶ms, sizeof(params)}; - - params.bExtract = CK_FALSE; - params.bExpand = CK_TRUE; - params.prfHashMechanism = CKM_SHA256; - params.ulSaltType = CKF_HKDF_SALT_NULL; - params.pInfo = (CK_BYTE_PTR)aInfo->data; - params.ulInfoLen = aInfo->len; - CK_MECHANISM_TYPE deriveMech = CKM_HKDF_DERIVE; - CK_MECHANISM_TYPE keyMech = aKey ? CKM_AES_GCM : CKM_HKDF_DERIVE; - - UniquePK11SymKey derivedKey( - PK11_Derive(aPrk, deriveMech, ¶msItem, keyMech, CKA_DERIVE, aLen)); - if (!derivedKey) { - return SECFailure; - } - - aOutKey.swap(derivedKey); - return SECSuccess; -} - -/* - * def decrypt_response_body(context, Q_plain, R_encrypted, response_nonce): - * aead_key, aead_nonce = derive_secrets(context, Q_plain, response_nonce) - * aad = 0x02 || len(response_nonce) || response_nonce - * R_plain, error = Open(key, nonce, aad, R_encrypted) - * return R_plain, error - */ -bool ODoHDNSPacket::DecryptDNSResponse() { - ObliviousDoHMessage message; - if (!CreateObliviousDoHMessage(mResponse, mBodySize, message)) { - LOG(("ODoHDNSPacket::DecryptDNSResponse invalid response")); - return false; - } - - if (message.mType != ODOH_RESPONSE) { - return false; - } - - const unsigned int kResponseNonceLen = 16; - // KeyId is actually response_nonce - if (message.mKeyId.Length() != kResponseNonceLen) { - return false; - } - - // def derive_secrets(context, Q_plain, response_nonce): - // secret = context.Export("odoh response", Nk) - // salt = Q_plain || len(response_nonce) || response_nonce - // prk = Extract(salt, secret) - // key = Expand(odoh_prk, "odoh key", Nk) - // nonce = Expand(odoh_prk, "odoh nonce", Nn) - // return key, nonce - const SECItem kODoHResponsetInfoItem = { - siBuffer, (unsigned char*)kODoHResponse, - static_cast(strlen(kODoHResponse))}; - const unsigned int kAes128GcmKeyLen = 16; - const unsigned int kAes128GcmNonceLen = 12; - PK11SymKey* tmp = nullptr; - SECStatus rv = PK11_HPKE_ExportSecret(mContext, &kODoHResponsetInfoItem, - kAes128GcmKeyLen, &tmp); - if (rv != SECSuccess) { - LOG(("ODoHDNSPacket::DecryptDNSResponse export secret failed")); - return false; - } - UniquePK11SymKey odohSecret(tmp); - - SECItem* salt(::SECITEM_AllocItem(nullptr, nullptr, - mPlainQuery->len + 2 + kResponseNonceLen)); - memcpy(salt->data, mPlainQuery->data, mPlainQuery->len); - NetworkEndian::writeUint16(&salt->data[mPlainQuery->len], kResponseNonceLen); - memcpy(salt->data + mPlainQuery->len + 2, message.mKeyId.Elements(), - kResponseNonceLen); - UniqueSECItem st(salt); - UniquePK11SymKey odohPrk; - rv = HKDFExtract(salt, odohSecret.get(), odohPrk); - if (rv != SECSuccess) { - LOG(("ODoHDNSPacket::DecryptDNSResponse extract failed")); - return false; - } - - SECItem keyInfoItem = {siBuffer, (unsigned char*)&kODoHKey[0], - static_cast(strlen(kODoHKey))}; - UniquePK11SymKey key; - rv = HKDFExpand(odohPrk.get(), &keyInfoItem, kAes128GcmKeyLen, true, key); - if (rv != SECSuccess) { - LOG(("ODoHDNSPacket::DecryptDNSResponse expand key failed")); - return false; - } - - SECItem nonceInfoItem = {siBuffer, (unsigned char*)&kODoHNonce[0], - static_cast(strlen(kODoHNonce))}; - UniquePK11SymKey nonce; - rv = HKDFExpand(odohPrk.get(), &nonceInfoItem, kAes128GcmNonceLen, false, - nonce); - if (rv != SECSuccess) { - LOG(("ODoHDNSPacket::DecryptDNSResponse expand nonce failed")); - return false; - } - - rv = PK11_ExtractKeyValue(nonce.get()); - if (rv != SECSuccess) { - return false; - } - - SECItem* derivedItem = PK11_GetKeyData(nonce.get()); - if (!derivedItem) { - return false; - } - - // aad = 0x02 || len(response_nonce) || response_nonce - SECItem* aadItem( - ::SECITEM_AllocItem(nullptr, nullptr, 1 + 2 + kResponseNonceLen)); - aadItem->data[0] = ODOH_RESPONSE; - NetworkEndian::writeUint16(&aadItem->data[1], kResponseNonceLen); - memcpy(&aadItem->data[3], message.mKeyId.Elements(), kResponseNonceLen); - UniqueSECItem aad(aadItem); - - SECItem paramItem; - CK_GCM_PARAMS param; - param.pIv = derivedItem->data; - param.ulIvLen = derivedItem->len; - param.ulIvBits = param.ulIvLen * 8; - param.ulTagBits = 16 * 8; - param.pAAD = (CK_BYTE_PTR)aad->data; - param.ulAADLen = aad->len; - - paramItem.type = siBuffer; - paramItem.data = (unsigned char*)(¶m); - paramItem.len = sizeof(CK_GCM_PARAMS); - - memset(mResponse, 0, mBodySize); - rv = PK11_Decrypt(key.get(), CKM_AES_GCM, ¶mItem, mResponse, &mBodySize, - MAX_SIZE, message.mEncryptedMessage.Elements(), - message.mEncryptedMessage.Length()); - if (rv != SECSuccess) { - LOG(("ODoHDNSPacket::DecryptDNSResponse decrypt failed %d", - PORT_GetError())); - return false; - } - - return true; -} - } // namespace net } // namespace mozilla diff --git a/netwerk/dns/DNSPacket.h b/netwerk/dns/DNSPacket.h index 46b85b806600..12df52c1d751 100644 --- a/netwerk/dns/DNSPacket.h +++ b/netwerk/dns/DNSPacket.h @@ -106,39 +106,6 @@ class DNSPacket { Maybe mOriginHost; }; -class ODoHDNSPacket final : public DNSPacket { - public: - ODoHDNSPacket() = default; - virtual ~ODoHDNSPacket(); - - static bool ParseODoHConfigs(Span aData, - nsTArray& aOut); - - virtual nsresult EncodeRequest(nsCString& aBody, const nsACString& aHost, - uint16_t aType, bool aDisableECS) override; - - virtual nsresult Decode( - nsCString& aHost, enum TrrType aType, nsCString& aCname, - bool aAllowRFC1918, DOHresp& aResp, TypeRecordResultType& aTypeResult, - nsClassHashtable& aAdditionalRecords, - uint32_t& aTTL) override; - - protected: - bool EncryptDNSQuery(const nsACString& aQuery, uint16_t aPaddingLen, - const ObliviousDoHConfig& aConfig, - ObliviousDoHMessage& aOut); - bool DecryptDNSResponse(); - - HpkeContext* mContext = nullptr; - UniqueSECItem mPlainQuery; - // This struct indicates the range of decrypted responses stored in mResponse. - struct DecryptedResponseRange { - uint16_t mStart = 0; - uint16_t mLength = 0; - }; - Maybe mDecryptedResponseRange; -}; - } // namespace net } // namespace mozilla diff --git a/netwerk/dns/DNSUtils.h b/netwerk/dns/DNSUtils.h index 5a89e6c64691..f5b53c5d7232 100644 --- a/netwerk/dns/DNSUtils.h +++ b/netwerk/dns/DNSUtils.h @@ -16,13 +16,11 @@ namespace mozilla { namespace net { class NetworkConnectivityService; -class ODoHService; class TRR; class DNSUtils final { private: friend class NetworkConnectivityService; - friend class ODoHService; friend class ObliviousHttpService; friend class TRR; static nsresult CreateChannelHelper(nsIURI* aUri, nsIChannel** aResult); diff --git a/netwerk/dns/ODoH.cpp b/netwerk/dns/ODoH.cpp deleted file mode 100644 index dd752f74e290..000000000000 --- a/netwerk/dns/ODoH.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set sw=2 ts=8 et tw=80 : */ -/* 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/. */ - -#include "ODoH.h" - -#include "mozilla/Base64.h" -#include "nsIURIMutator.h" -#include "ODoHService.h" -#include "TRRService.h" -// Put DNSLogging.h at the end to avoid LOG being overwritten by other headers. -#include "DNSLogging.h" -#include "nsNetUtil.h" - -namespace mozilla { -namespace net { - -NS_IMETHODIMP -ODoH::Run() { - if (!gODoHService) { - RecordReason(TRRSkippedReason::TRR_SEND_FAILED); - FailData(NS_ERROR_FAILURE); - return NS_OK; - } - - if (!gODoHService->ODoHConfigs()) { - LOG(( - "ODoH::Run ODoHConfigs is not available mTriedDownloadODoHConfigs=%d\n", - mTriedDownloadODoHConfigs)); - // Make this lookup fail if we don't have a valid ODoHConfig and we already - // tried before. - if (NS_SUCCEEDED(gODoHService->UpdateODoHConfig()) && - !mTriedDownloadODoHConfigs) { - gODoHService->AppendPendingODoHRequest(this); - mTriedDownloadODoHConfigs = true; - } else { - RecordReason(TRRSkippedReason::ODOH_UPDATE_KEY_FAILED); - FailData(NS_ERROR_FAILURE); - return NS_OK; - } - return NS_OK; - } - - return TRR::Run(); -} - -DNSPacket* ODoH::GetOrCreateDNSPacket() { - if (!mPacket) { - mPacket = MakeUnique(); - } - - return mPacket.get(); -} - -nsresult ODoH::CreateQueryURI(nsIURI** aOutURI) { - nsAutoCString uri; - nsCOMPtr dnsURI; - gODoHService->GetRequestURI(uri); - - nsresult rv = NS_NewURI(getter_AddRefs(dnsURI), uri); - if (NS_FAILED(rv)) { - return rv; - } - - dnsURI.forget(aOutURI); - return NS_OK; -} - -void ODoH::HandleTimeout() { - // If this request is still in the pending queue, it means we can't get the - // ODoHConfigs within the timeout. - if (gODoHService->RemovePendingODoHRequest(this)) { - RecordReason(TRRSkippedReason::ODOH_KEY_NOT_AVAILABLE); - } - - TRR::HandleTimeout(); -} - -void ODoH::HandleEncodeError(nsresult aStatusCode) { - MOZ_ASSERT(NS_FAILED(aStatusCode)); - - DNSPacketStatus status = mPacket->PacketStatus(); - MOZ_ASSERT(status != DNSPacketStatus::Success); - - if (status == DNSPacketStatus::KeyNotAvailable) { - RecordReason(TRRSkippedReason::ODOH_KEY_NOT_AVAILABLE); - } else if (status == DNSPacketStatus::KeyNotUsable) { - RecordReason(TRRSkippedReason::ODOH_KEY_NOT_USABLE); - } else if (status == DNSPacketStatus::EncryptError) { - RecordReason(TRRSkippedReason::ODOH_ENCRYPTION_FAILED); - } else { - MOZ_ASSERT_UNREACHABLE("Unexpected status code."); - } -} - -void ODoH::HandleDecodeError(nsresult aStatusCode) { - MOZ_ASSERT(NS_FAILED(aStatusCode)); - - DNSPacketStatus status = mPacket->PacketStatus(); - MOZ_ASSERT(status != DNSPacketStatus::Success); - - if (status == DNSPacketStatus::DecryptError) { - RecordReason(TRRSkippedReason::ODOH_DECRYPTION_FAILED); - } - - TRR::HandleDecodeError(aStatusCode); -} - -} // namespace net -} // namespace mozilla diff --git a/netwerk/dns/ODoH.h b/netwerk/dns/ODoH.h deleted file mode 100644 index 385ac83bd583..000000000000 --- a/netwerk/dns/ODoH.h +++ /dev/null @@ -1,60 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set sw=2 ts=8 et tw=80 : */ -/* 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/. */ - -#ifndef mozilla_net_ODoH_h -#define mozilla_net_ODoH_h - -#include "TRR.h" - -namespace mozilla { -namespace net { - -class ODoH final : public TRR { - public: - explicit ODoH(AHostResolver* aResolver, nsHostRecord* aRec, - enum TrrType aType) - : TRR(aResolver, aRec, aType) {} - // when following CNAMEs - explicit ODoH(AHostResolver* aResolver, nsHostRecord* aRec, nsCString& aHost, - enum TrrType& aType, unsigned int aLoopCount, bool aPB) - : TRR(aResolver, aRec, aHost, aType, aLoopCount, aPB) {} - NS_IMETHOD Run() override; - // ODoH should not support push. - NS_IMETHOD GetInterface(const nsIID&, void**) override { - return NS_ERROR_NO_INTERFACE; - } - - protected: - virtual ~ODoH() = default; - virtual DNSPacket* GetOrCreateDNSPacket() override; - virtual nsresult CreateQueryURI(nsIURI** aOutURI) override; - virtual const char* ContentType() const override { - return "application/oblivious-dns-message"; - } - virtual DNSResolverType ResolverType() const override { - return DNSResolverType::ODoH; - } - virtual bool MaybeBlockRequest() override { - // TODO: check excluded list - return false; - }; - virtual void RecordProcessingTime(nsIChannel* aChannel) override { - // TODO: record processing time for ODoH. - } - virtual void ReportStatus(nsresult aStatusCode) override { - // TODO: record status in ODoHService. - } - virtual void HandleTimeout() override; - virtual void HandleEncodeError(nsresult aStatusCode) override; - virtual void HandleDecodeError(nsresult aStatusCode) override; - - bool mTriedDownloadODoHConfigs = false; -}; - -} // namespace net -} // namespace mozilla - -#endif diff --git a/netwerk/dns/ODoHService.cpp b/netwerk/dns/ODoHService.cpp deleted file mode 100644 index ce34a8ad892e..000000000000 --- a/netwerk/dns/ODoHService.cpp +++ /dev/null @@ -1,521 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. */ - -#include "ODoHService.h" - -#include "DNSUtils.h" -#include "mozilla/net/SocketProcessChild.h" -#include "mozilla/Preferences.h" -#include "mozilla/ScopeExit.h" -#include "mozilla/StaticPrefs_network.h" -#include "nsICancelable.h" -#include "nsIDNSAdditionalInfo.h" -#include "nsIDNSService.h" -#include "nsIDNSByTypeRecord.h" -#include "nsIOService.h" -#include "nsIObserverService.h" -#include "nsNetUtil.h" -#include "ODoH.h" -#include "TRRService.h" -#include "nsURLHelper.h" -// Put DNSLogging.h at the end to avoid LOG being overwritten by other headers. -#include "DNSLogging.h" - -static const char kODoHProxyURIPref[] = "network.trr.odoh.proxy_uri"; -static const char kODoHTargetHostPref[] = "network.trr.odoh.target_host"; -static const char kODoHTargetPathPref[] = "network.trr.odoh.target_path"; -static const char kODoHConfigsUriPref[] = "network.trr.odoh.configs_uri"; - -namespace mozilla { -namespace net { - -ODoHService* gODoHService = nullptr; - -NS_IMPL_ISUPPORTS(ODoHService, nsIDNSListener, nsIObserver, - nsISupportsWeakReference, nsITimerCallback, nsINamed, - nsIStreamLoaderObserver) - -ODoHService::ODoHService() - : mLock("net::ODoHService"), mQueryODoHConfigInProgress(false) { - gODoHService = this; -} - -ODoHService::~ODoHService() { gODoHService = nullptr; } - -bool ODoHService::Init() { - MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); - - nsCOMPtr prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); - if (!prefBranch) { - return false; - } - - prefBranch->AddObserver(kODoHProxyURIPref, this, true); - prefBranch->AddObserver(kODoHTargetHostPref, this, true); - prefBranch->AddObserver(kODoHTargetPathPref, this, true); - prefBranch->AddObserver(kODoHConfigsUriPref, this, true); - - ReadPrefs(nullptr); - - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - if (observerService) { - observerService->AddObserver(this, "xpcom-shutdown-threads", true); - } - - return true; -} - -bool ODoHService::Enabled() const { - return StaticPrefs::network_trr_odoh_enabled(); -} - -NS_IMETHODIMP -ODoHService::Observe(nsISupports* aSubject, const char* aTopic, - const char16_t* aData) { - MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); - if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { - ReadPrefs(NS_ConvertUTF16toUTF8(aData).get()); - } else if (!strcmp(aTopic, "xpcom-shutdown-threads")) { - if (mTTLTimer) { - mTTLTimer->Cancel(); - mTTLTimer = nullptr; - } - } - - return NS_OK; -} - -nsresult ODoHService::ReadPrefs(const char* aName) { - if (!aName || !strcmp(aName, kODoHConfigsUriPref)) { - OnODohConfigsURIChanged(); - } - if (!aName || !strcmp(aName, kODoHProxyURIPref) || - !strcmp(aName, kODoHTargetHostPref) || - !strcmp(aName, kODoHTargetPathPref)) { - OnODoHPrefsChange(aName == nullptr); - } - - return NS_OK; -} - -void ODoHService::OnODohConfigsURIChanged() { - nsAutoCString uri; - Preferences::GetCString(kODoHConfigsUriPref, uri); - - bool updateConfig = false; - { - MutexAutoLock lock(mLock); - if (!mODoHConfigsUri.Equals(uri)) { - mODoHConfigsUri = uri; - updateConfig = true; - } - } - - if (updateConfig) { - UpdateODoHConfigFromURI(); - } -} - -void ODoHService::OnODoHPrefsChange(bool aInit) { - nsAutoCString proxyURI; - Preferences::GetCString(kODoHProxyURIPref, proxyURI); - nsAutoCString targetHost; - Preferences::GetCString(kODoHTargetHostPref, targetHost); - nsAutoCString targetPath; - Preferences::GetCString(kODoHTargetPathPref, targetPath); - - bool updateODoHConfig = false; - { - MutexAutoLock lock(mLock); - mODoHProxyURI = proxyURI; - // Only update ODoHConfig when the host is really changed. - if (!mODoHTargetHost.Equals(targetHost) && mODoHConfigsUri.IsEmpty()) { - updateODoHConfig = true; - } - mODoHTargetHost = targetHost; - mODoHTargetPath = targetPath; - - BuildODoHRequestURI(); - } - - if (updateODoHConfig) { - // When this function is called from ODoHService::Init(), it's on the same - // call stack as nsDNSService is inited. In this case, we need to dispatch - // UpdateODoHConfigFromHTTPSRR(), since recursively getting DNS service is - // not allowed. - auto task = []() { gODoHService->UpdateODoHConfigFromHTTPSRR(); }; - if (aInit) { - NS_DispatchToMainThread(NS_NewRunnableFunction( - "ODoHService::UpdateODoHConfigFromHTTPSRR", std::move(task))); - } else { - task(); - } - } -} - -static nsresult ExtractHostAndPort(const nsACString& aURI, nsCString& aResult, - int32_t& aOutPort) { - nsCOMPtr uri; - nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI); - if (NS_FAILED(rv)) { - return rv; - } - - if (!uri->SchemeIs("https")) { - LOG(("ODoHService host uri is not https")); - return NS_ERROR_FAILURE; - } - - rv = uri->GetPort(&aOutPort); - if (NS_FAILED(rv)) { - return rv; - } - - return uri->GetAsciiHost(aResult); -} - -void ODoHService::BuildODoHRequestURI() { - mLock.AssertCurrentThreadOwns(); - - mODoHRequestURI.Truncate(); - if (mODoHTargetHost.IsEmpty() || mODoHTargetPath.IsEmpty()) { - return; - } - - if (mODoHProxyURI.IsEmpty()) { - mODoHRequestURI.Append(mODoHTargetHost); - mODoHRequestURI.AppendLiteral("/"); - mODoHRequestURI.Append(mODoHTargetPath); - } else { - nsAutoCString hostStr; - int32_t port = -1; - if (NS_FAILED(ExtractHostAndPort(mODoHTargetHost, hostStr, port))) { - return; - } - - mODoHRequestURI.Append(mODoHProxyURI); - mODoHRequestURI.AppendLiteral("?targethost="); - mODoHRequestURI.Append(hostStr); - mODoHRequestURI.AppendLiteral("&targetpath=/"); - mODoHRequestURI.Append(mODoHTargetPath); - } -} - -void ODoHService::GetRequestURI(nsACString& aResult) { - MutexAutoLock lock(mLock); - aResult = mODoHRequestURI; -} - -nsresult ODoHService::UpdateODoHConfig() { - LOG(("ODoHService::UpdateODoHConfig")); - if (mQueryODoHConfigInProgress) { - return NS_OK; - } - - if (NS_SUCCEEDED(UpdateODoHConfigFromURI())) { - return NS_OK; - } - - return UpdateODoHConfigFromHTTPSRR(); -} - -nsresult ODoHService::UpdateODoHConfigFromURI() { - LOG(("ODoHService::UpdateODoHConfigFromURI")); - - nsAutoCString configUri; - { - MutexAutoLock lock(mLock); - configUri = mODoHConfigsUri; - } - - if (configUri.IsEmpty() || !StringBeginsWith(configUri, "https://"_ns)) { - LOG(("ODoHService::UpdateODoHConfigFromURI: uri is invalid")); - return UpdateODoHConfigFromHTTPSRR(); - } - - nsCOMPtr target = TRRService::Get()->MainThreadOrTRRThread(); - if (!target) { - return NS_ERROR_UNEXPECTED; - } - - if (!target->IsOnCurrentThread()) { - nsresult rv = target->Dispatch(NS_NewRunnableFunction( - "ODoHService::UpdateODoHConfigFromURI", - []() { gODoHService->UpdateODoHConfigFromURI(); })); - if (NS_SUCCEEDED(rv)) { - // Set mQueryODoHConfigInProgress to true to avoid updating ODoHConfigs - // when waiting the runnable to be executed. - mQueryODoHConfigInProgress = true; - } - return rv; - } - - // In case any error happens, we should reset mQueryODoHConfigInProgress. - auto guard = MakeScopeExit([&]() { mQueryODoHConfigInProgress = false; }); - - nsCOMPtr uri; - nsresult rv = NS_NewURI(getter_AddRefs(uri), configUri); - if (NS_FAILED(rv)) { - return rv; - } - - nsCOMPtr channel; - rv = DNSUtils::CreateChannelHelper(uri, getter_AddRefs(channel)); - if (NS_FAILED(rv) || !channel) { - LOG(("NewChannel failed!")); - return rv; - } - - rv = channel->SetLoadFlags( - nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING | - nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_URL_CLASSIFIER); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr httpChannel = do_QueryInterface(channel); - if (!httpChannel) { - return NS_ERROR_UNEXPECTED; - } - - // This connection should not use TRR - rv = httpChannel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr loader; - rv = NS_NewStreamLoader(getter_AddRefs(loader), this); - if (NS_FAILED(rv)) { - return rv; - } - - rv = httpChannel->AsyncOpen(loader); - if (NS_FAILED(rv)) { - return rv; - } - - // AsyncOpen succeeded, dismiss the guard. - MutexAutoLock lock(mLock); - guard.release(); - mLoader.swap(loader); - return rv; -} - -nsresult ODoHService::UpdateODoHConfigFromHTTPSRR() { - LOG(("ODoHService::UpdateODoHConfigFromHTTPSRR")); - - nsAutoCString uri; - { - MutexAutoLock lock(mLock); - uri = mODoHTargetHost; - } - - nsCOMPtr dns( - do_GetService("@mozilla.org/network/dns-service;1")); - if (!dns) { - return NS_ERROR_NOT_AVAILABLE; - } - - if (!TRRService::Get()) { - return NS_ERROR_NOT_AVAILABLE; - } - - nsAutoCString hostStr; - int32_t port = -1; - nsresult rv = ExtractHostAndPort(uri, hostStr, port); - if (NS_FAILED(rv)) { - return rv; - } - - nsCOMPtr tmpOutstanding; - nsCOMPtr target = TRRService::Get()->MainThreadOrTRRThread(); - // We'd like to bypass the DNS cache, since ODoHConfigs will be updated - // manually by ODoHService. - nsIDNSService::DNSFlags flags = - nsIDNSService::RESOLVE_DISABLE_ODOH | nsIDNSService::RESOLVE_BYPASS_CACHE; - nsCOMPtr info; - if (port != -1) { - Unused << dns->NewAdditionalInfo(""_ns, port, getter_AddRefs(info)); - } - rv = dns->AsyncResolveNative(hostStr, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, - flags, info, this, target, OriginAttributes(), - getter_AddRefs(tmpOutstanding)); - LOG(("ODoHService::UpdateODoHConfig [host=%s rv=%" PRIx32 "]", hostStr.get(), - static_cast(rv))); - - if (NS_SUCCEEDED(rv)) { - mQueryODoHConfigInProgress = true; - } - return rv; -} - -void ODoHService::StartTTLTimer(uint32_t aTTL) { - if (mTTLTimer) { - mTTLTimer->Cancel(); - mTTLTimer = nullptr; - } - LOG(("ODoHService::StartTTLTimer ttl=%d(s)", aTTL)); - NS_NewTimerWithCallback(getter_AddRefs(mTTLTimer), this, aTTL * 1000, - nsITimer::TYPE_ONE_SHOT); -} - -NS_IMETHODIMP -ODoHService::Notify(nsITimer* aTimer) { - MOZ_ASSERT(aTimer == mTTLTimer); - UpdateODoHConfig(); - return NS_OK; -} - -NS_IMETHODIMP -ODoHService::GetName(nsACString& aName) { - aName.AssignLiteral("ODoHService"); - return NS_OK; -} - -void ODoHService::ODoHConfigUpdateDone(uint32_t aTTL, - Span aRawConfig) { - MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(), - NS_IsMainThread() || TRRService::Get()->IsOnTRRThread()); - MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread()); - - MutexAutoLock lock(mLock); - mQueryODoHConfigInProgress = false; - mODoHConfigs.reset(); - - nsTArray configs; - if (ODoHDNSPacket::ParseODoHConfigs(aRawConfig, configs)) { - mODoHConfigs.emplace(std::move(configs)); - } - - // Let observers know whether ODoHService is activated or not. - bool hasODoHConfigs = mODoHConfigs && !mODoHConfigs->IsEmpty(); - if (aTTL < StaticPrefs::network_trr_odoh_min_ttl()) { - aTTL = StaticPrefs::network_trr_odoh_min_ttl(); - } - auto task = [hasODoHConfigs, aTTL]() { - MOZ_ASSERT(NS_IsMainThread()); - if (XRE_IsSocketProcess()) { - SocketProcessChild::GetSingleton()->SendODoHServiceActivated( - hasODoHConfigs); - } - - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - - if (observerService) { - observerService->NotifyObservers(nullptr, "odoh-service-activated", - hasODoHConfigs ? u"true" : u"false"); - } - - if (aTTL) { - gODoHService->StartTTLTimer(aTTL); - } - }; - - if (NS_IsMainThread()) { - task(); - } else { - NS_DispatchToMainThread( - NS_NewRunnableFunction("ODoHService::Activated", std::move(task))); - } - - if (!mPendingRequests.IsEmpty()) { - nsTArray> requests = std::move(mPendingRequests); - nsCOMPtr target = - TRRService::Get()->MainThreadOrTRRThread(); - for (auto& query : requests) { - target->Dispatch(query.forget()); - } - } -} - -NS_IMETHODIMP -ODoHService::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRec, - nsresult aStatus) { - MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(), - NS_IsMainThread() || TRRService::Get()->IsOnTRRThread()); - MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread()); - - nsCOMPtr httpsRecord; - nsCString rawODoHConfig; - auto notifyDone = MakeScopeExit([&]() { - uint32_t ttl = 0; - if (httpsRecord) { - Unused << httpsRecord->GetTtl(&ttl); - } - - ODoHConfigUpdateDone( - ttl, - Span(reinterpret_cast(rawODoHConfig.BeginReading()), - rawODoHConfig.Length())); - }); - - LOG(("ODoHService::OnLookupComplete [aStatus=%" PRIx32 "]", - static_cast(aStatus))); - if (NS_FAILED(aStatus)) { - return NS_OK; - } - - httpsRecord = do_QueryInterface(aRec); - if (!httpsRecord) { - return NS_OK; - } - - nsTArray> svcbRecords; - httpsRecord->GetRecords(svcbRecords); - for (const auto& record : svcbRecords) { - Unused << record->GetODoHConfig(rawODoHConfig); - if (!rawODoHConfig.IsEmpty()) { - break; - } - } - - return NS_OK; -} - -NS_IMETHODIMP -ODoHService::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext, - nsresult aStatus, uint32_t aLength, - const uint8_t* aContent) { - MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(), - NS_IsMainThread() || TRRService::Get()->IsOnTRRThread()); - MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread()); - LOG(("ODoHService::OnStreamComplete aLength=%d\n", aLength)); - - { - MutexAutoLock lock(mLock); - mLoader = nullptr; - } - ODoHConfigUpdateDone(0, Span(aContent, aLength)); - return NS_OK; -} - -const Maybe>& ODoHService::ODoHConfigs() { - MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(), - NS_IsMainThread() || TRRService::Get()->IsOnTRRThread()); - MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread()); - - return mODoHConfigs; -} - -void ODoHService::AppendPendingODoHRequest(ODoH* aRequest) { - LOG(("ODoHService::AppendPendingODoHQuery\n")); - MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(), - NS_IsMainThread() || TRRService::Get()->IsOnTRRThread()); - MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread()); - - MutexAutoLock lock(mLock); - mPendingRequests.AppendElement(aRequest); -} - -bool ODoHService::RemovePendingODoHRequest(ODoH* aRequest) { - MOZ_ASSERT_IF(XRE_IsParentProcess() && TRRService::Get(), - NS_IsMainThread() || TRRService::Get()->IsOnTRRThread()); - MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread()); - - MutexAutoLock lock(mLock); - return mPendingRequests.RemoveElement(aRequest); -} - -} // namespace net -} // namespace mozilla diff --git a/netwerk/dns/ODoHService.h b/netwerk/dns/ODoHService.h deleted file mode 100644 index 4e09ebe3984c..000000000000 --- a/netwerk/dns/ODoHService.h +++ /dev/null @@ -1,80 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. */ - -#ifndef ODoHService_h_ -#define ODoHService_h_ - -#include "DNS.h" -#include "mozilla/Atomics.h" -#include "mozilla/Maybe.h" -#include "mozilla/Mutex.h" -#include "nsString.h" -#include "nsIDNSListener.h" -#include "nsIObserver.h" -#include "nsIStreamLoader.h" -#include "nsITimer.h" -#include "nsWeakReference.h" - -namespace mozilla { -namespace net { - -class ODoH; - -class ODoHService : public nsIDNSListener, - public nsIObserver, - public nsSupportsWeakReference, - public nsITimerCallback, - public nsINamed, - public nsIStreamLoaderObserver { - public: - NS_DECL_THREADSAFE_ISUPPORTS - NS_DECL_NSIDNSLISTENER - NS_DECL_NSIOBSERVER - NS_DECL_NSITIMERCALLBACK - NS_DECL_NSINAMED - NS_DECL_NSISTREAMLOADEROBSERVER - - ODoHService(); - bool Init(); - bool Enabled() const; - - const Maybe>& ODoHConfigs(); - void AppendPendingODoHRequest(ODoH* aRequest); - bool RemovePendingODoHRequest(ODoH* aRequest); - void GetRequestURI(nsACString& aResult); - // Send a DNS query to reterive the ODoHConfig. - nsresult UpdateODoHConfig(); - - private: - virtual ~ODoHService(); - nsresult ReadPrefs(const char* aName); - void OnODoHPrefsChange(bool aInit); - void BuildODoHRequestURI(); - void StartTTLTimer(uint32_t aTTL); - void OnODohConfigsURIChanged(); - void ODoHConfigUpdateDone(uint32_t aTTL, Span aRawConfig); - nsresult UpdateODoHConfigFromHTTPSRR(); - nsresult UpdateODoHConfigFromURI(); - - mozilla::Mutex mLock; - Atomic mQueryODoHConfigInProgress; - nsCString mODoHProxyURI MOZ_GUARDED_BY(mLock); - nsCString mODoHTargetHost MOZ_GUARDED_BY(mLock); - nsCString mODoHTargetPath MOZ_GUARDED_BY(mLock); - nsCString mODoHRequestURI MOZ_GUARDED_BY(mLock); - nsCString mODoHConfigsUri MOZ_GUARDED_BY(mLock); - Maybe> mODoHConfigs MOZ_GUARDED_BY(mLock); - nsTArray> mPendingRequests MOZ_GUARDED_BY(mLock); - // This timer is always touched on main thread to avoid race conditions. - nsCOMPtr mTTLTimer; - nsCOMPtr mLoader MOZ_GUARDED_BY(mLock); -}; - -extern ODoHService* gODoHService; - -} // namespace net -} // namespace mozilla - -#endif diff --git a/netwerk/dns/TRR.cpp b/netwerk/dns/TRR.cpp index c3421ad26a24..3bfc892c65b1 100644 --- a/netwerk/dns/TRR.cpp +++ b/netwerk/dns/TRR.cpp @@ -26,7 +26,6 @@ #include "nsStringStream.h" #include "nsThreadUtils.h" #include "nsURLHelper.h" -#include "ODoH.h" #include "ObliviousHttpChannel.h" #include "TRR.h" #include "TRRService.h" @@ -881,9 +880,7 @@ nsresult TRR::FollowCname(nsIChannel* aChannel) { LOG(("TRR::On200Response CNAME %s => %s (%u)\n", mHost.get(), mCname.get(), mCnameLoop)); RefPtr trr = - ResolverType() == DNSResolverType::ODoH - ? new ODoH(mHostResolver, mRec, mCname, mType, mCnameLoop, mPB) - : new TRR(mHostResolver, mRec, mCname, mType, mCnameLoop, mPB); + new TRR(mHostResolver, mRec, mCname, mType, mCnameLoop, mPB); if (!TRRService::Get()) { return NS_ERROR_FAILURE; } diff --git a/netwerk/dns/TRRQuery.cpp b/netwerk/dns/TRRQuery.cpp index 0484ccbeb05e..defe4353dd8c 100644 --- a/netwerk/dns/TRRQuery.cpp +++ b/netwerk/dns/TRRQuery.cpp @@ -9,7 +9,6 @@ #include "nsQueryObject.h" #include "TRR.h" #include "TRRService.h" -#include "ODoH.h" // Put DNSLogging.h at the end to avoid LOG being overwritten by other headers. #include "DNSLogging.h" @@ -65,15 +64,10 @@ void TRRQuery::MarkSendingTRR(TRR* trr, enum TrrType rectype, MutexAutoLock&) { } } -void TRRQuery::PrepareQuery(bool aUseODoH, enum TrrType aRecType, +void TRRQuery::PrepareQuery(enum TrrType aRecType, nsTArray>& aRequestsToSend) { LOG(("TRR Resolve %s type %d\n", mRecord->host.get(), (int)aRecType)); - RefPtr trr; - if (aUseODoH) { - trr = new ODoH(this, mRecord, aRecType); - } else { - trr = new TRR(this, mRecord, aRecType); - } + RefPtr trr = new TRR(this, mRecord, aRecType); { MutexAutoLock trrlock(mTrrLock); @@ -105,14 +99,9 @@ bool TRRQuery::SendQueries(nsTArray>& aRequestsToSend) { return madeQuery; } -nsresult TRRQuery::DispatchLookup(TRR* pushedTRR, bool aUseODoH) { - if (aUseODoH && pushedTRR) { - MOZ_ASSERT(false, "ODoH should not support push"); - return NS_ERROR_UNKNOWN_HOST; - } - +nsresult TRRQuery::DispatchLookup(TRR* pushedTRR) { if (!mRecord->IsAddrRecord()) { - return DispatchByTypeLookup(pushedTRR, aUseODoH); + return DispatchByTypeLookup(pushedTRR); } RefPtr addrRec = do_QueryObject(mRecord); @@ -142,21 +131,20 @@ nsresult TRRQuery::DispatchLookup(TRR* pushedTRR, bool aUseODoH) { // same time. nsTArray> requestsToSend; if ((mRecord->af == AF_UNSPEC || mRecord->af == AF_INET6)) { - PrepareQuery(aUseODoH, TRRTYPE_AAAA, requestsToSend); + PrepareQuery(TRRTYPE_AAAA, requestsToSend); } if (mRecord->af == AF_UNSPEC || mRecord->af == AF_INET) { - PrepareQuery(aUseODoH, TRRTYPE_A, requestsToSend); + PrepareQuery(TRRTYPE_A, requestsToSend); } if (SendQueries(requestsToSend)) { - mUsingODoH = aUseODoH; return NS_OK; } return NS_ERROR_UNKNOWN_HOST; } -nsresult TRRQuery::DispatchByTypeLookup(TRR* pushedTRR, bool aUseODoH) { +nsresult TRRQuery::DispatchByTypeLookup(TRR* pushedTRR) { RefPtr typeRec = do_QueryObject(mRecord); MOZ_ASSERT(typeRec); if (!typeRec) { @@ -179,12 +167,7 @@ nsresult TRRQuery::DispatchByTypeLookup(TRR* pushedTRR, bool aUseODoH) { } LOG(("TRR Resolve %s type %d\n", typeRec->host.get(), (int)rectype)); - RefPtr trr; - if (aUseODoH) { - trr = new ODoH(this, mRecord, rectype); - } else { - trr = pushedTRR ? pushedTRR : new TRR(this, mRecord, rectype); - } + RefPtr trr = pushedTRR ? pushedTRR : new TRR(this, mRecord, rectype); if (pushedTRR || NS_SUCCEEDED(TRRService::Get()->DispatchTRRRequest(trr))) { MutexAutoLock trrlock(mTrrLock); @@ -285,7 +268,7 @@ AHostResolver::LookupStatus TRRQuery::CompleteLookup( if (newRRSet->TRRType() == TRRTYPE_A) { LOG(("A lookup failed. Checking if AAAA record exists")); nsTArray> requestsToSend; - PrepareQuery(mUsingODoH, TRRTYPE_AAAA, requestsToSend); + PrepareQuery(TRRTYPE_AAAA, requestsToSend); if (SendQueries(requestsToSend)) { LOG(("Sent AAAA request")); return LOOKUP_OK; @@ -293,7 +276,7 @@ AHostResolver::LookupStatus TRRQuery::CompleteLookup( } else if (newRRSet->TRRType() == TRRTYPE_AAAA) { LOG(("AAAA lookup failed. Checking if A record exists")); nsTArray> requestsToSend; - PrepareQuery(mUsingODoH, TRRTYPE_A, requestsToSend); + PrepareQuery(TRRTYPE_A, requestsToSend); if (SendQueries(requestsToSend)) { LOG(("Sent A request")); return LOOKUP_OK; diff --git a/netwerk/dns/TRRQuery.h b/netwerk/dns/TRRQuery.h index 14f08ac7ac39..5c606bcc9a4d 100644 --- a/netwerk/dns/TRRQuery.h +++ b/netwerk/dns/TRRQuery.h @@ -22,7 +22,7 @@ class TRRQuery : public AHostResolver { mRecord(aHostRecord), mTrrLock("TRRQuery.mTrrLock") {} - nsresult DispatchLookup(TRR* pushedTRR = nullptr, bool aUseODoHProxy = false); + nsresult DispatchLookup(TRR* pushedTRR = nullptr); void Cancel(nsresult aStatus); @@ -69,15 +69,13 @@ class TRRQuery : public AHostResolver { mozilla::TimeDuration Duration() { return mTrrDuration; } private: - nsresult DispatchByTypeLookup(TRR* pushedTRR = nullptr, - bool aUseODoHProxy = false); + nsresult DispatchByTypeLookup(TRR* pushedTRR = nullptr); private: ~TRRQuery() = default; void MarkSendingTRR(TRR* trr, TrrType rectype, MutexAutoLock&); - void PrepareQuery(bool aUseODoH, TrrType aRecType, - nsTArray>& aRequestsToSend); + void PrepareQuery(TrrType aRecType, nsTArray>& aRequestsToSend); bool SendQueries(nsTArray>& aRequestsToSend); RefPtr mHostResolver; @@ -96,7 +94,6 @@ class TRRQuery : public AHostResolver { Atomic mTRRRequestCounter{0}; uint8_t mTRRSuccess = 0; // number of successful TRR responses - bool mUsingODoH = false; bool mCalledCompleteLookup = false; mozilla::TimeDuration mTrrDuration; diff --git a/netwerk/dns/TRRService.cpp b/netwerk/dns/TRRService.cpp index 0fcb413b9217..5cb42f43082d 100644 --- a/netwerk/dns/TRRService.cpp +++ b/netwerk/dns/TRRService.cpp @@ -216,11 +216,6 @@ nsresult TRRService::Init() { sTRRBackgroundThread = thread; } - mODoHService = new ODoHService(); - if (!mODoHService->Init()) { - return NS_ERROR_FAILURE; - } - Preferences::RegisterCallbackAndCall( EventTelemetryPrefChanged, "network.trr.confirmation_telemetry_enabled"_ns); diff --git a/netwerk/dns/TRRService.h b/netwerk/dns/TRRService.h index 9ca2b7db8ea7..eb6b87b58f4d 100644 --- a/netwerk/dns/TRRService.h +++ b/netwerk/dns/TRRService.h @@ -11,7 +11,6 @@ #include "nsIObserver.h" #include "nsITimer.h" #include "nsWeakReference.h" -#include "ODoHService.h" #include "TRRServiceBase.h" #include "nsICaptivePortalService.h" #include "nsTHashSet.h" @@ -109,7 +108,6 @@ class TRRService : public TRRServiceBase, friend class TRRServiceChild; friend class TRRServiceParent; - friend class ODoHService; static void AddObserver(nsIObserver* aObserver, nsIObserverService* aObserverService = nullptr); static bool CheckCaptivePortalIsPassed(); @@ -378,7 +376,6 @@ class TRRService : public TRRServiceBase, // This is used to track whether a confirmation was triggered by a URI change, // so we don't trigger another one just because other prefs have changed. bool mConfirmationTriggered{false}; - RefPtr mODoHService; nsCOMPtr mLinkService; }; diff --git a/netwerk/dns/moz.build b/netwerk/dns/moz.build index a83d2720c4bb..17612ff1b396 100644 --- a/netwerk/dns/moz.build +++ b/netwerk/dns/moz.build @@ -78,8 +78,6 @@ UNIFIED_SOURCES += [ "nsHostRecord.cpp", "nsHostResolver.cpp", "nsIDNService.cpp", - "ODoH.cpp", - "ODoHService.cpp", "punycode.c", "TRR.cpp", "TRRQuery.cpp", diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp index ca7018c2adbc..f115cab4de34 100644 --- a/netwerk/dns/nsDNSService2.cpp +++ b/netwerk/dns/nsDNSService2.cpp @@ -128,9 +128,7 @@ NS_IMETHODIMP nsDNSRecord::IsTRR(bool* retval) { MutexAutoLock lock(mHostRecord->addr_info_lock); if (mHostRecord->addr_info) { - // TODO: Let the consumers of nsIDNSRecord be unaware of the difference of - // TRR and ODoH. Will let them know the truth when needed. - *retval = mHostRecord->addr_info->IsTRROrODoH(); + *retval = mHostRecord->addr_info->IsTRR(); } else { *retval = false; } @@ -146,7 +144,7 @@ nsDNSRecord::ResolvedInSocketProcess(bool* retval) { NS_IMETHODIMP nsDNSRecord::GetTrrFetchDuration(double* aTime) { MutexAutoLock lock(mHostRecord->addr_info_lock); - if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRROrODoH()) { + if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRR()) { *aTime = mHostRecord->addr_info->GetTrrFetchDuration(); } else { *aTime = 0; @@ -157,7 +155,7 @@ nsDNSRecord::GetTrrFetchDuration(double* aTime) { NS_IMETHODIMP nsDNSRecord::GetTrrFetchDurationNetworkOnly(double* aTime) { MutexAutoLock lock(mHostRecord->addr_info_lock); - if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRROrODoH()) { + if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRR()) { *aTime = mHostRecord->addr_info->GetTrrFetchDurationNetworkOnly(); } else { *aTime = 0; @@ -847,7 +845,6 @@ nsDNSService::Init() { observerService->AddObserver(this, "last-pb-context-exited", false); observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); - observerService->AddObserver(this, "odoh-service-activated", false); } RefPtr res; @@ -1303,14 +1300,6 @@ nsDNSService::GetMyHostName(nsACString& result) { return NS_ERROR_FAILURE; } -NS_IMETHODIMP -nsDNSService::GetODoHActivated(bool* aResult) { - NS_ENSURE_ARG(aResult); - - *aResult = mODoHActivated; - return NS_OK; -} - NS_IMETHODIMP nsDNSService::Observe(nsISupports* subject, const char* topic, const char16_t* data) { @@ -1333,8 +1322,6 @@ nsDNSService::Observe(nsISupports* subject, const char* topic, } } else if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { Shutdown(); - } else if (!strcmp(topic, "odoh-service-activated")) { - mODoHActivated = u"true"_ns.Equals(data); } if (flushCache && resolver) { diff --git a/netwerk/dns/nsDNSService2.h b/netwerk/dns/nsDNSService2.h index cd516289d026..db8dd3eec6db 100644 --- a/netwerk/dns/nsDNSService2.h +++ b/netwerk/dns/nsDNSService2.h @@ -128,7 +128,6 @@ class nsDNSService final : public mozilla::net::DNSServiceBase, uint32_t mResCacheExpiration = 0; uint32_t mResCacheGrace = 0; bool mResolverPrefsUpdated = false; - bool mODoHActivated = false; nsClassHashtable> mFailedSVCDomainNames; }; diff --git a/netwerk/dns/nsHostRecord.cpp b/netwerk/dns/nsHostRecord.cpp index cc38dd555781..65b1d011502d 100644 --- a/netwerk/dns/nsHostRecord.cpp +++ b/netwerk/dns/nsHostRecord.cpp @@ -245,7 +245,7 @@ bool AddrHostRecord::HasUsableResultInternal( bool AddrHostRecord::RemoveOrRefresh(bool aTrrToo) { // no need to flush TRRed names, they're not resolved "locally" MutexAutoLock lock(addr_info_lock); - if (addr_info && !aTrrToo && addr_info->IsTRROrODoH()) { + if (addr_info && !aTrrToo && addr_info->IsTRR()) { return false; } if (LoadNative()) { @@ -284,21 +284,6 @@ void AddrHostRecord::ResolveComplete() { : Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::osFail); } - if (mResolverType == DNSResolverType::ODoH) { - // XXX(kershaw): Consider adding the failed host name into a blocklist. - if (mTRRSuccess) { - uint32_t millis = static_cast(mTrrDuration.ToMilliseconds()); - Telemetry::Accumulate(Telemetry::DNS_ODOH_LOOKUP_TIME, millis); - } - - if (nsHostResolver::Mode() == nsIDNSService::MODE_TRRFIRST) { - Telemetry::Accumulate(Telemetry::ODOH_SKIP_REASON_ODOH_FIRST, - static_cast(mTRRSkippedReason)); - } - - return; - } - if (mResolverType == DNSResolverType::TRR) { if (mTRRSuccess) { MOZ_DIAGNOSTIC_ASSERT(mTRRSkippedReason == diff --git a/netwerk/dns/nsHostRecord.h b/netwerk/dns/nsHostRecord.h index 94b9967db1d4..0064bb9e4206 100644 --- a/netwerk/dns/nsHostRecord.h +++ b/netwerk/dns/nsHostRecord.h @@ -321,7 +321,7 @@ class AddrHostRecord final : public nsHostRecord { mozilla::TimeDuration mTrrDuration; mozilla::TimeDuration mNativeDuration; - // TRR or ODoH was used on this record + // TRR was used on this record mozilla::Atomic mResolverType{DNSResolverType::Native}; // clang-format off diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp index f994d83e633a..ea4eaa6401d1 100644 --- a/netwerk/dns/nsHostResolver.cpp +++ b/netwerk/dns/nsHostResolver.cpp @@ -36,7 +36,6 @@ #include "TRR.h" #include "TRRQuery.h" #include "TRRService.h" -#include "ODoHService.h" #include "mozilla/Atomics.h" #include "mozilla/HashFunctions.h" @@ -933,9 +932,7 @@ nsresult nsHostResolver::TrrLookup(nsHostRecord* aRec, MaybeRenewHostRecordLocked(rec, aLock); RefPtr query = new TRRQuery(this, rec); - bool useODoH = gODoHService->Enabled() && - !((rec->flags & nsIDNSService::RESOLVE_DISABLE_ODOH)); - nsresult rv = query->DispatchLookup(pushedTRR, useODoH); + nsresult rv = query->DispatchLookup(pushedTRR); if (NS_FAILED(rv)) { rec->RecordReason(TRRSkippedReason::TRR_DID_NOT_MAKE_QUERY); return rv; @@ -1292,7 +1289,7 @@ void nsHostResolver::PrepareRecordExpirationAddrRecord( unsigned int grace = mDefaultGracePeriod; unsigned int ttl = mDefaultCacheLifetime; - if (sGetTtlEnabled || rec->addr_info->IsTRROrODoH()) { + if (sGetTtlEnabled || rec->addr_info->IsTRR()) { if (rec->addr_info && rec->addr_info->TTL() != AddrInfo::NO_TTL_DATA) { ttl = rec->addr_info->TTL(); } @@ -1604,7 +1601,7 @@ nsHostResolver::LookupStatus nsHostResolver::CompleteLookupLocked( bool hasNativeResult = false; { MutexAutoLock lock(addrRec->addr_info_lock); - if (addrRec->addr_info && !addrRec->addr_info->IsTRROrODoH()) { + if (addrRec->addr_info && !addrRec->addr_info->IsTRR()) { hasNativeResult = true; } } @@ -1899,7 +1896,7 @@ void nsHostResolver::GetDNSCacheEntries(nsTArray* args) { info.hostaddr.AppendElement(buf); } } - info.TRR = addrRec->addr_info->IsTRROrODoH(); + info.TRR = addrRec->addr_info->IsTRR(); } info.originAttributesSuffix = recordEntry.GetKey().originSuffix; diff --git a/netwerk/dns/nsIDNSService.idl b/netwerk/dns/nsIDNSService.idl index e81877cba832..c1aecccd8c33 100644 --- a/netwerk/dns/nsIDNSService.idl +++ b/netwerk/dns/nsIDNSService.idl @@ -88,8 +88,6 @@ interface nsIDNSService : nsISupports RESOLVE_IGNORE_SOCKS_DNS = (1 << 13), // If set, only cached IP hint addresses will be returned from resolve/asyncResolve. RESOLVE_IP_HINT = (1 << 14), - // If set, do not use ODoH for resolving the host name. - RESOLVE_DISABLE_ODOH = (1 << 15), // If set, the DNS service will pass a DNS record to // OnLookupComplete even when there was a resolution error. RESOLVE_WANT_RECORD_ON_ERROR = (1 << 16), @@ -349,12 +347,6 @@ interface nsIDNSService : nsISupports */ readonly attribute ACString TRRDomainKey; - /** - * Returns true when we have valid ODoHConfigs to encrypt/decrypt oblivious - * DNS packets. - */ - readonly attribute boolean ODoHActivated; - /************************************************************************* * Listed below are the various flags that may be OR'd together to form * the aFlags parameter passed to asyncResolve() and resolve(). diff --git a/netwerk/ipc/PSocketProcess.ipdl b/netwerk/ipc/PSocketProcess.ipdl index 8347644e3823..6a922ab57421 100644 --- a/netwerk/ipc/PSocketProcess.ipdl +++ b/netwerk/ipc/PSocketProcess.ipdl @@ -128,7 +128,6 @@ parent: OriginAttributes aOriginAttributes, nsCString aRequestString) returns (bool aAccepted); - async ODoHServiceActivated(bool aActivated); async ExcludeHttp2OrHttp3(HttpConnectionInfoCloneArgs aArgs); diff --git a/netwerk/ipc/SocketProcessParent.cpp b/netwerk/ipc/SocketProcessParent.cpp index f55ec680af62..dabd2a66ce55 100644 --- a/netwerk/ipc/SocketProcessParent.cpp +++ b/netwerk/ipc/SocketProcessParent.cpp @@ -309,18 +309,6 @@ void SocketProcessParent::Destroy(RefPtr&& aParent) { new DeferredDeleteSocketProcessParent(std::move(aParent))); } -mozilla::ipc::IPCResult SocketProcessParent::RecvODoHServiceActivated( - const bool& aActivated) { - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - - if (observerService) { - observerService->NotifyObservers(nullptr, "odoh-service-activated", - aActivated ? u"true" : u"false"); - } - return IPC_OK(); -} - mozilla::ipc::IPCResult SocketProcessParent::RecvExcludeHttp2OrHttp3( const HttpConnectionInfoCloneArgs& aArgs) { RefPtr cinfo = diff --git a/netwerk/ipc/SocketProcessParent.h b/netwerk/ipc/SocketProcessParent.h index ee11701ed129..fb7840288675 100644 --- a/netwerk/ipc/SocketProcessParent.h +++ b/netwerk/ipc/SocketProcessParent.h @@ -98,8 +98,6 @@ class SocketProcessParent final nsIURI* aPushedURL, OriginAttributes&& aOriginAttributes, nsCString&& aRequestString, CachePushCheckResolver&& aResolver); - mozilla::ipc::IPCResult RecvODoHServiceActivated(const bool& aActivated); - mozilla::ipc::IPCResult RecvExcludeHttp2OrHttp3( const HttpConnectionInfoCloneArgs& aArgs); mozilla::ipc::IPCResult RecvOnConsoleMessage(const nsString& aMessage); diff --git a/netwerk/protocol/http/TRRServiceChannel.cpp b/netwerk/protocol/http/TRRServiceChannel.cpp index 033c0ae26ed6..3a316d1e45d2 100644 --- a/netwerk/protocol/http/TRRServiceChannel.cpp +++ b/netwerk/protocol/http/TRRServiceChannel.cpp @@ -1180,18 +1180,15 @@ nsresult TRRServiceChannel::SetupReplacementChannel(nsIURI* aNewURI, encodedChannel->SetApplyConversion(LoadApplyConversion()); } - // mContentTypeHint is empty when this channel is used to download - // ODoHConfigs. if (mContentTypeHint.IsEmpty()) { return NS_OK; } // Make sure we set content-type on the old channel properly. - MOZ_ASSERT(mContentTypeHint.Equals("application/dns-message") || - mContentTypeHint.Equals("application/oblivious-dns-message")); + MOZ_ASSERT(mContentTypeHint.Equals("application/dns-message")); // Apply TRR specific settings. Note that we already know mContentTypeHint is - // "application/dns-message" or "application/oblivious-dns-message" here. + // "application/dns-message" here. return TRR::SetupTRRServiceChannelInternal( httpChannel, mRequestHead.ParsedMethod() == nsHttpRequestHead::kMethod_Get, diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index dbbbcbfbe2fd..beffad131c3b 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -7029,17 +7029,7 @@ static void RecordOnStartTelemetry(nsresult aStatus, Others = 2, }; - if (StaticPrefs::network_trr_odoh_enabled()) { - nsCOMPtr dns = do_GetService(NS_DNSSERVICE_CONTRACTID); - if (!dns) { - return; - } - bool ODoHActivated = false; - if (NS_SUCCEEDED(dns->GetODoHActivated(&ODoHActivated)) && ODoHActivated) { - Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_ONSTART_SUCCESS_ODOH, - NS_SUCCEEDED(aStatus)); - } - } else if (TRRService::Get() && TRRService::Get()->IsConfirmed()) { + if (TRRService::Get() && TRRService::Get()->IsConfirmed()) { // Note this telemetry probe is not working when DNS resolution is done in // the socket process. HttpOnStartState state = HttpOnStartState::Others; diff --git a/netwerk/socket/nsISocketProvider.idl b/netwerk/socket/nsISocketProvider.idl index 01576defd2a4..1f19b932f957 100644 --- a/netwerk/socket/nsISocketProvider.idl +++ b/netwerk/socket/nsISocketProvider.idl @@ -138,7 +138,7 @@ interface nsISocketProvider : nsISupports /** * If set, indicates that the connection used a privacy-preserving DNS - * transport such as DoH, DoQ, ODOH or similar. Currently this field is + * transport such as DoH, DoQ or similar. Currently this field is * set only when DoH is used via the TRR. */ const unsigned long USED_PRIVATE_DNS = (1 << 12); diff --git a/netwerk/test/unit/test_odoh.js b/netwerk/test/unit/test_odoh.js deleted file mode 100644 index 51f91879147b..000000000000 --- a/netwerk/test/unit/test_odoh.js +++ /dev/null @@ -1,295 +0,0 @@ -/* 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/. */ - -/* - * Most of tests in this file are reused from test_trr.js, except tests listed - * below. Since ODoH doesn't support push and customerized DNS resolver, some - * related tests are skipped. - * - * test_trr_flags - * test_push - * test_clearCacheOnURIChange // TODO: Clear DNS cache when ODoH prefs changed. - * test_dnsSuffix - * test_async_resolve_with_trr_server - * test_content_encoding_gzip - * test_redirect - * test_confirmation - * test_detected_uri - * test_pref_changes - * test_dohrollout_mode - * test_purge_trr_cache_on_mode_change - * test_old_bootstrap_pref - */ - -"use strict"; - -/* import-globals-from trr_common.js */ - -const certOverrideService = Cc[ - "@mozilla.org/security/certoverride;1" -].getService(Ci.nsICertOverrideService); - -add_setup(async function setup() { - h2Port = trr_test_setup(); - runningODoHTests = true; - - // This is for skiping the security check for the odoh host. - certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData( - true - ); - - registerCleanupFunction(() => { - trr_clear_prefs(); - Services.prefs.clearUserPref("network.trr.odoh.enabled"); - Services.prefs.clearUserPref("network.trr.odoh.target_path"); - Services.prefs.clearUserPref("network.trr.odoh.configs_uri"); - certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData( - false - ); - }); - - if (mozinfo.socketprocess_networking) { - Services.dns; // Needed to trigger socket process. - await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched); - } - - Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY); -}); - -add_task(async function testODoHConfig() { - // use the h2 server as DOH provider - Services.prefs.setCharPref( - "network.trr.uri", - "https://foo.example.com:" + h2Port + "/odohconfig" - ); - - let { inRecord } = await new TRRDNSListener("odoh_host.example.com", { - type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC, - }); - let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records; - Assert.equal(answer[0].priority, 1); - Assert.equal(answer[0].name, "odoh_host.example.com"); - Assert.equal(answer[0].values.length, 1); - let odohconfig = answer[0].values[0].QueryInterface( - Ci.nsISVCParamODoHConfig - ).ODoHConfig; - Assert.equal(odohconfig.length, 46); -}); - -let testIndex = 0; - -async function ODoHConfigTest(query, ODoHHost, expectedResult = false) { - Services.prefs.setCharPref( - "network.trr.uri", - "https://foo.example.com:" + h2Port + "/odohconfig?" + query - ); - - // Setting the pref "network.trr.odoh.target_host" will trigger the reload of - // the ODoHConfigs. - if (ODoHHost != undefined) { - Services.prefs.setCharPref("network.trr.odoh.target_host", ODoHHost); - } else { - Services.prefs.setCharPref( - "network.trr.odoh.target_host", - `https://odoh_host_${testIndex++}.com` - ); - } - - await topicObserved("odoh-service-activated"); - Assert.equal(Services.dns.ODoHActivated, expectedResult); -} - -add_task(async function testODoHConfig1() { - await ODoHConfigTest("invalid=empty"); -}); - -add_task(async function testODoHConfig2() { - await ODoHConfigTest("invalid=version"); -}); - -add_task(async function testODoHConfig3() { - await ODoHConfigTest("invalid=configLength"); -}); - -add_task(async function testODoHConfig4() { - await ODoHConfigTest("invalid=totalLength"); -}); - -add_task(async function testODoHConfig5() { - await ODoHConfigTest("invalid=kemId"); -}); - -add_task(async function testODoHConfig6() { - // Use a very short TTL. - Services.prefs.setIntPref("network.trr.odoh.min_ttl", 1); - await ODoHConfigTest("invalid=kemId&ttl=1"); - - // This is triggered by the expiration of the TTL. - await topicObserved("odoh-service-activated"); - Assert.ok(!Services.dns.ODoHActivated); - Services.prefs.clearUserPref("network.trr.odoh.min_ttl"); -}); - -add_task(async function testODoHConfig7() { - Services.dns.clearCache(true); - Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first - Services.prefs.setBoolPref("network.trr.odoh.enabled", true); - // At this point, we've queried the ODoHConfig, but there is no usable config - // (kemId is not supported). So, we should see the DNS result is from the - // native resolver. - await new TRRDNSListener("bar.example.com", "127.0.0.1"); -}); - -async function ODoHConfigTestHTTP(configUri, expectedResult) { - // Setting the pref "network.trr.odoh.configs_uri" will trigger the reload of - // the ODoHConfigs. - Services.prefs.setCharPref("network.trr.odoh.configs_uri", configUri); - - await topicObserved("odoh-service-activated"); - Assert.equal(Services.dns.ODoHActivated, expectedResult); -} - -add_task(async function testODoHConfig8() { - Services.dns.clearCache(true); - Services.prefs.setCharPref("network.trr.uri", ""); - - await ODoHConfigTestHTTP( - `https://foo.example.com:${h2Port}/odohconfig?downloadFrom=http`, - true - ); -}); - -add_task(async function testODoHConfig9() { - // Reset the prefs back to the correct values, so we will get valid - // ODoHConfigs when "network.trr.odoh.configs_uri" is cleared below. - Services.prefs.setCharPref( - "network.trr.uri", - `https://foo.example.com:${h2Port}/odohconfig` - ); - Services.prefs.setCharPref( - "network.trr.odoh.target_host", - `https://odoh_host.example.com:${h2Port}` - ); - - // Use a very short TTL. - Services.prefs.setIntPref("network.trr.odoh.min_ttl", 1); - // This will trigger to download ODoHConfigs from HTTPS RR. - Services.prefs.clearUserPref("network.trr.odoh.configs_uri"); - - await topicObserved("odoh-service-activated"); - Assert.ok(Services.dns.ODoHActivated); - - await ODoHConfigTestHTTP( - `https://foo.example.com:${h2Port}/odohconfig?downloadFrom=http`, - true - ); - - // This is triggered by the expiration of the TTL. - await topicObserved("odoh-service-activated"); - Assert.ok(Services.dns.ODoHActivated); - Services.prefs.clearUserPref("network.trr.odoh.min_ttl"); -}); - -add_task(async function testODoHConfig10() { - // We can still get ODoHConfigs from HTTPS RR. - await ODoHConfigTestHTTP("http://invalid_odoh_config.com", true); -}); - -add_task(async function ensureODoHConfig() { - Services.prefs.setBoolPref("network.trr.odoh.enabled", true); - // Make sure we can connect to ODOH target successfully. - Services.prefs.setCharPref( - "network.dns.localDomains", - "odoh_host.example.com" - ); - - // Make sure we have an usable ODoHConfig to use. - await ODoHConfigTestHTTP( - `https://foo.example.com:${h2Port}/odohconfig?downloadFrom=http`, - true - ); -}); - -add_task(test_A_record); - -add_task(test_AAAA_records); - -add_task(test_RFC1918); - -add_task(test_GET_ECS); - -add_task(test_timeout_mode3); - -add_task(test_strict_native_fallback); - -add_task(test_no_answers_fallback); - -add_task(test_404_fallback); - -add_task(test_mode_1_and_4); - -add_task(test_CNAME); - -add_task(test_name_mismatch); - -add_task(test_mode_2); - -add_task(test_excluded_domains); - -add_task(test_captiveportal_canonicalURL); - -add_task(test_parentalcontrols); - -// TRR-first check that DNS result is used if domain is part of the builtin-excluded-domains pref -add_task(test_builtin_excluded_domains); - -add_task(test_excluded_domains_mode3); - -add_task(test25e); - -add_task(test_parentalcontrols_mode3); - -add_task(test_builtin_excluded_domains_mode3); - -add_task(count_cookies); - -add_task(test_connection_closed); - -add_task(test_fetch_time); - -add_task(test_fqdn); - -add_task(test_ipv6_trr_fallback); - -add_task(test_ipv4_trr_fallback); - -add_task(test_no_retry_without_doh); - -add_task(test_connection_reuse_and_cycling).skip(); // Bug 1742743 - -add_task(async function testODoHConfigNotAvailableInMode3() { - Services.dns.clearCache(true); - Services.prefs.setIntPref("network.trr.mode", 3); - Services.prefs.setCharPref("network.trr.uri", ""); - - await ODoHConfigTestHTTP("https://failed_odoh_config.com", false); - - // In mode 3, the DNS lookup should fail. - let { inStatus } = await new TRRDNSListener("test.example.com", { - expectedSuccess: false, - }); - - Assert.equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST); -}); - -add_task(async function testODoHConfigNotAvailableInMode2() { - Services.dns.clearCache(true); - Services.prefs.setIntPref("network.trr.mode", 2); - Services.prefs.setCharPref("network.trr.uri", ""); - - await ODoHConfigTestHTTP("https://failed_odoh_config_1.com", false); - - // In mode 2, we fallback to native. - await new TRRDNSListener("test.example.com", "127.0.0.1"); -}); diff --git a/netwerk/test/unit/test_trr.js b/netwerk/test/unit/test_trr.js index bed4a78e6d1a..1fcba44f8604 100644 --- a/netwerk/test/unit/test_trr.js +++ b/netwerk/test/unit/test_trr.js @@ -8,7 +8,6 @@ SetParentalControlEnabled(false); function setup() { h2Port = trr_test_setup(); - runningODoHTests = false; } setup(); diff --git a/netwerk/test/unit/test_trr_telemetry.js b/netwerk/test/unit/test_trr_telemetry.js index 8ac8274d2371..69f5d59201ff 100644 --- a/netwerk/test/unit/test_trr_telemetry.js +++ b/netwerk/test/unit/test_trr_telemetry.js @@ -15,7 +15,6 @@ const { TelemetryTestUtils } = ChromeUtils.importESModule( function setup() { h2Port = trr_test_setup(); - runningODoHTests = false; } let TRR_OK = 1; diff --git a/netwerk/test/unit/test_trr_with_proxy.js b/netwerk/test/unit/test_trr_with_proxy.js index 2f95bf2acdfa..420cc9e7164a 100644 --- a/netwerk/test/unit/test_trr_with_proxy.js +++ b/netwerk/test/unit/test_trr_with_proxy.js @@ -21,7 +21,6 @@ const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(); function setup() { h2Port = trr_test_setup(); - runningODoHTests = false; SetParentalControlEnabled(false); } diff --git a/netwerk/test/unit/trr_common.js b/netwerk/test/unit/trr_common.js index 779a23249c73..62cf4d094db8 100644 --- a/netwerk/test/unit/trr_common.js +++ b/netwerk/test/unit/trr_common.js @@ -38,7 +38,6 @@ async function SetParentalControlEnabled(aEnabled) { MockRegistrar.unregister(cid); } -let runningODoHTests = false; let runningOHTTPTests = false; let h2Port; @@ -69,8 +68,6 @@ function setModeAndURIForOHTTP(mode, path, domain) { function setModeAndURI(mode, path, domain) { if (runningOHTTPTests) { setModeAndURIForOHTTP(mode, path, domain); - } else if (runningODoHTests) { - setModeAndURIForODoH(mode, path); } else { Services.prefs.setIntPref("network.trr.mode", mode); if (domain) { @@ -197,11 +194,7 @@ async function test_GET_ECS() { info("Verifying resolution via GET with ECS disabled"); Services.dns.clearCache(true); // The template part should be discarded - if (runningODoHTests) { - setModeAndURI(3, "odoh"); - } else { - setModeAndURI(3, "doh{?dns}"); - } + setModeAndURI(3, "doh{?dns}"); Services.prefs.setBoolPref("network.trr.useGET", true); Services.prefs.setBoolPref("network.trr.disable-ECS", true); @@ -209,11 +202,7 @@ async function test_GET_ECS() { info("Verifying resolution via GET with ECS enabled"); Services.dns.clearCache(true); - if (runningODoHTests) { - setModeAndURI(3, "odoh"); - } else { - setModeAndURI(3, "doh"); - } + setModeAndURI(3, "doh"); Services.prefs.setBoolPref("network.trr.disable-ECS", false); await new TRRDNSListener("get.example.com", "5.5.5.5"); @@ -364,14 +353,6 @@ async function test_strict_native_fallback() { info("Now with confirmation failed - should fallback"); Services.dns.clearCache(true); setModeAndURI(2, "doh?responseIP=2.2.2.2&corruptedAnswer=true"); - if (runningODoHTests) { - Services.prefs.setCharPref( - "network.trr.uri", - "https://foo.example.com:" + - h2Port + - "/odohconfig?failConfirmation=true" - ); - } Services.prefs.setCharPref("network.trr.confirmationNS", "example.com"); await TestUtils.waitForCondition( // 3 => CONFIRM_FAILED, 4 => CONFIRM_TRYING_FAILED @@ -390,12 +371,6 @@ async function test_strict_native_fallback() { setModeAndURI(2, "doh?responseIP=2.2.2.2"); if (!mozinfo.socketprocess_networking) { // Only need to reset confirmation state if we messed with it before. - if (runningODoHTests) { - Services.prefs.setCharPref( - "network.trr.uri", - "https://foo.example.com:" + h2Port + "/odohconfig" - ); - } Services.prefs.setCharPref("network.trr.confirmationNS", "skip"); await TestUtils.waitForCondition( // 5 => CONFIRM_DISABLED @@ -496,22 +471,14 @@ async function test_CNAME() { // The dns-cname path alternates between sending us a CNAME pointing to // another domain, and an A record. If we follow the cname correctly, doing // a lookup with this path as the DoH URI should resolve to that A record. - if (runningODoHTests) { - setModeAndURI(3, "odoh?cname=content"); - } else { - setModeAndURI(3, "dns-cname"); - } + setModeAndURI(3, "dns-cname"); await new TRRDNSListener("cname.example.com", "99.88.77.66"); info("Verifying that we bail out when we're thrown into a CNAME loop"); Services.dns.clearCache(true); // First mode 3. - if (runningODoHTests) { - setModeAndURI(3, "odoh?responseIP=none&cnameloop=true"); - } else { - setModeAndURI(3, "doh?responseIP=none&cnameloop=true"); - } + setModeAndURI(3, "doh?responseIP=none&cnameloop=true"); let { inStatus } = await new TRRDNSListener( "test18.example.com", @@ -525,22 +492,14 @@ async function test_CNAME() { // Now mode 2. Services.dns.clearCache(true); - if (runningODoHTests) { - setModeAndURI(2, "ododoh?responseIP=none&cnameloop=trueoh"); - } else { - setModeAndURI(2, "doh?responseIP=none&cnameloop=true"); - } + setModeAndURI(2, "doh?responseIP=none&cnameloop=true"); await new TRRDNSListener("test20.example.com", "127.0.0.1"); // Should fallback info("Check that we correctly handle CNAME bundled with an A record"); Services.dns.clearCache(true); // "dns-cname-a" path causes server to send a CNAME as well as an A record - if (runningODoHTests) { - setModeAndURI(3, "odoh?cname=ARecord"); - } else { - setModeAndURI(3, "dns-cname-a"); - } + setModeAndURI(3, "dns-cname-a"); await new TRRDNSListener("cname-a.example.com", "9.8.7.6"); } diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index 9bafab1c13ac..e92bf7b45f7d 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -611,11 +611,6 @@ skip-if = !debug (os == "win" && socketprocess_networking) [test_SuperfluousAuth.js] -[test_odoh.js] -head = head_channels.js head_cache.js head_cookies.js head_trr.js head_http3.js trr_common.js -run-sequentially = node server exceptions dont replay well -skip-if = - os == "win" && os_version == "6.1" # Skip on Azure - frequent failure [test_trr_confirmation.js] skip-if = socketprocess_networking # confirmation state isn't passed cross-process diff --git a/python/mozbuild/mozbuild/action/test_archive.py b/python/mozbuild/mozbuild/action/test_archive.py index 720c2f024318..06fef60f8d14 100644 --- a/python/mozbuild/mozbuild/action/test_archive.py +++ b/python/mozbuild/mozbuild/action/test_archive.py @@ -544,7 +544,6 @@ ARCHIVE_FILES = { "node_ip/**", "node-ws/**", "dns-packet/**", - "odoh-wasm/**", "remotexpcshelltests.py", "runxpcshelltests.py", "selftest.py", diff --git a/testing/xpcshell/moz-http2/moz-http2.js b/testing/xpcshell/moz-http2/moz-http2.js index be9a904dcb1c..334ccd8e1c7c 100644 --- a/testing/xpcshell/moz-http2/moz-http2.js +++ b/testing/xpcshell/moz-http2/moz-http2.js @@ -20,7 +20,6 @@ const ip = require(`${node_http2_root}/../node_ip`); const { fork } = require("child_process"); const path = require("path"); const zlib = require("zlib"); -const odoh = require(`${node_http2_root}/../odoh-wasm/pkg`); // Hook into the decompression code to log the decompressed name-value pairs var compression_module = node_http2_root + "/lib/protocol/compressor"; @@ -1121,174 +1120,6 @@ function handleRequest(req, res) { res.end(""); }); return; - } else if (u.pathname === "/odohconfig") { - let payload = Buffer.from(""); - req.on("data", function receiveData(chunk) { - payload = Buffer.concat([payload, chunk]); - }); - req.on("end", function finishedData() { - let answers = []; - let odohconfig; - if (u.query.invalid) { - if (u.query.invalid === "empty") { - odohconfig = Buffer.from(""); - } else if (u.query.invalid === "version") { - odohconfig = Buffer.from( - "002cff030028002000010001002021c8c16355091b28d521cb196627297955c1b607a3dcf1f136534578460d077d", - "hex" - ); - } else if (u.query.invalid === "configLength") { - odohconfig = Buffer.from( - "002cff040028002000010001002021c8c16355091b28d521cb196627297955c1b607a3dcf1f136534578460d07", - "hex" - ); - } else if (u.query.invalid === "totalLength") { - odohconfig = Buffer.from( - "012cff030028002000010001002021c8c16355091b28d521cb196627297955c1b607a3dcf1f136534578460d077d", - "hex" - ); - } else if (u.query.invalid === "kemId") { - odohconfig = Buffer.from( - "002cff040028002100010001002021c8c16355091b28d521cb196627297955c1b607a3dcf1f136534578460d077d", - "hex" - ); - } - } else { - odohconfig = odoh.get_odoh_config(); - } - - if (u.query.downloadFrom === "http") { - res.writeHead(200); - res.write(odohconfig); - res.end(""); - } else { - var b64encoded = Buffer.from(odohconfig).toString("base64"); - let packet = dnsPacket.decode(payload); - if ( - u.query.failConfirmation == "true" && - packet.questions[0].type == "NS" && - packet.questions[0].name == "example.com" - ) { - res.writeHead(200); - res.write("<12bytes"); - res.end(""); - return; - } - if (packet.questions[0].type == "HTTPS") { - answers.push({ - name: packet.questions[0].name, - type: packet.questions[0].type, - ttl: u.query.ttl ? u.query.ttl : 55, - class: "IN", - flush: false, - data: { - priority: 1, - name: packet.questions[0].name, - values: [ - { - key: "odoh", - value: b64encoded, - needBase64Decode: true, - }, - ], - }, - }); - } - - let buf = dnsPacket.encode({ - type: "response", - id: packet.id, - flags: dnsPacket.RECURSION_DESIRED, - questions: packet.questions, - answers, - }); - - res.setHeader("Content-Type", "application/dns-message"); - res.setHeader("Content-Length", buf.length); - res.writeHead(200); - res.write(buf); - res.end(""); - } - }); - return; - } else if (u.pathname === "/odoh") { - let responseIP = u.query.responseIP; - if (!responseIP) { - responseIP = "5.5.5.5"; - } - - if (u.query.auth) { - if (!handleAuth()) { - return; - } - } - - if (u.query.noResponse) { - return; - } - - let payload = Buffer.from(""); - - function emitResponse(response, requestPayload) { - let decryptedQuery = odoh.decrypt_query(requestPayload); - let packet = dnsPacket.decode(Buffer.from(decryptedQuery.buffer)); - let answer = createDNSAnswer( - response, - packet, - responseIP, - requestPayload - ); - if (!answer) { - return; - } - - let encryptedResponse = odoh.create_response(answer); - writeDNSResponse( - response, - encryptedResponse, - getDelayFromPacket(packet, responseType(packet, responseIP)), - "application/oblivious-dns-message" - ); - } - - if (u.query.dns) { - payload = Buffer.from(u.query.dns, "base64"); - emitResponse(res, payload); - return; - } - - req.on("data", function receiveData(chunk) { - payload = Buffer.concat([payload, chunk]); - }); - req.on("end", function finishedData() { - if (u.query.httpError) { - res.writeHead(404); - res.end("Not Found"); - return; - } - - if (u.query.cname) { - let decryptedQuery = odoh.decrypt_query(payload); - let rContent; - if (u.query.cname === "ARecord") { - rContent = createCNameARecord(); - } else { - rContent = createCNameContent(Buffer.from(decryptedQuery.buffer)); - } - let encryptedResponse = odoh.create_response(rContent); - res.setHeader("Content-Type", "application/oblivious-dns-message"); - res.setHeader("Content-Length", encryptedResponse.length); - res.writeHead(200); - res.write(encryptedResponse); - res.end(""); - return; - } - // parload is empty when we send redirect response. - if (payload.length) { - emitResponse(res, payload); - } - }); - return; } else if (u.pathname === "/httpssvc_as_altsvc") { let payload = Buffer.from(""); req.on("data", function receiveData(chunk) { diff --git a/testing/xpcshell/odoh-wasm/Cargo.toml b/testing/xpcshell/odoh-wasm/Cargo.toml deleted file mode 100644 index f709e66139d5..000000000000 --- a/testing/xpcshell/odoh-wasm/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[workspace] - -[package] -name = "odoh-wasm" -version = "0.1.0" -authors = ["Kershaw Chang "] -edition = "2018" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -default = ["console_error_panic_hook"] - -[dependencies] -wasm-bindgen = "0.2.63" -odoh-rs = "=0.1.10" -hpke = "=0.5.0" -js-sys = "0.3" -hex = "0.4" -futures = "0.3.1" -rand = "=0.7" - -# The `console_error_panic_hook` crate provides better debugging of panics by -# logging them with `console.error`. This is great for development, but requires -# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for -# code size when deploying. -console_error_panic_hook = { version = "0.1.6", optional = true } - -# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size -# compared to the default allocator's ~10K. It is slower than the default -# allocator, however. -# -# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. -wee_alloc = { version = "0.4.5", optional = true } - -[dev-dependencies] -wasm-bindgen-test = "0.3.13" - -[profile.release] -# Tell `rustc` to optimize for small code size. -opt-level = "s" diff --git a/testing/xpcshell/odoh-wasm/LICENSE_APACHE b/testing/xpcshell/odoh-wasm/LICENSE_APACHE deleted file mode 100644 index 1b5ec8b78e23..000000000000 --- a/testing/xpcshell/odoh-wasm/LICENSE_APACHE +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS diff --git a/testing/xpcshell/odoh-wasm/LICENSE_MIT b/testing/xpcshell/odoh-wasm/LICENSE_MIT deleted file mode 100644 index 681a58f76e09..000000000000 --- a/testing/xpcshell/odoh-wasm/LICENSE_MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2018 Kershaw Chang - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/testing/xpcshell/odoh-wasm/README.md b/testing/xpcshell/odoh-wasm/README.md deleted file mode 100644 index d3e413bfec51..000000000000 --- a/testing/xpcshell/odoh-wasm/README.md +++ /dev/null @@ -1,75 +0,0 @@ -
- -

wasm-pack-template

- - A template for kick starting a Rust and WebAssembly project using wasm-pack. - -

- Build Status -

- -

- Tutorial - | - Chat -

- - Built with 🦀🕸 by The Rust and WebAssembly Working Group -
- -## About - -[**📚 Read this template tutorial! 📚**][template-docs] - -This template is designed for compiling Rust libraries into WebAssembly and -publishing the resulting package to NPM. - -Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other -templates and usages of `wasm-pack`. - -[tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html -[template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html - -## 🚴 Usage - -### 🐑 Use `cargo generate` to Clone this Template - -[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate) - -``` -cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project -cd my-project -``` - -### 🛠️ Build with `wasm-pack build` - -``` -wasm-pack build -``` - -### 🛠️ Build a module that can be used for nodejs - -``` -wasm-pack build --target nodejs -``` - -### 🔬 Test in Headless Browsers with `wasm-pack test` - -``` -wasm-pack test --headless --firefox -``` - -### 🎁 Publish to NPM with `wasm-pack publish` - -``` -wasm-pack publish -``` - -## 🔋 Batteries Included - -* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating - between WebAssembly and JavaScript. -* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) - for logging panic messages to the developer console. -* [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized - for small code size. diff --git a/testing/xpcshell/odoh-wasm/pkg/README.md b/testing/xpcshell/odoh-wasm/pkg/README.md deleted file mode 100644 index 1e4617a6d293..000000000000 --- a/testing/xpcshell/odoh-wasm/pkg/README.md +++ /dev/null @@ -1,69 +0,0 @@ -
- -

wasm-pack-template

- - A template for kick starting a Rust and WebAssembly project using wasm-pack. - -

- Build Status -

- -

- Tutorial - | - Chat -

- - Built with 🦀🕸 by The Rust and WebAssembly Working Group -
- -## About - -[**📚 Read this template tutorial! 📚**][template-docs] - -This template is designed for compiling Rust libraries into WebAssembly and -publishing the resulting package to NPM. - -Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other -templates and usages of `wasm-pack`. - -[tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html -[template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html - -## 🚴 Usage - -### 🐑 Use `cargo generate` to Clone this Template - -[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate) - -``` -cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project -cd my-project -``` - -### 🛠️ Build with `wasm-pack build` - -``` -wasm-pack build -``` - -### 🔬 Test in Headless Browsers with `wasm-pack test` - -``` -wasm-pack test --headless --firefox -``` - -### 🎁 Publish to NPM with `wasm-pack publish` - -``` -wasm-pack publish -``` - -## 🔋 Batteries Included - -* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating - between WebAssembly and JavaScript. -* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) - for logging panic messages to the developer console. -* [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized - for small code size. diff --git a/testing/xpcshell/odoh-wasm/pkg/odoh_wasm.d.ts b/testing/xpcshell/odoh-wasm/pkg/odoh_wasm.d.ts deleted file mode 100644 index 1f90ef659192..000000000000 --- a/testing/xpcshell/odoh-wasm/pkg/odoh_wasm.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** -* @returns {Uint8Array} -*/ -export function get_odoh_config(): Uint8Array; -/** -* @param {Uint8Array} odoh_encrypted_query_msg -* @returns {Uint8Array} -*/ -export function decrypt_query(odoh_encrypted_query_msg: Uint8Array): Uint8Array; -/** -* @param {Uint8Array} response -* @returns {Uint8Array} -*/ -export function create_response(response: Uint8Array): Uint8Array; diff --git a/testing/xpcshell/odoh-wasm/pkg/odoh_wasm.js b/testing/xpcshell/odoh-wasm/pkg/odoh_wasm.js deleted file mode 100644 index 14b97d74365b..000000000000 --- a/testing/xpcshell/odoh-wasm/pkg/odoh_wasm.js +++ /dev/null @@ -1,132 +0,0 @@ -let imports = {}; -imports['__wbindgen_placeholder__'] = module.exports; -let wasm; -const { TextDecoder } = require(`util`); - -const heap = new Array(32).fill(undefined); - -heap.push(undefined, null, true, false); - -function getObject(idx) { return heap[idx]; } - -let heap_next = heap.length; - -function dropObject(idx) { - if (idx < 36) return; - heap[idx] = heap_next; - heap_next = idx; -} - -function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; -} - -let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); - -cachedTextDecoder.decode(); - -let cachegetUint8Memory0 = null; -function getUint8Memory0() { - if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { - cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); - } - return cachegetUint8Memory0; -} - -function getStringFromWasm0(ptr, len) { - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); -} - -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); - const idx = heap_next; - heap_next = heap[idx]; - - heap[idx] = obj; - return idx; -} -/** -* @returns {Uint8Array} -*/ -module.exports.get_odoh_config = function() { - var ret = wasm.get_odoh_config(); - return takeObject(ret); -}; - -let WASM_VECTOR_LEN = 0; - -function passArray8ToWasm0(arg, malloc) { - const ptr = malloc(arg.length * 1); - getUint8Memory0().set(arg, ptr / 1); - WASM_VECTOR_LEN = arg.length; - return ptr; -} -/** -* @param {Uint8Array} odoh_encrypted_query_msg -* @returns {Uint8Array} -*/ -module.exports.decrypt_query = function(odoh_encrypted_query_msg) { - var ptr0 = passArray8ToWasm0(odoh_encrypted_query_msg, wasm.__wbindgen_malloc); - var len0 = WASM_VECTOR_LEN; - var ret = wasm.decrypt_query(ptr0, len0); - return takeObject(ret); -}; - -/** -* @param {Uint8Array} response -* @returns {Uint8Array} -*/ -module.exports.create_response = function(response) { - var ptr0 = passArray8ToWasm0(response, wasm.__wbindgen_malloc); - var len0 = WASM_VECTOR_LEN; - var ret = wasm.create_response(ptr0, len0); - return takeObject(ret); -}; - -module.exports.__wbindgen_object_drop_ref = function(arg0) { - takeObject(arg0); -}; - -module.exports.__wbg_log_b3f203d9e6882397 = function(arg0, arg1) { - console.log(getStringFromWasm0(arg0, arg1)); -}; - -module.exports.__wbg_buffer_eb2155f17856c20b = function(arg0) { - var ret = getObject(arg0).buffer; - return addHeapObject(ret); -}; - -module.exports.__wbg_newwithbyteoffsetandlength_7d07f77c6d0d8e26 = function(arg0, arg1, arg2) { - var ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0); - return addHeapObject(ret); -}; - -module.exports.__wbg_new_ff8b26f7b2d7e2fb = function(arg0) { - var ret = new Uint8Array(getObject(arg0)); - return addHeapObject(ret); -}; - -module.exports.__wbg_newwithlength_a49b32b2030b93c3 = function(arg0) { - var ret = new Uint8Array(arg0 >>> 0); - return addHeapObject(ret); -}; - -module.exports.__wbindgen_throw = function(arg0, arg1) { - throw new Error(getStringFromWasm0(arg0, arg1)); -}; - -module.exports.__wbindgen_memory = function() { - var ret = wasm.memory; - return addHeapObject(ret); -}; - -const path = require('path').join(__dirname, 'odoh_wasm_bg.wasm'); -const bytes = require('fs').readFileSync(path); - -const wasmModule = new WebAssembly.Module(bytes); -const wasmInstance = new WebAssembly.Instance(wasmModule, imports); -wasm = wasmInstance.exports; -module.exports.__wasm = wasm; - diff --git a/testing/xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm b/testing/xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm deleted file mode 100644 index ddca009ed480b81594b84fdc1b1f1caa1cb6cadc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165175 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 diff --git a/testing/xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm.d.ts b/testing/xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm.d.ts deleted file mode 100644 index e3a939f2e0fd..000000000000 --- a/testing/xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -export const memory: WebAssembly.Memory; -export function get_odoh_config(): number; -export function decrypt_query(a: number, b: number): number; -export function create_response(a: number, b: number): number; -export function __wbindgen_malloc(a: number): number; diff --git a/testing/xpcshell/odoh-wasm/pkg/package.json b/testing/xpcshell/odoh-wasm/pkg/package.json deleted file mode 100644 index e6db000676d2..000000000000 --- a/testing/xpcshell/odoh-wasm/pkg/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "odoh-wasm", - "collaborators": [ - "Kershaw Chang " - ], - "version": "0.1.0", - "files": [ - "odoh_wasm_bg.wasm", - "odoh_wasm.js", - "odoh_wasm_bg.js", - "odoh_wasm.d.ts" - ], - "main": "odoh_wasm.js", - "types": "odoh_wasm.d.ts" -} \ No newline at end of file diff --git a/testing/xpcshell/odoh-wasm/src/lib.rs b/testing/xpcshell/odoh-wasm/src/lib.rs deleted file mode 100644 index 8f94d2b567c3..000000000000 --- a/testing/xpcshell/odoh-wasm/src/lib.rs +++ /dev/null @@ -1,158 +0,0 @@ -use hpke::{ - kem::X25519HkdfSha256, - Kem as KemTrait, Serializable, -}; - -use odoh_rs::protocol::{ - create_response_msg, parse_received_query, - ObliviousDoHConfigContents, ObliviousDoHKeyPair, - ObliviousDoHQueryBody, -}; - -use futures::executor; -use hex; -use wasm_bindgen::prelude::*; - -pub type Kem = X25519HkdfSha256; - -// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global -// allocator. -#[cfg(feature = "wee_alloc")] -#[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - -pub const ODOH_VERSION: u16 = 0x0001; -const KEM_ID: u16 = 0x0020; -const KDF_ID: u16 = 0x0001; -const AEAD_ID: u16 = 0x0001; - -// random bytes, should be 32 bytes for X25519 keys -pub const IKM: &str = "871389a8727130974e3eb3ee528d440a871389a8727130974e3eb3ee528d440a"; - -#[wasm_bindgen] -extern "C" { - // Use `js_namespace` here to bind `console.log(..)` instead of just - // `log(..)` - #[wasm_bindgen(js_namespace = console)] - fn log(s: &str); - - // The `console.log` is quite polymorphic, so we can bind it with multiple - // signatures. Note that we need to use `js_name` to ensure we always call - // `log` in JS. - #[wasm_bindgen(js_namespace = console, js_name = log)] - fn log_u32(a: u32); - - // Multiple arguments too! - #[wasm_bindgen(js_namespace = console, js_name = log)] - fn log_many(a: &str, b: &str); -} - -macro_rules! console_log { - // Note that this is using the `log` function imported above during - // `bare_bones` - ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) -} - -fn generate_key_pair() -> ObliviousDoHKeyPair { - let ikm_bytes = hex::decode(IKM).unwrap(); - let (secret_key, public_key) = Kem::derive_keypair(&ikm_bytes); - let public_key_bytes = public_key.to_bytes().to_vec(); - let odoh_public_key = ObliviousDoHConfigContents { - kem_id: KEM_ID, - kdf_id: KDF_ID, - aead_id: AEAD_ID, - public_key: public_key_bytes, - }; - ObliviousDoHKeyPair { - private_key: secret_key, - public_key: odoh_public_key, - } -} - -#[wasm_bindgen] -pub fn get_odoh_config() -> js_sys::Uint8Array { - let key_pair = generate_key_pair(); - let public_key_bytes = key_pair.public_key.public_key; - let length_bytes = (public_key_bytes.len() as u16).to_be_bytes(); - let odoh_config_length = 12 + public_key_bytes.len(); - let version = ODOH_VERSION; - let odoh_contents_length = 8 + public_key_bytes.len(); - let kem_id = KEM_ID; // DHKEM(X25519, HKDF-SHA256) - let kdf_id = KDF_ID; // KDF(SHA-256) - let aead_id = AEAD_ID; // AEAD(AES-GCM-128) - let mut result = vec![]; - result.extend(&((odoh_config_length as u16).to_be_bytes())); - result.extend(&((version as u16).to_be_bytes())); - result.extend(&((odoh_contents_length as u16).to_be_bytes())); - result.extend(&((kem_id as u16).to_be_bytes())); - result.extend(&((kdf_id as u16).to_be_bytes())); - result.extend(&((aead_id as u16).to_be_bytes())); - result.extend(&length_bytes); - result.extend(&public_key_bytes); - return js_sys::Uint8Array::from(&result[..]); -} - -static mut QUERY_BODY: Option = None; -static mut SERVER_SECRET: Option> = None; - -#[wasm_bindgen] -pub fn decrypt_query( - odoh_encrypted_query_msg: &[u8], -) -> js_sys::Uint8Array { - let mut result = vec![]; - unsafe { - let key_pair = generate_key_pair(); - let parsed_res = - executor::block_on(parse_received_query(&key_pair, &odoh_encrypted_query_msg)); - let (parsed_query, secret) = match parsed_res { - Ok(t) => (t.0, t.1), - Err(_) => { - console_log!("parse_received_query failed!"); - return js_sys::Uint8Array::new_with_length(0) - }, - }; - - result.extend(&parsed_query.dns_msg); - - QUERY_BODY = Some(parsed_query); - SERVER_SECRET = Some(secret); - } - - return js_sys::Uint8Array::from(&result[..]); -} - -#[wasm_bindgen] -pub fn create_response( - response: &[u8], -) -> js_sys::Uint8Array { - unsafe { - if let Some(body) = &QUERY_BODY { - if let Some(secret) = &SERVER_SECRET { - // random bytes - let nonce = vec![0x1b, 0xff, 0xfd, 0xff, 0x1a, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xe]; - let result = executor::block_on(create_response_msg( - &secret, - &response, - None, - Some(nonce), - &body, - )); - let generated_response = match result { - Ok(r) => r, - Err(_) => { - console_log!("create_response_msg failed!"); - return js_sys::Uint8Array::new_with_length(0); - } - }; - - QUERY_BODY = None; - SERVER_SECRET = None; - return js_sys::Uint8Array::from(&generated_response[..]); - } - } - } - - console_log!("create_response_msg failed!"); - return js_sys::Uint8Array::new_with_length(0); -} diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index b4be974dbff7..a7682bf7879d 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -4174,15 +4174,6 @@ "bug_numbers": [1682552], "alert_emails": ["necko@mozilla.com", "kershaw@mozilla.com"] }, - "HTTP_CHANNEL_ONSTART_SUCCESS_ODOH": { - "record_in_processes": ["main"], - "products": ["firefox"], - "expires_in_version": "never", - "kind": "boolean", - "description": "Successfully started HTTP channels when ODoH is enabled", - "bug_numbers": [1689987], - "alert_emails": ["necko@mozilla.com", "kershaw@mozilla.com"] - }, "HTTP_CONNECTION_ENTRY_CACHE_HIT_1": { "record_in_processes": ["main", "socket"], "products": ["firefox", "fennec"], @@ -17903,50 +17894,6 @@ "kind": "boolean", "description": "Whether an HTTP request gets upgraded to HTTPS because HTTPS RR is presented" }, - "DNS_ODOH_LOOKUP_TIME": { - "record_in_processes": ["main", "socket"], - "products": ["firefox"], - "expires_in_version": "never", - "kind": "exponential", - "high": 60000, - "alert_emails": ["necko@mozilla.com", "kershaw@mozilla.com"], - "bug_numbers": [1689987], - "n_buckets": 50, - "description": "Time for a completed ODoH resolution (msec)" - }, - "ODOH_SKIP_REASON_ODOH_FIRST": { - "record_in_processes": ["main", "socket"], - "products": ["firefox"], - "alert_emails": ["necko@mozilla.com", "kershaw@mozilla.com"], - "expires_in_version": "never", - "kind": "enumerated", - "n_values": 50, - "bug_numbers": [1689987], - "releaseChannelCollection": "opt-out", - "description": "When in ODoH-first mode, it lists the reason we may have skipped ODoH" - }, - "HTTP_PAGE_DNS_ODOH_LOOKUP_TIME": { - "record_in_processes": ["main", "content"], - "products": ["firefox"], - "expires_in_version": "never", - "alert_emails": ["necko@mozilla.com", "kershaw@mozilla.com"], - "kind": "exponential", - "high": 30000, - "n_buckets": 50, - "description": "HTTP page channel: ODoH lookup time (ms)", - "bug_numbers": [1689987] - }, - "HTTP_SUB_DNS_ODOH_LOOKUP_TIME": { - "record_in_processes": ["main", "content"], - "products": ["firefox"], - "expires_in_version": "never", - "kind": "exponential", - "high": 30000, - "n_buckets": 50, - "bug_numbers": [1689987], - "alert_emails": ["necko@mozilla.com", "kershaw@mozilla.com"], - "description": "HTTP subitem channel: ODoH lookup time (ms)" - }, "HTTP_PRELOAD_IMAGE_STARTREQUEST_DELAY": { "record_in_processes": ["content"], "products": ["firefox"], diff --git a/tools/rewriting/ThirdPartyPaths.txt b/tools/rewriting/ThirdPartyPaths.txt index 3f2bc8da5fbb..8f581671d7df 100644 --- a/tools/rewriting/ThirdPartyPaths.txt +++ b/tools/rewriting/ThirdPartyPaths.txt @@ -171,7 +171,6 @@ testing/xpcshell/dns-packet/ testing/xpcshell/node_ip/ testing/xpcshell/node-http2/ testing/xpcshell/node-ws/ -testing/xpcshell/odoh-wasm/ third_party/ toolkit/components/certviewer/content/vendor/ toolkit/components/jsoncpp/