1 /*
2 * Copyright (C) 2008 Collin Jackson <collinj@webkit.org>
3 * Copyright (C) 2009 Apple Inc. All Rights Reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include "config.h"
28 #include "DNS.h"
29
30 #include "KURL.h"
31 #include "Timer.h"
32 #include <wtf/HashSet.h>
33 #include <wtf/RetainPtr.h>
34 #include <wtf/StdLibExtras.h>
35 #include <wtf/text/StringHash.h>
36
37 #if PLATFORM(WIN)
38 #include "LoaderRunLoopCF.h"
39 #include <CFNetwork/CFNetwork.h>
40 #endif
41
42 #if defined(BUILDING_ON_LEOPARD)
43 #include <SystemConfiguration/SystemConfiguration.h>
44 #endif
45
46 #ifdef BUILDING_ON_TIGER
47 // This function is available on Tiger, but not declared in the CFRunLoop.h header on Tiger.
48 extern "C" CFRunLoopRef CFRunLoopGetMain();
49 #endif
50
51 namespace WebCore {
52
53 // When resolve queue is empty, we fire async resolution requests immediately (which is important if the prefetch is triggered by hovering).
54 // But during page parsing, we should coalesce identical requests to avoid stressing out CFHost.
55 const int namesToResolveImmediately = 4;
56
57 // Coalesce prefetch requests for this long before sending them out.
58 const double coalesceDelayInSeconds = 1.0;
59
60 // Sending many DNS requests at once can overwhelm some gateways. CFHost doesn't currently throttle for us, see <rdar://8105550>.
61 const int maxSimultaneousRequests = 8;
62
63 // For a page has links to many outside sites, it is likely that the system DNS resolver won't be able to cache them all anyway, and we don't want
64 // to negatively affect other applications' performance by pushing their cached entries out.
65 // If we end up with lots of names to prefetch, some will be dropped.
66 const int maxRequestsToQueue = 64;
67
68 // If there were queued names that couldn't be sent simultaneously, check the state of resolvers after this delay.
69 const double retryResolvingInSeconds = 0.1;
70
proxyIsEnabledInSystemPreferences()71 static bool proxyIsEnabledInSystemPreferences()
72 {
73 // Don't do DNS prefetch if proxies are involved. For many proxy types, the user agent is never exposed
74 // to the IP address during normal operation. Querying an internal DNS server may not help performance,
75 // as it doesn't necessarily look up the actual external IP. Also, if DNS returns a fake internal address,
76 // local caches may keep it even after re-connecting to another network.
77
78 #if !defined(BUILDING_ON_LEOPARD)
79 RetainPtr<CFDictionaryRef> proxySettings(AdoptCF, CFNetworkCopySystemProxySettings());
80 #else
81 RetainPtr<CFDictionaryRef> proxySettings(AdoptCF, SCDynamicStoreCopyProxies(0));
82 #endif
83 if (!proxySettings)
84 return false;
85
86 static CFURLRef httpCFURL = KURL(ParsedURLString, "http://example.com/").createCFURL();
87 static CFURLRef httpsCFURL = KURL(ParsedURLString, "https://example.com/").createCFURL();
88
89 RetainPtr<CFArrayRef> httpProxyArray(AdoptCF, CFNetworkCopyProxiesForURL(httpCFURL, proxySettings.get()));
90 RetainPtr<CFArrayRef> httpsProxyArray(AdoptCF, CFNetworkCopyProxiesForURL(httpsCFURL, proxySettings.get()));
91
92 CFIndex httpProxyCount = CFArrayGetCount(httpProxyArray.get());
93 CFIndex httpsProxyCount = CFArrayGetCount(httpsProxyArray.get());
94 if (httpProxyCount == 1 && CFEqual(CFDictionaryGetValue(static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(httpProxyArray.get(), 0)), kCFProxyTypeKey), kCFProxyTypeNone))
95 httpProxyCount = 0;
96 if (httpsProxyCount == 1 && CFEqual(CFDictionaryGetValue(static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(httpsProxyArray.get(), 0)), kCFProxyTypeKey), kCFProxyTypeNone))
97 httpsProxyCount = 0;
98
99 return httpProxyCount || httpsProxyCount;
100 }
101
102 class DNSResolveQueue : public TimerBase {
103 public:
104 static DNSResolveQueue& shared();
105 void add(const String&);
106 void decrementRequestCount();
107
108 private:
109 DNSResolveQueue();
110
111 void resolve(const String&);
112 virtual void fired();
113 HashSet<String> m_names;
114 int m_requestsInFlight;
115 };
116
DNSResolveQueue()117 DNSResolveQueue::DNSResolveQueue()
118 : m_requestsInFlight(0)
119 {
120 }
121
shared()122 DNSResolveQueue& DNSResolveQueue::shared()
123 {
124 DEFINE_STATIC_LOCAL(DNSResolveQueue, names, ());
125 return names;
126 }
127
add(const String & name)128 void DNSResolveQueue::add(const String& name)
129 {
130 // If there are no names queued, and few enough are in flight, resolve immediately (the mouse may be over a link).
131 if (!m_names.size()) {
132 if (proxyIsEnabledInSystemPreferences())
133 return;
134
135 if (atomicIncrement(&m_requestsInFlight) <= namesToResolveImmediately) {
136 resolve(name);
137 return;
138 }
139 atomicDecrement(&m_requestsInFlight);
140 }
141
142 // It's better to not prefetch some names than to clog the queue.
143 // Dropping the newest names, because on a single page, these are likely to be below oldest ones.
144 if (m_names.size() < maxRequestsToQueue) {
145 m_names.add(name);
146 if (!isActive())
147 startOneShot(coalesceDelayInSeconds);
148 }
149 }
150
decrementRequestCount()151 void DNSResolveQueue::decrementRequestCount()
152 {
153 atomicDecrement(&m_requestsInFlight);
154 }
155
fired()156 void DNSResolveQueue::fired()
157 {
158 if (proxyIsEnabledInSystemPreferences()) {
159 m_names.clear();
160 return;
161 }
162
163 int requestsAllowed = maxSimultaneousRequests - m_requestsInFlight;
164
165 for (; !m_names.isEmpty() && requestsAllowed > 0; --requestsAllowed) {
166 atomicIncrement(&m_requestsInFlight);
167 HashSet<String>::iterator currentName = m_names.begin();
168 resolve(*currentName);
169 m_names.remove(currentName);
170 }
171
172 if (!m_names.isEmpty())
173 startOneShot(retryResolvingInSeconds);
174 }
175
clientCallback(CFHostRef theHost,CFHostInfoType,const CFStreamError *,void *)176 static void clientCallback(CFHostRef theHost, CFHostInfoType, const CFStreamError*, void*)
177 {
178 DNSResolveQueue::shared().decrementRequestCount(); // It's ok to call shared() from a secondary thread, the static variable has already been initialized by now.
179 CFRelease(theHost);
180 }
181
resolve(const String & hostname)182 void DNSResolveQueue::resolve(const String& hostname)
183 {
184 ASSERT(isMainThread());
185
186 RetainPtr<CFStringRef> hostnameCF(AdoptCF, hostname.createCFString());
187 RetainPtr<CFHostRef> host(AdoptCF, CFHostCreateWithName(0, hostnameCF.get()));
188 if (!host) {
189 atomicDecrement(&m_requestsInFlight);
190 return;
191 }
192 CFHostClientContext context = { 0, 0, 0, 0, 0 };
193 Boolean result = CFHostSetClient(host.get(), clientCallback, &context);
194 ASSERT_UNUSED(result, result);
195 #if !PLATFORM(WIN)
196 CFHostScheduleWithRunLoop(host.get(), CFRunLoopGetMain(), kCFRunLoopCommonModes);
197 #else
198 // On Windows, we run a separate thread with CFRunLoop, which is where clientCallback will be called.
199 CFHostScheduleWithRunLoop(host.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
200 #endif
201 CFHostStartInfoResolution(host.get(), kCFHostAddresses, 0);
202 host.releaseRef(); // The host will be released from clientCallback().
203 }
204
prefetchDNS(const String & hostname)205 void prefetchDNS(const String& hostname)
206 {
207 ASSERT(isMainThread());
208 if (hostname.isEmpty())
209 return;
210 DNSResolveQueue::shared().add(hostname);
211 }
212
213 }
214