• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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