Home | History | Annotate | Download | only in cf
      1 /*
      2  * Copyright (C) 2008 Collin Jackson  <collinj (at) 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 
     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 
    117 DNSResolveQueue::DNSResolveQueue()
    118     : m_requestsInFlight(0)
    119 {
    120 }
    121 
    122 DNSResolveQueue& DNSResolveQueue::shared()
    123 {
    124     DEFINE_STATIC_LOCAL(DNSResolveQueue, names, ());
    125     return names;
    126 }
    127 
    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 
    151 void DNSResolveQueue::decrementRequestCount()
    152 {
    153     atomicDecrement(&m_requestsInFlight);
    154 }
    155 
    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 
    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 
    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 
    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