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