1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "mozilla/net/ChildDNSService.h"
6 #include "nsIDNSListener.h"
7 #include "nsIIOService.h"
8 #include "nsIThread.h"
9 #include "nsThreadUtils.h"
10 #include "nsIXPConnect.h"
11 #include "nsIPrefService.h"
12 #include "nsIProtocolProxyService.h"
13 #include "nsNetCID.h"
14 #include "mozilla/ClearOnShutdown.h"
15 #include "mozilla/StaticPtr.h"
16 #include "mozilla/SystemGroup.h"
17 #include "mozilla/net/NeckoChild.h"
18 #include "mozilla/net/DNSListenerProxy.h"
19 #include "nsServiceManagerUtils.h"
20 
21 namespace mozilla {
22 namespace net {
23 
24 //-----------------------------------------------------------------------------
25 // ChildDNSService
26 //-----------------------------------------------------------------------------
27 
28 static StaticRefPtr<ChildDNSService> gChildDNSService;
29 static const char kPrefNameDisablePrefetch[] = "network.dns.disablePrefetch";
30 
GetSingleton()31 already_AddRefed<ChildDNSService> ChildDNSService::GetSingleton() {
32   MOZ_ASSERT(IsNeckoChild());
33 
34   if (!gChildDNSService) {
35     gChildDNSService = new ChildDNSService();
36     ClearOnShutdown(&gChildDNSService);
37   }
38 
39   return do_AddRef(gChildDNSService);
40 }
41 
NS_IMPL_ISUPPORTS(ChildDNSService,nsIDNSService,nsPIDNSService,nsIObserver)42 NS_IMPL_ISUPPORTS(ChildDNSService, nsIDNSService, nsPIDNSService, nsIObserver)
43 
44 ChildDNSService::ChildDNSService()
45     : mFirstTime(true),
46       mDisablePrefetch(false),
47       mPendingRequestsLock("DNSPendingRequestsLock") {
48   MOZ_ASSERT(IsNeckoChild());
49 }
50 
~ChildDNSService()51 ChildDNSService::~ChildDNSService() {}
52 
GetDNSRecordHashKey(const nsACString & aHost,const OriginAttributes & aOriginAttributes,uint32_t aFlags,const nsACString & aNetworkInterface,nsIDNSListener * aListener,nsACString & aHashKey)53 void ChildDNSService::GetDNSRecordHashKey(
54     const nsACString &aHost, const OriginAttributes &aOriginAttributes,
55     uint32_t aFlags, const nsACString &aNetworkInterface,
56     nsIDNSListener *aListener, nsACString &aHashKey) {
57   aHashKey.Assign(aHost);
58 
59   nsAutoCString originSuffix;
60   aOriginAttributes.CreateSuffix(originSuffix);
61   aHashKey.Assign(originSuffix);
62 
63   aHashKey.AppendInt(aFlags);
64   if (!aNetworkInterface.IsEmpty()) {
65     aHashKey.Append(aNetworkInterface);
66   }
67   aHashKey.AppendPrintf("%p", aListener);
68 }
69 
70 //-----------------------------------------------------------------------------
71 // ChildDNSService::nsIDNSService
72 //-----------------------------------------------------------------------------
73 
74 NS_IMETHODIMP
AsyncResolve(const nsACString & hostname,uint32_t flags,nsIDNSListener * listener,nsIEventTarget * target_,JS::HandleValue aOriginAttributes,JSContext * aCx,uint8_t aArgc,nsICancelable ** result)75 ChildDNSService::AsyncResolve(const nsACString &hostname, uint32_t flags,
76                               nsIDNSListener *listener, nsIEventTarget *target_,
77                               JS::HandleValue aOriginAttributes, JSContext *aCx,
78                               uint8_t aArgc, nsICancelable **result) {
79   OriginAttributes attrs;
80 
81   if (aArgc == 1) {
82     if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
83       return NS_ERROR_INVALID_ARG;
84     }
85   }
86 
87   return AsyncResolveExtendedNative(hostname, flags, EmptyCString(), listener,
88                                     target_, attrs, result);
89 }
90 
91 NS_IMETHODIMP
AsyncResolveNative(const nsACString & hostname,uint32_t flags,nsIDNSListener * listener,nsIEventTarget * target_,const OriginAttributes & aOriginAttributes,nsICancelable ** result)92 ChildDNSService::AsyncResolveNative(const nsACString &hostname, uint32_t flags,
93                                     nsIDNSListener *listener,
94                                     nsIEventTarget *target_,
95                                     const OriginAttributes &aOriginAttributes,
96                                     nsICancelable **result) {
97   return AsyncResolveExtendedNative(hostname, flags, EmptyCString(), listener,
98                                     target_, aOriginAttributes, result);
99 }
100 
101 NS_IMETHODIMP
AsyncResolveExtended(const nsACString & aHostname,uint32_t flags,const nsACString & aNetworkInterface,nsIDNSListener * listener,nsIEventTarget * target_,JS::HandleValue aOriginAttributes,JSContext * aCx,uint8_t aArgc,nsICancelable ** result)102 ChildDNSService::AsyncResolveExtended(
103     const nsACString &aHostname, uint32_t flags,
104     const nsACString &aNetworkInterface, nsIDNSListener *listener,
105     nsIEventTarget *target_, JS::HandleValue aOriginAttributes, JSContext *aCx,
106     uint8_t aArgc, nsICancelable **result) {
107   OriginAttributes attrs;
108 
109   if (aArgc == 1) {
110     if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
111       return NS_ERROR_INVALID_ARG;
112     }
113   }
114 
115   return AsyncResolveExtendedNative(aHostname, flags, aNetworkInterface,
116                                     listener, target_, attrs, result);
117 }
118 
119 NS_IMETHODIMP
AsyncResolveExtendedNative(const nsACString & hostname,uint32_t flags,const nsACString & aNetworkInterface,nsIDNSListener * listener,nsIEventTarget * target_,const OriginAttributes & aOriginAttributes,nsICancelable ** result)120 ChildDNSService::AsyncResolveExtendedNative(
121     const nsACString &hostname, uint32_t flags,
122     const nsACString &aNetworkInterface, nsIDNSListener *listener,
123     nsIEventTarget *target_, const OriginAttributes &aOriginAttributes,
124     nsICancelable **result) {
125   NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
126 
127   if (mDisablePrefetch && (flags & RESOLVE_SPECULATE)) {
128     return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
129   }
130 
131   // We need original flags for the pending requests hash.
132   uint32_t originalFlags = flags;
133 
134   // Support apps being 'offline' even if parent is not: avoids DNS traffic by
135   // apps that have been told they are offline.
136   if (GetOffline()) {
137     flags |= RESOLVE_OFFLINE;
138   }
139 
140   // We need original listener for the pending requests hash.
141   nsIDNSListener *originalListener = listener;
142 
143   // make sure JS callers get notification on the main thread
144   nsCOMPtr<nsIEventTarget> target = target_;
145   nsCOMPtr<nsIXPConnectWrappedJS> wrappedListener = do_QueryInterface(listener);
146   if (wrappedListener && !target) {
147     target = SystemGroup::EventTargetFor(TaskCategory::Network);
148   }
149   if (target) {
150     // Guarantee listener freed on main thread.  Not sure we need this in child
151     // (or in parent in nsDNSService.cpp) but doesn't hurt.
152     listener = new DNSListenerProxy(listener, target);
153   }
154 
155   RefPtr<DNSRequestChild> childReq = new DNSRequestChild(
156       hostname, aOriginAttributes, flags, aNetworkInterface, listener, target);
157 
158   {
159     MutexAutoLock lock(mPendingRequestsLock);
160     nsCString key;
161     GetDNSRecordHashKey(hostname, aOriginAttributes, originalFlags,
162                         aNetworkInterface, originalListener, key);
163     nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
164     if (mPendingRequests.Get(key, &hashEntry)) {
165       hashEntry->AppendElement(childReq);
166     } else {
167       hashEntry = new nsTArray<RefPtr<DNSRequestChild>>();
168       hashEntry->AppendElement(childReq);
169       mPendingRequests.Put(key, hashEntry);
170     }
171   }
172 
173   childReq->StartRequest();
174 
175   childReq.forget(result);
176   return NS_OK;
177 }
178 
179 NS_IMETHODIMP
CancelAsyncResolve(const nsACString & aHostname,uint32_t aFlags,nsIDNSListener * aListener,nsresult aReason,JS::HandleValue aOriginAttributes,JSContext * aCx,uint8_t aArgc)180 ChildDNSService::CancelAsyncResolve(const nsACString &aHostname,
181                                     uint32_t aFlags, nsIDNSListener *aListener,
182                                     nsresult aReason,
183                                     JS::HandleValue aOriginAttributes,
184                                     JSContext *aCx, uint8_t aArgc) {
185   OriginAttributes attrs;
186 
187   if (aArgc == 1) {
188     if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
189       return NS_ERROR_INVALID_ARG;
190     }
191   }
192 
193   return CancelAsyncResolveExtendedNative(aHostname, aFlags, EmptyCString(),
194                                           aListener, aReason, attrs);
195 }
196 
197 NS_IMETHODIMP
CancelAsyncResolveNative(const nsACString & aHostname,uint32_t aFlags,nsIDNSListener * aListener,nsresult aReason,const OriginAttributes & aOriginAttributes)198 ChildDNSService::CancelAsyncResolveNative(
199     const nsACString &aHostname, uint32_t aFlags, nsIDNSListener *aListener,
200     nsresult aReason, const OriginAttributes &aOriginAttributes) {
201   return CancelAsyncResolveExtendedNative(
202       aHostname, aFlags, EmptyCString(), aListener, aReason, aOriginAttributes);
203 }
204 
205 NS_IMETHODIMP
CancelAsyncResolveExtended(const nsACString & aHostname,uint32_t aFlags,const nsACString & aNetworkInterface,nsIDNSListener * aListener,nsresult aReason,JS::HandleValue aOriginAttributes,JSContext * aCx,uint8_t aArgc)206 ChildDNSService::CancelAsyncResolveExtended(const nsACString &aHostname,
207                                             uint32_t aFlags,
208                                             const nsACString &aNetworkInterface,
209                                             nsIDNSListener *aListener,
210                                             nsresult aReason,
211                                             JS::HandleValue aOriginAttributes,
212                                             JSContext *aCx, uint8_t aArgc) {
213   OriginAttributes attrs;
214 
215   if (aArgc == 1) {
216     if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
217       return NS_ERROR_INVALID_ARG;
218     }
219   }
220 
221   return CancelAsyncResolveExtendedNative(aHostname, aFlags, aNetworkInterface,
222                                           aListener, aReason, attrs);
223 }
224 
225 NS_IMETHODIMP
CancelAsyncResolveExtendedNative(const nsACString & aHostname,uint32_t aFlags,const nsACString & aNetworkInterface,nsIDNSListener * aListener,nsresult aReason,const OriginAttributes & aOriginAttributes)226 ChildDNSService::CancelAsyncResolveExtendedNative(
227     const nsACString &aHostname, uint32_t aFlags,
228     const nsACString &aNetworkInterface, nsIDNSListener *aListener,
229     nsresult aReason, const OriginAttributes &aOriginAttributes) {
230   if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE)) {
231     return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
232   }
233 
234   MutexAutoLock lock(mPendingRequestsLock);
235   nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
236   nsCString key;
237   GetDNSRecordHashKey(aHostname, aOriginAttributes, aFlags, aNetworkInterface,
238                       aListener, key);
239   if (mPendingRequests.Get(key, &hashEntry)) {
240     // We cancel just one.
241     hashEntry->ElementAt(0)->Cancel(aReason);
242   }
243 
244   return NS_OK;
245 }
246 
247 NS_IMETHODIMP
Resolve(const nsACString & hostname,uint32_t flags,JS::HandleValue aOriginAttributes,JSContext * aCx,uint8_t aArgc,nsIDNSRecord ** result)248 ChildDNSService::Resolve(const nsACString &hostname, uint32_t flags,
249                          JS::HandleValue aOriginAttributes, JSContext *aCx,
250                          uint8_t aArgc, nsIDNSRecord **result) {
251   // not planning to ever support this, since sync IPDL is evil.
252   return NS_ERROR_NOT_AVAILABLE;
253 }
254 
255 NS_IMETHODIMP
ResolveNative(const nsACString & hostname,uint32_t flags,const OriginAttributes & aOriginAttributes,nsIDNSRecord ** result)256 ChildDNSService::ResolveNative(const nsACString &hostname, uint32_t flags,
257                                const OriginAttributes &aOriginAttributes,
258                                nsIDNSRecord **result) {
259   // not planning to ever support this, since sync IPDL is evil.
260   return NS_ERROR_NOT_AVAILABLE;
261 }
262 
263 NS_IMETHODIMP
GetDNSCacheEntries(nsTArray<mozilla::net::DNSCacheEntries> * args)264 ChildDNSService::GetDNSCacheEntries(
265     nsTArray<mozilla::net::DNSCacheEntries> *args) {
266   // Only used by networking dashboard, so may not ever need this in child.
267   // (and would provide a way to spy on what hosts other apps are connecting to,
268   // unless we start keeping per-app DNS caches).
269   return NS_ERROR_NOT_AVAILABLE;
270 }
271 
272 NS_IMETHODIMP
GetMyHostName(nsACString & result)273 ChildDNSService::GetMyHostName(nsACString &result) {
274   // TODO: get value from parent during PNecko construction?
275   return NS_ERROR_NOT_AVAILABLE;
276 }
277 
NotifyRequestDone(DNSRequestChild * aDnsRequest)278 void ChildDNSService::NotifyRequestDone(DNSRequestChild *aDnsRequest) {
279   // We need the original flags and listener for the pending requests hash.
280   uint32_t originalFlags = aDnsRequest->mFlags & ~RESOLVE_OFFLINE;
281   nsCOMPtr<nsIDNSListener> originalListener = aDnsRequest->mListener;
282   nsCOMPtr<nsIDNSListenerProxy> wrapper = do_QueryInterface(originalListener);
283   if (wrapper) {
284     wrapper->GetOriginalListener(getter_AddRefs(originalListener));
285     if (NS_WARN_IF(!originalListener)) {
286       MOZ_ASSERT(originalListener);
287       return;
288     }
289   }
290 
291   MutexAutoLock lock(mPendingRequestsLock);
292 
293   nsCString key;
294   GetDNSRecordHashKey(aDnsRequest->mHost, aDnsRequest->mOriginAttributes,
295                       originalFlags, aDnsRequest->mNetworkInterface,
296                       originalListener, key);
297 
298   nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
299 
300   if (mPendingRequests.Get(key, &hashEntry)) {
301     int idx;
302     if ((idx = hashEntry->IndexOf(aDnsRequest))) {
303       hashEntry->RemoveElementAt(idx);
304       if (hashEntry->IsEmpty()) {
305         mPendingRequests.Remove(key);
306       }
307     }
308   }
309 }
310 
311 //-----------------------------------------------------------------------------
312 // ChildDNSService::nsPIDNSService
313 //-----------------------------------------------------------------------------
314 
Init()315 nsresult ChildDNSService::Init() {
316   // Disable prefetching either by explicit preference or if a manual proxy
317   // is configured
318   bool disablePrefetch = false;
319   int proxyType = nsIProtocolProxyService::PROXYCONFIG_DIRECT;
320 
321   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
322   if (prefs) {
323     prefs->GetIntPref("network.proxy.type", &proxyType);
324     prefs->GetBoolPref(kPrefNameDisablePrefetch, &disablePrefetch);
325   }
326 
327   if (mFirstTime) {
328     mFirstTime = false;
329     if (prefs) {
330       prefs->AddObserver(kPrefNameDisablePrefetch, this, false);
331 
332       // Monitor these to see if there is a change in proxy configuration
333       // If a manual proxy is in use, disable prefetch implicitly
334       prefs->AddObserver("network.proxy.type", this, false);
335     }
336   }
337 
338   mDisablePrefetch = disablePrefetch ||
339                      (proxyType == nsIProtocolProxyService::PROXYCONFIG_MANUAL);
340 
341   return NS_OK;
342 }
343 
Shutdown()344 nsresult ChildDNSService::Shutdown() { return NS_OK; }
345 
346 NS_IMETHODIMP
GetPrefetchEnabled(bool * outVal)347 ChildDNSService::GetPrefetchEnabled(bool *outVal) {
348   *outVal = !mDisablePrefetch;
349   return NS_OK;
350 }
351 
352 NS_IMETHODIMP
SetPrefetchEnabled(bool inVal)353 ChildDNSService::SetPrefetchEnabled(bool inVal) {
354   mDisablePrefetch = !inVal;
355   return NS_OK;
356 }
357 
GetOffline() const358 bool ChildDNSService::GetOffline() const {
359   bool offline = false;
360   nsCOMPtr<nsIIOService> io = do_GetService(NS_IOSERVICE_CONTRACTID);
361   if (io) {
362     io->GetOffline(&offline);
363   }
364   return offline;
365 }
366 
367 //-----------------------------------------------------------------------------
368 // ChildDNSService::nsIObserver
369 //-----------------------------------------------------------------------------
370 
371 NS_IMETHODIMP
Observe(nsISupports * subject,const char * topic,const char16_t * data)372 ChildDNSService::Observe(nsISupports *subject, const char *topic,
373                          const char16_t *data) {
374   // we are only getting called if a preference has changed.
375   NS_ASSERTION(strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0,
376                "unexpected observe call");
377 
378   // Reread prefs
379   Init();
380   return NS_OK;
381 }
382 
383 }  // namespace net
384 }  // namespace mozilla
385