/* 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 "mozilla/net/ChildDNSService.h" #include "nsIDNSListener.h" #include "nsIIOService.h" #include "nsIThread.h" #include "nsThreadUtils.h" #include "nsIXPConnect.h" #include "nsIPrefService.h" #include "nsIProtocolProxyService.h" #include "nsNetCID.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/StaticPtr.h" #include "mozilla/SystemGroup.h" #include "mozilla/net/NeckoChild.h" #include "mozilla/net/DNSListenerProxy.h" #include "nsServiceManagerUtils.h" namespace mozilla { namespace net { //----------------------------------------------------------------------------- // ChildDNSService //----------------------------------------------------------------------------- static StaticRefPtr gChildDNSService; static const char kPrefNameDisablePrefetch[] = "network.dns.disablePrefetch"; already_AddRefed ChildDNSService::GetSingleton() { MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsSocketProcess()); if (!gChildDNSService) { gChildDNSService = new ChildDNSService(); ClearOnShutdown(&gChildDNSService); } return do_AddRef(gChildDNSService); } NS_IMPL_ISUPPORTS(ChildDNSService, nsIDNSService, nsPIDNSService, nsIObserver) ChildDNSService::ChildDNSService() : mFirstTime(true), mDisablePrefetch(false), mPendingRequestsLock("DNSPendingRequestsLock") { MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsSocketProcess()); } void ChildDNSService::GetDNSRecordHashKey( const nsACString& aHost, uint16_t aType, const OriginAttributes& aOriginAttributes, uint32_t aFlags, nsIDNSListener* aListener, nsACString& aHashKey) { aHashKey.Assign(aHost); aHashKey.AppendInt(aType); nsAutoCString originSuffix; aOriginAttributes.CreateSuffix(originSuffix); aHashKey.Append(originSuffix); aHashKey.AppendInt(aFlags); aHashKey.AppendPrintf("%p", aListener); } nsresult ChildDNSService::AsyncResolveInternal( const nsACString& hostname, uint16_t type, uint32_t flags, nsIDNSListener* listener, nsIEventTarget* target_, const OriginAttributes& aOriginAttributes, nsICancelable** result) { if (XRE_IsContentProcess()) { NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE); } if (mDisablePrefetch && (flags & RESOLVE_SPECULATE)) { return NS_ERROR_DNS_LOOKUP_QUEUE_FULL; } // We need original listener for the pending requests hash. nsIDNSListener* originalListener = listener; // make sure JS callers get notification on the main thread nsCOMPtr target = target_; nsCOMPtr wrappedListener = do_QueryInterface(listener); if (wrappedListener && !target) { target = SystemGroup::EventTargetFor(TaskCategory::Network); } if (target) { // Guarantee listener freed on main thread. Not sure we need this in child // (or in parent in nsDNSService.cpp) but doesn't hurt. listener = new DNSListenerProxy(listener, target); } RefPtr childReq = new DNSRequestChild( hostname, type, aOriginAttributes, flags, listener, target); { MutexAutoLock lock(mPendingRequestsLock); nsCString key; GetDNSRecordHashKey(hostname, type, aOriginAttributes, flags, originalListener, key); auto entry = mPendingRequests.LookupForAdd(key); if (entry) { entry.Data()->AppendElement(childReq); } else { entry.OrInsert([&]() { auto* hashEntry = new nsTArray>(); hashEntry->AppendElement(childReq); return hashEntry; }); } } childReq->StartRequest(); childReq.forget(result); return NS_OK; } nsresult ChildDNSService::CancelAsyncResolveInternal( const nsACString& aHostname, uint16_t aType, uint32_t aFlags, nsIDNSListener* aListener, nsresult aReason, const OriginAttributes& aOriginAttributes) { if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE)) { return NS_ERROR_DNS_LOOKUP_QUEUE_FULL; } MutexAutoLock lock(mPendingRequestsLock); nsTArray>* hashEntry; nsCString key; GetDNSRecordHashKey(aHostname, aType, aOriginAttributes, aFlags, aListener, key); if (mPendingRequests.Get(key, &hashEntry)) { // We cancel just one. hashEntry->ElementAt(0)->Cancel(aReason); } return NS_OK; } //----------------------------------------------------------------------------- // ChildDNSService::nsIDNSService //----------------------------------------------------------------------------- NS_IMETHODIMP ChildDNSService::AsyncResolve(const nsACString& hostname, uint32_t flags, nsIDNSListener* listener, nsIEventTarget* target_, JS::HandleValue aOriginAttributes, JSContext* aCx, uint8_t aArgc, nsICancelable** result) { OriginAttributes attrs; if (aArgc == 1) { if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } } return AsyncResolveInternal(hostname, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags, listener, target_, attrs, result); } NS_IMETHODIMP ChildDNSService::AsyncResolveNative(const nsACString& hostname, uint32_t flags, nsIDNSListener* listener, nsIEventTarget* target_, const OriginAttributes& aOriginAttributes, nsICancelable** result) { return AsyncResolveInternal(hostname, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags, listener, target_, aOriginAttributes, result); } NS_IMETHODIMP ChildDNSService::AsyncResolveByType(const nsACString& hostname, uint16_t type, uint32_t flags, nsIDNSListener* listener, nsIEventTarget* target_, JS::HandleValue aOriginAttributes, JSContext* aCx, uint8_t aArgc, nsICancelable** result) { OriginAttributes attrs; if (aArgc == 1) { if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } } return AsyncResolveInternal(hostname, type, flags, listener, target_, attrs, result); } NS_IMETHODIMP ChildDNSService::AsyncResolveByTypeNative( const nsACString& hostname, uint16_t type, uint32_t flags, nsIDNSListener* listener, nsIEventTarget* target_, const OriginAttributes& aOriginAttributes, nsICancelable** result) { return AsyncResolveInternal(hostname, type, flags, listener, target_, aOriginAttributes, result); } NS_IMETHODIMP ChildDNSService::CancelAsyncResolve(const nsACString& aHostname, uint32_t aFlags, nsIDNSListener* aListener, nsresult aReason, JS::HandleValue aOriginAttributes, JSContext* aCx, uint8_t aArgc) { OriginAttributes attrs; if (aArgc == 1) { if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } } return CancelAsyncResolveInternal(aHostname, nsIDNSService::RESOLVE_TYPE_DEFAULT, aFlags, aListener, aReason, attrs); } NS_IMETHODIMP ChildDNSService::CancelAsyncResolveNative( const nsACString& aHostname, uint32_t aFlags, nsIDNSListener* aListener, nsresult aReason, const OriginAttributes& aOriginAttributes) { return CancelAsyncResolveInternal(aHostname, nsIDNSService::RESOLVE_TYPE_DEFAULT, aFlags, aListener, aReason, aOriginAttributes); } NS_IMETHODIMP ChildDNSService::CancelAsyncResolveByType(const nsACString& aHostname, uint16_t aType, uint32_t aFlags, nsIDNSListener* aListener, nsresult aReason, JS::HandleValue aOriginAttributes, JSContext* aCx, uint8_t aArgc) { OriginAttributes attrs; if (aArgc == 1) { if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } } return CancelAsyncResolveInternal(aHostname, aType, aFlags, aListener, aReason, attrs); } NS_IMETHODIMP ChildDNSService::CancelAsyncResolveByTypeNative( const nsACString& aHostname, uint16_t aType, uint32_t aFlags, nsIDNSListener* aListener, nsresult aReason, const OriginAttributes& aOriginAttributes) { return CancelAsyncResolveInternal(aHostname, aType, aFlags, aListener, aReason, aOriginAttributes); } NS_IMETHODIMP ChildDNSService::Resolve(const nsACString& hostname, uint32_t flags, JS::HandleValue aOriginAttributes, JSContext* aCx, uint8_t aArgc, nsIDNSRecord** result) { // not planning to ever support this, since sync IPDL is evil. return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP ChildDNSService::ResolveNative(const nsACString& hostname, uint32_t flags, const OriginAttributes& aOriginAttributes, nsIDNSRecord** result) { // not planning to ever support this, since sync IPDL is evil. return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP ChildDNSService::GetDNSCacheEntries( nsTArray* args) { // Only used by networking dashboard, so may not ever need this in child. // (and would provide a way to spy on what hosts other apps are connecting to, // unless we start keeping per-app DNS caches). return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP ChildDNSService::ClearCache(bool aTrrToo) { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP ChildDNSService::ReloadParentalControlEnabled() { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP ChildDNSService::GetMyHostName(nsACString& result) { // TODO: get value from parent during PNecko construction? return NS_ERROR_NOT_AVAILABLE; } void ChildDNSService::NotifyRequestDone(DNSRequestChild* aDnsRequest) { // We need the original flags and listener for the pending requests hash. uint32_t originalFlags = aDnsRequest->mFlags & ~RESOLVE_OFFLINE; nsCOMPtr originalListener = aDnsRequest->mListener; nsCOMPtr wrapper = do_QueryInterface(originalListener); if (wrapper) { wrapper->GetOriginalListener(getter_AddRefs(originalListener)); if (NS_WARN_IF(!originalListener)) { MOZ_ASSERT(originalListener); return; } } MutexAutoLock lock(mPendingRequestsLock); nsCString key; GetDNSRecordHashKey(aDnsRequest->mHost, aDnsRequest->mType, aDnsRequest->mOriginAttributes, originalFlags, originalListener, key); nsTArray>* hashEntry; if (mPendingRequests.Get(key, &hashEntry)) { auto idx = hashEntry->IndexOf(aDnsRequest); if (idx != nsTArray>::NoIndex) { hashEntry->RemoveElementAt(idx); if (hashEntry->IsEmpty()) { mPendingRequests.Remove(key); } } } } //----------------------------------------------------------------------------- // ChildDNSService::nsPIDNSService //----------------------------------------------------------------------------- nsresult ChildDNSService::Init() { // Disable prefetching either by explicit preference or if a manual proxy // is configured bool disablePrefetch = false; int proxyType = nsIProtocolProxyService::PROXYCONFIG_DIRECT; nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (prefs) { prefs->GetIntPref("network.proxy.type", &proxyType); prefs->GetBoolPref(kPrefNameDisablePrefetch, &disablePrefetch); } if (mFirstTime) { mFirstTime = false; if (prefs) { prefs->AddObserver(kPrefNameDisablePrefetch, this, false); // Monitor these to see if there is a change in proxy configuration // If a manual proxy is in use, disable prefetch implicitly prefs->AddObserver("network.proxy.type", this, false); } } mDisablePrefetch = disablePrefetch || (proxyType == nsIProtocolProxyService::PROXYCONFIG_MANUAL); return NS_OK; } nsresult ChildDNSService::Shutdown() { return NS_OK; } NS_IMETHODIMP ChildDNSService::GetPrefetchEnabled(bool* outVal) { *outVal = !mDisablePrefetch; return NS_OK; } NS_IMETHODIMP ChildDNSService::SetPrefetchEnabled(bool inVal) { mDisablePrefetch = !inVal; return NS_OK; } //----------------------------------------------------------------------------- // ChildDNSService::nsIObserver //----------------------------------------------------------------------------- NS_IMETHODIMP ChildDNSService::Observe(nsISupports* subject, const char* topic, const char16_t* data) { // we are only getting called if a preference has changed. NS_ASSERTION(strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0, "unexpected observe call"); // Reread prefs Init(); return NS_OK; } } // namespace net } // namespace mozilla