Backed out changeset af645c9220f1 (bug 1835805) for causing bustage on DNSPacket.cpp. CLOSED TREE

This commit is contained in:
Natalia Csoregi 2023-05-31 18:52:13 +03:00
parent 9691ab4a5f
commit 448ccac0b9
52 changed files with 2883 additions and 35 deletions

View File

@ -1424,6 +1424,7 @@ 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/

View File

@ -12199,6 +12199,42 @@
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

View File

@ -1001,6 +1001,23 @@ void nsLoadGroup::TelemetryReportChannel(nsITimedChannel* aTimedChannel,
asyncOpen, requestStart);
}
if (StaticPrefs::network_trr_odoh_enabled() && !domainLookupStart.IsNull() &&
!domainLookupEnd.IsNull()) {
nsCOMPtr<nsIDNSService> 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
}

View File

@ -386,6 +386,14 @@ 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 =
@ -430,6 +438,12 @@ nsresult ChildDNSService::Init() {
AddPrefObserver(prefs);
}
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService) {
observerService->AddObserver(this, "odoh-service-activated", false);
}
return NS_OK;
}
@ -475,7 +489,10 @@ 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;
}

View File

@ -60,6 +60,8 @@ 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<nsCStringHashKey, nsTArray<RefPtr<DNSRequestSender>>>
mPendingRequests;

View File

