mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
Backed out changeset af645c9220f1 (bug 1835805) for causing bustage on DNSPacket.cpp. CLOSED TREE
This commit is contained in:
parent
9691ab4a5f
commit
448ccac0b9
@ -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/
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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*)¶ms, sizeof(params)};
|
||||
|
||||
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
||||
if (!slot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UniqueSECItem rawConfig(CreateRawConfig(aConfig));
|
||||
if (!rawConfig) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UniquePK11SymKey configKey(PK11_ImportDataKey(slot.get(), CKM_HKDF_DATA,
|
||||
PK11_OriginUnwrap, CKA_DERIVE,
|
||||
rawConfig.get(), nullptr));
|
||||
if (!configKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
params.bExtract = CK_TRUE;
|
||||
params.bExpand = CK_TRUE;
|
||||
params.prfHashMechanism = CKM_SHA256;
|
||||
params.ulSaltType = CKF_HKDF_SALT_NULL;
|
||||
params.pInfo = (unsigned char*)&hODoHConfigID[0];
|
||||
params.ulInfoLen = strlen(hODoHConfigID);
|
||||
UniquePK11SymKey derived(PK11_DeriveWithFlags(
|
||||
configKey.get(), CKM_HKDF_DATA, ¶msi, CKM_HKDF_DERIVE, CKA_DERIVE,
|
||||
SHA256_LENGTH, CKF_SIGN | CKF_VERIFY));
|
||||
|
||||
rv = PK11_ExtractKeyValue(derived.get());
|
||||
if (rv != SECSuccess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SECItem* derivedItem = PK11_GetKeyData(derived.get());
|
||||
if (!derivedItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (derivedItem->len != SHA256_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aConfig.mConfigId.AppendElements(derivedItem->data, derivedItem->len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool ODoHDNSPacket::ParseODoHConfigs(Span<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*)¶ms, sizeof(params)};
|
||||
|
||||
params.bExtract = CK_TRUE;
|
||||
params.bExpand = CK_FALSE;
|
||||
params.prfHashMechanism = CKM_SHA256;
|
||||
params.ulSaltType = aSalt ? CKF_HKDF_SALT_DATA : CKF_HKDF_SALT_NULL;
|
||||
params.pSalt = aSalt ? (CK_BYTE_PTR)aSalt->data : nullptr;
|
||||
params.ulSaltLen = aSalt ? aSalt->len : 0;
|
||||
|
||||
UniquePK11SymKey prk(PK11_Derive(aIkm, CKM_HKDF_DERIVE, ¶msItem,
|
||||
CKM_HKDF_DERIVE, CKA_DERIVE, 0));
|
||||
if (!prk) {
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
aOutKey.swap(prk);
|
||||
return SECSuccess;
|
||||
}
|
||||
|
||||
static SECStatus HKDFExpand(PK11SymKey* aPrk, const SECItem* aInfo, int aLen,
|
||||
bool aKey, UniquePK11SymKey& aOutKey) {
|
||||
CK_HKDF_PARAMS params = {0};
|
||||
SECItem paramsItem = {siBuffer, (unsigned char*)¶ms, sizeof(params)};
|
||||
|
||||
params.bExtract = CK_FALSE;
|
||||
params.bExpand = CK_TRUE;
|
||||
params.prfHashMechanism = CKM_SHA256;
|
||||
params.ulSaltType = CKF_HKDF_SALT_NULL;
|
||||
params.pInfo = (CK_BYTE_PTR)aInfo->data;
|
||||
params.ulInfoLen = aInfo->len;
|
||||
CK_MECHANISM_TYPE deriveMech = CKM_HKDF_DERIVE;
|
||||
CK_MECHANISM_TYPE keyMech = aKey ? CKM_AES_GCM : CKM_HKDF_DERIVE;
|
||||
|
||||
UniquePK11SymKey derivedKey(
|
||||
PK11_Derive(aPrk, deriveMech, ¶msItem, keyMech, CKA_DERIVE, aLen));
|
||||
if (!derivedKey) {
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
aOutKey.swap(derivedKey);
|
||||
return SECSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
* def decrypt_response_body(context, Q_plain, R_encrypted, response_nonce):
|
||||
* aead_key, aead_nonce = derive_secrets(context, Q_plain, response_nonce)
|
||||
* aad = 0x02 || len(response_nonce) || response_nonce
|
||||
* R_plain, error = Open(key, nonce, aad, R_encrypted)
|
||||
* return R_plain, error
|
||||
*/
|
||||
bool ODoHDNSPacket::DecryptDNSResponse() {
|
||||
ObliviousDoHMessage message;
|
||||
if (!CreateObliviousDoHMessage(mResponse, mBodySize, message)) {
|
||||
LOG(("ODoHDNSPacket::DecryptDNSResponse invalid response"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (message.mType != ODOH_RESPONSE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned int kResponseNonceLen = 16;
|
||||
// KeyId is actually response_nonce
|
||||
if (message.mKeyId.Length() != kResponseNonceLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// def derive_secrets(context, Q_plain, response_nonce):
|
||||
// secret = context.Export("odoh response", Nk)
|
||||
// salt = Q_plain || len(response_nonce) || response_nonce
|
||||
// prk = Extract(salt, secret)
|
||||
// key = Expand(odoh_prk, "odoh key", Nk)
|
||||
// nonce = Expand(odoh_prk, "odoh nonce", Nn)
|
||||
// return key, nonce
|
||||
const SECItem kODoHResponsetInfoItem = {
|
||||
siBuffer, (unsigned char*)kODoHResponse,
|
||||
static_cast<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*)(¶m);
|
||||
paramItem.len = sizeof(CK_GCM_PARAMS);
|
||||
|
||||
memset(mResponse, 0, mBodySize);
|
||||
rv = PK11_Decrypt(key.get(), CKM_AES_GCM, ¶mItem, mResponse, &mBodySize,
|
||||
MAX_SIZE, message.mEncryptedMessage.Elements(),
|
||||
message.mEncryptedMessage.Length());
|
||||
if (rv != SECSuccess) {
|
||||
LOG(("ODoHDNSPacket::DecryptDNSResponse decrypt failed %d",
|
||||
PORT_GetError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
112
netwerk/dns/ODoH.cpp
Normal 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
60
netwerk/dns/ODoH.h
Normal 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
521
netwerk/dns/ODoHService.cpp
Normal 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
80
netwerk/dns/ODoHService.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -78,6 +78,8 @@ UNIFIED_SOURCES += [
|
||||
"nsHostRecord.cpp",
|
||||
"nsHostResolver.cpp",
|
||||
"nsIDNService.cpp",
|
||||
"ODoH.cpp",
|
||||
"ODoHService.cpp",
|
||||
"punycode.c",
|
||||
"TRR.cpp",
|
||||
"TRRQuery.cpp",
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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 ==
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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().
|
||||
|
@ -128,6 +128,7 @@ parent:
|
||||
OriginAttributes aOriginAttributes,
|
||||
nsCString aRequestString)
|
||||
returns (bool aAccepted);
|
||||
async ODoHServiceActivated(bool aActivated);
|
||||
|
||||
async ExcludeHttp2OrHttp3(HttpConnectionInfoCloneArgs aArgs);
|
||||
|
||||
|
@ -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 =
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
295
netwerk/test/unit/test_odoh.js
Normal file
295
netwerk/test/unit/test_odoh.js
Normal 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");
|
||||
});
|
@ -8,6 +8,7 @@ SetParentalControlEnabled(false);
|
||||
|
||||
function setup() {
|
||||
h2Port = trr_test_setup();
|
||||
runningODoHTests = false;
|
||||
}
|
||||
|
||||
setup();
|
||||
|
@ -15,6 +15,7 @@ const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
||||
|
||||
function setup() {
|
||||
h2Port = trr_test_setup();
|
||||
runningODoHTests = false;
|
||||
}
|
||||
|
||||
let TRR_OK = 1;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -544,6 +544,7 @@ ARCHIVE_FILES = {
|
||||
"node_ip/**",
|
||||
"node-ws/**",
|
||||
"dns-packet/**",
|
||||
"odoh-wasm/**",
|
||||
"remotexpcshelltests.py",
|
||||
"runxpcshelltests.py",
|
||||
"selftest.py",
|
||||
|
@ -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) {
|
||||
|
42
testing/xpcshell/odoh-wasm/Cargo.toml
Normal file
42
testing/xpcshell/odoh-wasm/Cargo.toml
Normal 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"
|
176
testing/xpcshell/odoh-wasm/LICENSE_APACHE
Normal file
176
testing/xpcshell/odoh-wasm/LICENSE_APACHE
Normal 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
|
25
testing/xpcshell/odoh-wasm/LICENSE_MIT
Normal file
25
testing/xpcshell/odoh-wasm/LICENSE_MIT
Normal 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.
|
75
testing/xpcshell/odoh-wasm/README.md
Normal file
75
testing/xpcshell/odoh-wasm/README.md
Normal 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.
|
69
testing/xpcshell/odoh-wasm/pkg/README.md
Normal file
69
testing/xpcshell/odoh-wasm/pkg/README.md
Normal 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.
|
16
testing/xpcshell/odoh-wasm/pkg/odoh_wasm.d.ts
vendored
Normal file
16
testing/xpcshell/odoh-wasm/pkg/odoh_wasm.d.ts
vendored
Normal 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;
|
132
testing/xpcshell/odoh-wasm/pkg/odoh_wasm.js
Normal file
132
testing/xpcshell/odoh-wasm/pkg/odoh_wasm.js
Normal 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;
|
||||
|
BIN
testing/xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm
Normal file
BIN
testing/xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm
Normal file
Binary file not shown.
7
testing/xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm.d.ts
vendored
Normal file
7
testing/xpcshell/odoh-wasm/pkg/odoh_wasm_bg.wasm.d.ts
vendored
Normal 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;
|
15
testing/xpcshell/odoh-wasm/pkg/package.json
Normal file
15
testing/xpcshell/odoh-wasm/pkg/package.json
Normal 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"
|
||||
}
|
158
testing/xpcshell/odoh-wasm/src/lib.rs
Normal file
158
testing/xpcshell/odoh-wasm/src/lib.rs
Normal 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);
|
||||
}
|
@ -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"],
|
||||
|
@ -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/
|
||||
|
Loading…
Reference in New Issue
Block a user