@ -156,7 +156,39 @@ union NetAddr {
nsCString ToString() const;
};
enum class DNSResolverType : uint32_t { Native = 0, TRR };
#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<uint8_t> mPublicKey;
};
struct ObliviousDoHConfig {
uint16_t mVersion{};
uint16_t mLength{};
ObliviousDoHConfigContents mContents;
nsTArray<uint8_t> mConfigId;
};
enum ObliviousDoHMessageType : uint8_t {
ODOH_QUERY = 1,
ODOH_RESPONSE = 2,
};
struct ObliviousDoHMessage {
ObliviousDoHMessageType mType{ODOH_QUERY};
nsTArray<uint8_t> mKeyId;
nsTArray<uint8_t> mEncryptedMessage;
};
enum class DNSResolverType : uint32_t { Native = 0, TRR, ODoH };
class AddrInfo {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AddrInfo)
@ -184,7 +216,10 @@ class AddrInfo {
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
bool IsTRR() const { return mResolverType == DNSResolverType::TRR; }
bool IsTRROrODoH() const {
return mResolverType == DNSResolverType::TRR ||
mResolverType == DNSResolverType::ODoH;
}
DNSResolverType ResolverType() const { return mResolverType; }
unsigned int TRRType() { return mTRRType; }

View File

@ -8,6 +8,7 @@
#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"
@ -1076,5 +1077,552 @@ 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*)&params, 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, &paramsi, 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<const uint8_t> aData,
nsTArray<ObliviousDoHConfig>& 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 uint8_t>::const_iterator it = aData.begin();
uint16_t length = 0;
if (!get16bit(aData, it, length)) {
return false;
}
if (length != aData.Length() - 2) {
return false;
}
nsTArray<ObliviousDoHConfig> 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<HpkeKemId>(config.mContents.mKemId),
static_cast<HpkeKdfId>(config.mContents.mKdfId),
static_cast<HpkeAeadId>(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<uint8_t>(keyIdLength >> 8);
aBody += static_cast<uint8_t>(keyIdLength);
aBody.Append(reinterpret_cast<const char*>(message.mKeyId.Elements()),
keyIdLength);
uint16_t messageLen = message.mEncryptedMessage.Length();
aBody += static_cast<uint8_t>(messageLen >> 8);
aBody += static_cast<uint8_t>(messageLen);
aBody.Append(
reinterpret_cast<const char*>(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<HpkeKemId>(aConfig.mContents.mKemId),
static_cast<HpkeKdfId>(aConfig.mContents.mKdfId),
static_cast<HpkeAeadId>(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<nsCStringHashKey, DOHresp>& 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<unsigned int>(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<ObliviousDoHMessageType>(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*)&params, 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, &paramsItem,
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*)&params, 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, &paramsItem, 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<unsigned int>(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<unsigned int>(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<unsigned int>(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*)(&param);
paramItem.len = sizeof(CK_GCM_PARAMS);
memset(mResponse, 0, mBodySize);
rv = PK11_Decrypt(key.get(), CKM_AES_GCM, &paramItem, 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

View File

@ -106,6 +106,39 @@ class DNSPacket {
Maybe<nsCString> mOriginHost;
};
class ODoHDNSPacket final : public DNSPacket {
public:
ODoHDNSPacket() = default;
virtual ~ODoHDNSPacket();
static bool ParseODoHConfigs(Span<const uint8_t> aData,
nsTArray<ObliviousDoHConfig>& 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<nsCStringHashKey, DOHresp>& 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<DecryptedResponseRange> mDecryptedResponseRange;
};
} // namespace net
} // namespace mozilla

View File

@ -16,11 +16,13 @@ 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);

112
netwerk/dns/ODoH.cpp Normal file
View File

@ -0,0 +1,112 @@
/* -*- 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<ODoHDNSPacket>();
}
return mPacket.get();
}
nsresult ODoH::CreateQueryURI(nsIURI** aOutURI) {
nsAutoCString uri;
nsCOMPtr<nsIURI> 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

60
netwerk/dns/ODoH.h Normal file
View File

@ -0,0 +1,60 @@
/* -*- 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

521
netwerk/dns/ODoHService.cpp Normal file
View File

@ -0,0 +1,521 @@
/* -*- 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<nsIPrefBranch> 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<nsIObserverService> 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<nsIURI> 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<nsIEventTarget> 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<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), configUri);
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsIChannel> 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<nsIHttpChannel> 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<nsIStreamLoader> 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<nsIDNSService> 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<nsICancelable> tmpOutstanding;
nsCOMPtr<nsIEventTarget> 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<nsIDNSAdditionalInfo> 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<uint32_t>(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<const uint8_t> 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<ObliviousDoHConfig> 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<nsIObserverService> 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<RefPtr<ODoH>> requests = std::move(mPendingRequests);
nsCOMPtr<nsIEventTarget> 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<nsIDNSHTTPSSVCRecord> httpsRecord;
nsCString rawODoHConfig;
auto notifyDone = MakeScopeExit([&]() {
uint32_t ttl = 0;
if (httpsRecord) {
Unused << httpsRecord->GetTtl(&ttl);
}
ODoHConfigUpdateDone(
ttl,
Span(reinterpret_cast<const uint8_t*>(rawODoHConfig.BeginReading()),
rawODoHConfig.Length()));
});
LOG(("ODoHService::OnLookupComplete [aStatus=%" PRIx32 "]",
static_cast<uint32_t>(aStatus)));
if (NS_FAILED(aStatus)) {
return NS_OK;
}
httpsRecord = do_QueryInterface(aRec);
if (!httpsRecord) {
return NS_OK;
}
nsTArray<RefPtr<nsISVCBRecord>> 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<nsTArray<ObliviousDoHConfig>>& 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

80
netwerk/dns/ODoHService.h Normal file
View File

@ -0,0 +1,80 @@
/* -*- 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<nsTArray<ObliviousDoHConfig>>& 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<const uint8_t> aRawConfig);
nsresult UpdateODoHConfigFromHTTPSRR();
nsresult UpdateODoHConfigFromURI();
mozilla::Mutex mLock;
Atomic<bool, Relaxed> 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<nsTArray<ObliviousDoHConfig>> mODoHConfigs MOZ_GUARDED_BY(mLock);
nsTArray<RefPtr<ODoH>> mPendingRequests MOZ_GUARDED_BY(mLock);
// This timer is always touched on main thread to avoid race conditions.
nsCOMPtr<nsITimer> mTTLTimer;
nsCOMPtr<nsIStreamLoader> mLoader MOZ_GUARDED_BY(mLock);
};
extern ODoHService* gODoHService;
} // namespace net
} // namespace mozilla
#endif

View File

@ -26,6 +26,7 @@
#include "nsStringStream.h"
#include "nsThreadUtils.h"
#include "nsURLHelper.h"
#include "ODoH.h"
#include "ObliviousHttpChannel.h"
#include "TRR.h"
#include "TRRService.h"
@ -880,7 +881,9 @@ nsresult TRR::FollowCname(nsIChannel* aChannel) {
LOG(("TRR::On200Response CNAME %s => %s (%u)\n", mHost.get(), mCname.get(),
mCnameLoop));
RefPtr<TRR> trr =
new TRR(mHostResolver, mRec, mCname, mType, mCnameLoop, mPB);
ResolverType() == DNSResolverType::ODoH
? new ODoH(mHostResolver, mRec, mCname, mType, mCnameLoop, mPB)
: new TRR(mHostResolver, mRec, mCname, mType, mCnameLoop, mPB);
if (!TRRService::Get()) {
return NS_ERROR_FAILURE;
}

View File

@ -9,6 +9,7 @@
#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"
@ -64,10 +65,15 @@ void TRRQuery::MarkSendingTRR(TRR* trr, enum TrrType rectype, MutexAutoLock&) {
}
}
void TRRQuery::PrepareQuery(enum TrrType aRecType,
void TRRQuery::PrepareQuery(bool aUseODoH, enum TrrType aRecType,
nsTArray<RefPtr<TRR>>& aRequestsToSend) {
LOG(("TRR Resolve %s type %d\n", mRecord->host.get(), (int)aRecType));
RefPtr<TRR> trr = new TRR(this, mRecord, aRecType);
RefPtr<TRR> trr;
if (aUseODoH) {
trr = new ODoH(this, mRecord, aRecType);
} else {
trr = new TRR(this, mRecord, aRecType);
}
{
MutexAutoLock trrlock(mTrrLock);
@ -99,9 +105,14 @@ bool TRRQuery::SendQueries(nsTArray<RefPtr<TRR>>& aRequestsToSend) {
return madeQuery;
}
nsresult TRRQuery::DispatchLookup(TRR* pushedTRR) {
nsresult TRRQuery::DispatchLookup(TRR* pushedTRR, bool aUseODoH) {
if (aUseODoH && pushedTRR) {
MOZ_ASSERT(false, "ODoH should not support push");
return NS_ERROR_UNKNOWN_HOST;
}
if (!mRecord->IsAddrRecord()) {
return DispatchByTypeLookup(pushedTRR);
return DispatchByTypeLookup(pushedTRR, aUseODoH);
}
RefPtr<AddrHostRecord> addrRec = do_QueryObject(mRecord);
@ -131,20 +142,21 @@ nsresult TRRQuery::DispatchLookup(TRR* pushedTRR) {
// same time.
nsTArray<RefPtr<TRR>> requestsToSend;
if ((mRecord->af == AF_UNSPEC || mRecord->af == AF_INET6)) {
PrepareQuery(TRRTYPE_AAAA, requestsToSend);
PrepareQuery(aUseODoH, TRRTYPE_AAAA, requestsToSend);
}
if (mRecord->af == AF_UNSPEC || mRecord->af == AF_INET) {
PrepareQuery(TRRTYPE_A, requestsToSend);
PrepareQuery(aUseODoH, TRRTYPE_A, requestsToSend);
}
if (SendQueries(requestsToSend)) {
mUsingODoH = aUseODoH;
return NS_OK;
}
return NS_ERROR_UNKNOWN_HOST;
}
nsresult TRRQuery::DispatchByTypeLookup(TRR* pushedTRR) {
nsresult TRRQuery::DispatchByTypeLookup(TRR* pushedTRR, bool aUseODoH) {
RefPtr<TypeHostRecord> typeRec = do_QueryObject(mRecord);
MOZ_ASSERT(typeRec);
if (!typeRec) {
@ -167,7 +179,12 @@ nsresult TRRQuery::DispatchByTypeLookup(TRR* pushedTRR) {
}
LOG(("TRR Resolve %s type %d\n", typeRec->host.get(), (int)rectype));
RefPtr<TRR> trr = pushedTRR ? pushedTRR : new TRR(this, mRecord, rectype);
RefPtr<TRR> trr;
if (aUseODoH) {
trr = new ODoH(this, mRecord, rectype);
} else {
trr = pushedTRR ? pushedTRR : new TRR(this, mRecord, rectype);
}
if (pushedTRR || NS_SUCCEEDED(TRRService::Get()->DispatchTRRRequest(trr))) {
MutexAutoLock trrlock(mTrrLock);
@ -268,7 +285,7 @@ AHostResolver::LookupStatus TRRQuery::CompleteLookup(
if (newRRSet->TRRType() == TRRTYPE_A) {
LOG(("A lookup failed. Checking if AAAA record exists"));
nsTArray<RefPtr<TRR>> requestsToSend;
PrepareQuery(TRRTYPE_AAAA, requestsToSend);
PrepareQuery(mUsingODoH, TRRTYPE_AAAA, requestsToSend);
if (SendQueries(requestsToSend)) {
LOG(("Sent AAAA request"));
return LOOKUP_OK;
@ -276,7 +293,7 @@ AHostResolver::LookupStatus TRRQuery::CompleteLookup(
} else if (newRRSet->TRRType() == TRRTYPE_AAAA) {
LOG(("AAAA lookup failed. Checking if A record exists"));
nsTArray<RefPtr<TRR>> requestsToSend;
PrepareQuery(TRRTYPE_A, requestsToSend);
PrepareQuery(mUsingODoH, TRRTYPE_A, requestsToSend);
if (SendQueries(requestsToSend)) {
LOG(("Sent A request"));
return LOOKUP_OK;

View File

@ -22,7 +22,7 @@ class TRRQuery : public AHostResolver {
mRecord(aHostRecord),
mTrrLock("TRRQuery.mTrrLock") {}
nsresult DispatchLookup(TRR* pushedTRR = nullptr);
nsresult DispatchLookup(TRR* pushedTRR = nullptr, bool aUseODoHProxy = false);
void Cancel(nsresult aStatus);
@ -69,13 +69,15 @@ class TRRQuery : public AHostResolver {
mozilla::TimeDuration Duration() { return mTrrDuration; }
private:
nsresult DispatchByTypeLookup(TRR* pushedTRR = nullptr);
nsresult DispatchByTypeLookup(TRR* pushedTRR = nullptr,
bool aUseODoHProxy = false);
private:
~TRRQuery() = default;
void MarkSendingTRR(TRR* trr, TrrType rectype, MutexAutoLock&);
void PrepareQuery(TrrType aRecType, nsTArray<RefPtr<TRR>>& aRequestsToSend);
void PrepareQuery(bool aUseODoH, TrrType aRecType,
nsTArray<RefPtr<TRR>>& aRequestsToSend);
bool SendQueries(nsTArray<RefPtr<TRR>>& aRequestsToSend);
RefPtr<nsHostResolver> mHostResolver;
@ -94,6 +96,7 @@ class TRRQuery : public AHostResolver {
Atomic<uint32_t> mTRRRequestCounter{0};
uint8_t mTRRSuccess = 0; // number of successful TRR responses
bool mUsingODoH = false;
bool mCalledCompleteLookup = false;
mozilla::TimeDuration mTrrDuration;

View File

@ -216,6 +216,11 @@ nsresult TRRService::Init() {
sTRRBackgroundThread = thread;
}
mODoHService = new ODoHService();
if (!mODoHService->Init()) {
return NS_ERROR_FAILURE;
}
Preferences::RegisterCallbackAndCall(
EventTelemetryPrefChanged,
"network.trr.confirmation_telemetry_enabled"_ns);

View File

@ -11,6 +11,7 @@
#include "nsIObserver.h"
#include "nsITimer.h"
#include "nsWeakReference.h"
#include "ODoHService.h"
#include "TRRServiceBase.h"
#include "nsICaptivePortalService.h"
#include "nsTHashSet.h"
@ -108,6 +109,7 @@ class TRRService : public TRRServiceBase,
friend class TRRServiceChild;
friend class TRRServiceParent;
friend class ODoHService;
static void AddObserver(nsIObserver* aObserver,
nsIObserverService* aObserverService = nullptr);
static bool CheckCaptivePortalIsPassed();
@ -376,6 +378,7 @@ 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<ODoHService> mODoHService;
nsCOMPtr<nsINetworkLinkService> mLinkService;
};

View File

@ -78,6 +78,8 @@ UNIFIED_SOURCES += [
"nsHostRecord.cpp",
"nsHostResolver.cpp",
"nsIDNService.cpp",
"ODoH.cpp",
"ODoHService.cpp",
"punycode.c",
"TRR.cpp",
"TRRQuery.cpp",

View File

@ -128,7 +128,9 @@ NS_IMETHODIMP
nsDNSRecord::IsTRR(bool* retval) {
MutexAutoLock lock(mHostRecord->addr_info_lock);
if (mHostRecord->addr_info) {
*retval = mHostRecord->addr_info->IsTRR();
// 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();
} else {
*retval = false;
}
@ -144,7 +146,7 @@ nsDNSRecord::ResolvedInSocketProcess(bool* retval) {
NS_IMETHODIMP
nsDNSRecord::GetTrrFetchDuration(double* aTime) {
MutexAutoLock lock(mHostRecord->addr_info_lock);
if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRR()) {
if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRROrODoH()) {
*aTime = mHostRecord->addr_info->GetTrrFetchDuration();
} else {
*aTime = 0;
@ -155,7 +157,7 @@ nsDNSRecord::GetTrrFetchDuration(double* aTime) {
NS_IMETHODIMP
nsDNSRecord::GetTrrFetchDurationNetworkOnly(double* aTime) {
MutexAutoLock lock(mHostRecord->addr_info_lock);
if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRR()) {
if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRROrODoH()) {
*aTime = mHostRecord->addr_info->GetTrrFetchDurationNetworkOnly();
} else {
*aTime = 0;
@ -845,6 +847,7 @@ 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<nsHostResolver> res;
@ -1300,6 +1303,14 @@ 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) {
@ -1322,6 +1333,8 @@ 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) {

View File

@ -128,6 +128,7 @@ class nsDNSService final : public mozilla::net::DNSServiceBase,
uint32_t mResCacheExpiration = 0;
uint32_t mResCacheGrace = 0;
bool mResolverPrefsUpdated = false;
bool mODoHActivated = false;
nsClassHashtable<nsCStringHashKey, nsTArray<nsCString>> mFailedSVCDomainNames;
};

View File

@ -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->IsTRR()) {
if (addr_info && !aTrrToo && addr_info->IsTRROrODoH()) {
return false;
}
if (LoadNative()) {
@ -284,6 +284,21 @@ 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<uint32_t>(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<uint32_t>(mTRRSkippedReason));
}
return;
}
if (mResolverType == DNSResolverType::TRR) {
if (mTRRSuccess) {
MOZ_DIAGNOSTIC_ASSERT(mTRRSkippedReason ==

View File

@ -321,7 +321,7 @@ class AddrHostRecord final : public nsHostRecord {
mozilla::TimeDuration mTrrDuration;
mozilla::TimeDuration mNativeDuration;
// TRR was used on this record
// TRR or ODoH was used on this record
mozilla::Atomic<DNSResolverType> mResolverType{DNSResolverType::Native};
// clang-format off

View File

@ -36,6 +36,7 @@
#include "TRR.h"
#include "TRRQuery.h"
#include "TRRService.h"
#include "ODoHService.h"
#include "mozilla/Atomics.h"
#include "mozilla/HashFunctions.h"
@ -932,7 +933,9 @@ nsresult nsHostResolver::TrrLookup(nsHostRecord* aRec,
MaybeRenewHostRecordLocked(rec, aLock);
RefPtr<TRRQuery> query = new TRRQuery(this, rec);
nsresult rv = query->DispatchLookup(pushedTRR);
bool useODoH = gODoHService->Enabled() &&
!((rec->flags & nsIDNSService::RESOLVE_DISABLE_ODOH));
nsresult rv = query->DispatchLookup(pushedTRR, useODoH);
if (NS_FAILED(rv)) {
rec->RecordReason(TRRSkippedReason::TRR_DID_NOT_MAKE_QUERY);
return rv;
@ -1289,7 +1292,7 @@ void nsHostResolver::PrepareRecordExpirationAddrRecord(
unsigned int grace = mDefaultGracePeriod;
unsigned int ttl = mDefaultCacheLifetime;
if (sGetTtlEnabled || rec->addr_info->IsTRR()) {
if (sGetTtlEnabled || rec->addr_info->IsTRROrODoH()) {
if (rec->addr_info && rec->addr_info->TTL() != AddrInfo::NO_TTL_DATA) {
ttl = rec->addr_info->TTL();
}
@ -1601,7 +1604,7 @@ nsHostResolver::LookupStatus nsHostResolver::CompleteLookupLocked(
bool hasNativeResult = false;
{
MutexAutoLock lock(addrRec->addr_info_lock);
if (addrRec->addr_info && !addrRec->addr_info->IsTRR()) {
if (addrRec->addr_info && !addrRec->addr_info->IsTRROrODoH()) {
hasNativeResult = true;
}
}
@ -1896,7 +1899,7 @@ void nsHostResolver::GetDNSCacheEntries(nsTArray<DNSCacheEntries>* args) {
info.hostaddr.AppendElement(buf);
}
}
info.TRR = addrRec->addr_info->IsTRR();
info.TRR = addrRec->addr_info->IsTRROrODoH();
}
info.originAttributesSuffix = recordEntry.GetKey().originSuffix;

View File

@ -88,6 +88,8 @@ 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),
@ -347,6 +349,12 @@ 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().

View File

@ -128,6 +128,7 @@ parent:
OriginAttributes aOriginAttributes,
nsCString aRequestString)
returns (bool aAccepted);
async ODoHServiceActivated(bool aActivated);
async ExcludeHttp2OrHttp3(HttpConnectionInfoCloneArgs aArgs);

View File

@ -309,6 +309,18 @@ void SocketProcessParent::Destroy(RefPtr<SocketProcessParent>&& aParent) {
new DeferredDeleteSocketProcessParent(std::move(aParent)));
}
mozilla::ipc::IPCResult SocketProcessParent::RecvODoHServiceActivated(
const bool& aActivated) {
nsCOMPtr<nsIObserverService> 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<nsHttpConnectionInfo> cinfo =

View File

@ -98,6 +98,8 @@ 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);

View File

@ -1180,15 +1180,18 @@ 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"));
MOZ_ASSERT(mContentTypeHint.Equals("application/dns-message") ||
mContentTypeHint.Equals("application/oblivious-dns-message"));
// Apply TRR specific settings. Note that we already know mContentTypeHint is
// "application/dns-message" here.
// "application/dns-message" or "application/oblivious-dns-message" here.
return TRR::SetupTRRServiceChannelInternal(
httpChannel,
mRequestHead.ParsedMethod() == nsHttpRequestHead::kMethod_Get,

View File

@ -7029,7 +7029,17 @@ static void RecordOnStartTelemetry(nsresult aStatus,
Others = 2,
};
if (TRRService::Get() && TRRService::Get()->IsConfirmed()) {
if (StaticPrefs::network_trr_odoh_enabled()) {
nsCOMPtr<nsIDNSService> 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()) {
// Note this telemetry probe is not working when DNS resolution is done in
// the socket process.
HttpOnStartState state = HttpOnStartState::Others;

View File

@ -138,7 +138,7 @@ interface nsISocketProvider : nsISupports
/**
* If set, indicates that the connection used a privacy-preserving DNS
* transport such as DoH, DoQ or similar. Currently this field is
* transport such as DoH, DoQ, ODOH or similar. Currently this field is
* set only when DoH is used via the TRR.
*/
const unsigned long USED_PRIVATE_DNS = (1 << 12);

View File

@ -0,0 +1,295 @@
/* 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");
});

View File

@ -8,6 +8,7 @@ SetParentalControlEnabled(false);
function setup() {
h2Port = trr_test_setup();
runningODoHTests = false;
}
setup();

View File

@ -15,6 +15,7 @@ const { TelemetryTestUtils } = ChromeUtils.importESModule(
function setup() {
h2Port = trr_test_setup();
runningODoHTests = false;
}
let TRR_OK = 1;

View File

@ -21,6 +21,7 @@ const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
function setup() {
h2Port = trr_test_setup();
runningODoHTests = false;
SetParentalControlEnabled(false);
}

View File

@ -38,6 +38,7 @@ async function SetParentalControlEnabled(aEnabled) {
MockRegistrar.unregister(cid);
}
let runningODoHTests = false;
let runningOHTTPTests = false;
let h2Port;
@ -68,6 +69,8 @@ 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) {
@ -194,7 +197,11 @@ async function test_GET_ECS() {
info("Verifying resolution via GET with ECS disabled");
Services.dns.clearCache(true);
// The template part should be discarded
setModeAndURI(3, "doh{?dns}");
if (runningODoHTests) {
setModeAndURI(3, "odoh");
} else {
setModeAndURI(3, "doh{?dns}");
}
Services.prefs.setBoolPref("network.trr.useGET", true);
Services.prefs.setBoolPref("network.trr.disable-ECS", true);
@ -202,7 +209,11 @@ async function test_GET_ECS() {
info("Verifying resolution via GET with ECS enabled");
Services.dns.clearCache(true);
setModeAndURI(3, "doh");
if (runningODoHTests) {
setModeAndURI(3, "odoh");
} else {
setModeAndURI(3, "doh");
}
Services.prefs.setBoolPref("network.trr.disable-ECS", false);
await new TRRDNSListener("get.example.com", "5.5.5.5");
@ -353,6 +364,14 @@ 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
@ -371,6 +390,12 @@ 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
@ -471,14 +496,22 @@ 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.
setModeAndURI(3, "dns-cname");
if (runningODoHTests) {
setModeAndURI(3, "odoh?cname=content");
} else {
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.
setModeAndURI(3, "doh?responseIP=none&cnameloop=true");
if (runningODoHTests) {
setModeAndURI(3, "odoh?responseIP=none&cnameloop=true");
} else {
setModeAndURI(3, "doh?responseIP=none&cnameloop=true");
}
let { inStatus } = await new TRRDNSListener(
"test18.example.com",
@ -492,14 +525,22 @@ async function test_CNAME() {
// Now mode 2.
Services.dns.clearCache(true);
setModeAndURI(2, "doh?responseIP=none&cnameloop=true");
if (runningODoHTests) {
setModeAndURI(2, "ododoh?responseIP=none&cnameloop=trueoh");
} else {
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
setModeAndURI(3, "dns-cname-a");
if (runningODoHTests) {
setModeAndURI(3, "odoh?cname=ARecord");
} else {
setModeAndURI(3, "dns-cname-a");
}
await new TRRDNSListener("cname-a.example.com", "9.8.7.6");
}

View File

@ -611,6 +611,11 @@ 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

View File

@ -544,6 +544,7 @@ ARCHIVE_FILES = {
"node_ip/**",
"node-ws/**",
"dns-packet/**",
"odoh-wasm/**",
"remotexpcshelltests.py",
"runxpcshelltests.py",
"selftest.py",

View File

@ -20,6 +20,7 @@ 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";
@ -1120,6 +1121,174 @@ 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) {

View File

@ -0,0 +1,42 @@
[workspace]
[package]
name = "odoh-wasm"
version = "0.1.0"
authors = ["Kershaw Chang <kershaw@mozilla.com>"]
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"

View File

@ -0,0 +1,176 @@
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

View File

@ -0,0 +1,25 @@
Copyright (c) 2018 Kershaw Chang <kershaw@mozilla.com>
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.

View File

@ -0,0 +1,75 @@
<div align="center">
<h1><code>wasm-pack-template</code></h1>
<strong>A template for kick starting a Rust and WebAssembly project using <a href="https://github.com/rustwasm/wasm-pack">wasm-pack</a>.</strong>
<p>
<a href="https://travis-ci.org/rustwasm/wasm-pack-template"><img src="https://img.shields.io/travis/rustwasm/wasm-pack-template.svg?style=flat-square" alt="Build Status" /></a>
</p>
<h3>
<a href="https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html">Tutorial</a>
<span> | </span>
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
</h3>
<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
</div>
## 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.

View File

@ -0,0 +1,69 @@
<div align="center">
<h1><code>wasm-pack-template</code></h1>
<strong>A template for kick starting a Rust and WebAssembly project using <a href="https://github.com/rustwasm/wasm-pack">wasm-pack</a>.</strong>
<p>
<a href="https://travis-ci.org/rustwasm/wasm-pack-template"><img src="https://img.shields.io/travis/rustwasm/wasm-pack-template.svg?style=flat-square" alt="Build Status" /></a>
</p>
<h3>
<a href="https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html">Tutorial</a>
<span> | </span>
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
</h3>
<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
</div>
## 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.

View File

@ -0,0 +1,16 @@
/* 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;

View File

@ -0,0 +1,132 @@
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;

Binary file not shown.

View File

@ -0,0 +1,7 @@
/* 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;

View File

@ -0,0 +1,15 @@
{
"name": "odoh-wasm",
"collaborators": [
"Kershaw Chang <kershaw@mozilla.com>"
],
"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"
}

View File

@ -0,0 +1,158 @@
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<ObliviousDoHQueryBody> = None;
static mut SERVER_SECRET: Option<Vec<u8>> = 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);
}

View File

@ -4174,6 +4174,15 @@
"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"],
@ -17894,6 +17903,50 @@
"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"],

View File

@ -171,6 +171,7 @@ 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/