packages feed

hsdns-cache (empty) → 1.0.0

raw patch · 4 files changed

+263/−0 lines, 4 filesdep +SafeSemaphoredep +basedep +hsdnssetup-changed

Dependencies added: SafeSemaphore, base, hsdns, network, text, time, unordered-containers

Files

+ ADNS/Cache.hs view
@@ -0,0 +1,188 @@+{-# LANGUAGE RecordWildCards, ViewPatterns, OverloadedStrings,+             TupleSections, PatternGuards #-}+{-|+  Caching asynchronous DNS resolver built on top of+  GNU ADNS <http://www.chiark.greenend.org.uk/~ian/adns/>.++   * Resolves several IP addresses for one host (if available)+     in round-robin fashion.++   * Limits number of parallel requests (so DNS resolving continues to work+     even under heavy load).++   * Errors are cached too (for one minute).++   * Handles CNAMEs (@hsdns@ returns error for them).++  You should link your program with the /threaded/ runtime-system+  when using this module. In GHC, this is accomplished by specifying @-threaded@+  on the command-line.++  This cache is tested in a long running web-crawler+  (used in <http://bazqux.com>) so it should be safe to use it in real world+  applications.+-}+module ADNS.Cache+    ( -- * DNS cache+      DnsCache+    , withDnsCache+    , stopDnsCache++      -- * DNS lookup+    , resolveA++      -- * Utils+    , showHostAddress+    ) where++import ADNS+import ADNS.Base+import ADNS.Endian+import Data.List+import Data.Word+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T+import Control.Monad+import Data.Bits+import Control.Concurrent+import qualified Control.Concurrent.MSem as MSem+import Data.Time.Clock.POSIX++-- | Asynchronous DNS cache.+data DnsCache+    = DnsCache+      { resolver :: Resolver+      , sem      :: MSem.MSem Int+      , cache    :: MVar (HM.HashMap T.Text (POSIXTime,+                                             Either T.Text (Queue HostAddress)))+      }++data Queue a = Queue ![a] ![a]+    deriving Show++listQ :: [a] -> Queue a+listQ l = Queue l []++rotQ :: Queue a -> Maybe (a, Queue a)+rotQ (Queue [] []) = Nothing+rotQ (Queue (x:xs) ys) = Just (x, Queue xs (x:ys))+rotQ (Queue [] ys) = rotQ (Queue (reverse ys) [])++-- not more than 30 simultaneous resolveA calls+maxQueries :: Int+maxQueries = 30++-- | Create cache and run action passed.+withDnsCache :: (DnsCache -> IO a) -> IO a+withDnsCache act =+    initResolver [NoErrPrint, NoServerWarn, NoSigPipe] $ \ r -> do+        --        ^ there was sigsegv in adns__lprintf when exiting using Ctrl+C+        --          so the warnings printing is suppressed+        c <- newMVar HM.empty+        s <- MSem.new maxQueries+        act (DnsCache r s c)++-- | Wait till all running resolvers are finished and block further resolvers.+stopDnsCache :: DnsCache -> IO ()+stopDnsCache d =+    replicateM_ maxQueries $ MSem.wait (sem d)++-- | Resolve A DNS record.+resolveA :: DnsCache -> HostName -> IO (Either String HostAddress)+resolveA d@(DnsCache {..}) domain+    | isIPAddr domain = return $ Right $ ipToWord32 domain+    | otherwise = do+    t <- getPOSIXTime+    mbr <- modifyMVar cache $ \ c ->+        case HM.lookup key c of+             Just (expT, a)+                 | t < expT -> fmap (\(m,r) -> (m, Just r)) $+                               insRot False c expT a+             _ -> return (c, Nothing)+    case mbr of+        Just r -> return r+        _ -> do+            ra <- MSem.with sem $ resolveA' d [] domain+            modifyMVar cache $ \ m ->+                case ra of+                    Left e -> insRot True m (t+60) $ Left $ T.pack e+                    Right (max (t+60) -> et, rras) ->+                        insRot True m et $ Right $ listQ [a | RRAddr a <- rras]+    where key = T.pack domain+          err False m _ e = return (m, Left $ T.unpack e)+          err True m t e = return (HM.insert key (t, Left e) m, Left $ T.unpack e)+          insRot f m t (Left e) = err f m t e+          insRot f m t (Right q)+              | Just (a, q') <- rotQ q =+                  return (HM.insert key (t, Right q') m,+                          Right a)+              | otherwise =+                  err f m t "No RRAddr???"++resolveA' :: DnsCache -> [HostName] -> HostName+          -> IO (Either String (POSIXTime, [RRAddr]))+resolveA' d@(DnsCache {..}) parents x+    | length parents > 20 =+        return $ Left $ "Too many CNAMEs " ++ show (x : parents)+    | x `elem` parents =+        return $ Left $ "CNAME loop " ++ show x ++ " already in "+                   ++ show parents+    | otherwise = do+        Answer {..} <- resolver x A+                       [QuoteOk_Query, QuoteOk_AnsHost] >>= takeMVar+        case () of+            _ | status == sOK -> do+--                  print (posixSecondsToUTCTime (realToFrac expires :: POSIXTime))+                  return $ Right (realToFrac expires, [ a | RRA a <- rrs ])+              | status == sPROHIBITEDCNAME+              , Just cn <- cname -> resolveA' d (x:parents) cn+                           -- cycle when CNAME refers another CNAME+            _ -> do+                e <- adnsStrerror status+                s <- adnsErrAbbrev status+                return $ Left $ e ++ " (" ++ s ++ ")"++isIPAddr :: HostName -> Bool+isIPAddr hn = length groups == 4 && all ip groups+    where groups = splitPoints hn+          ip x = length x <= 3 && all (\ e -> e >= '0' && e <= '9') x &&+                 read x <= (255 :: Int)++splitPoints :: HostName -> [String]+splitPoints = filter (/= ".") . groupBy (\ a b -> a /= '.' && b /= '.')++ipToWord32 :: String -> Word32+ipToWord32 ip =+    let [b4,b3,b2,b1] = (map read $ splitPoints ip) :: [Word32]+        mk x1 x2 x3 x4 =+            (x1 `shiftL` 24) + (x2 `shiftL` 16) + (x3 `shiftL` 8) + x4+    in+      case endian of+        BigEndian    -> mk b4 b3 b2 b1+        LittleEndian -> mk b1 b2 b3 b4+        PDPEndian    -> mk b2 b1 b4 b3++-- | Show @HostAddress@ in standard 123.45.67.89 format.+--+-- Unlike 'inet_ntoa' this function is pure and thread-safe.+showHostAddress :: HostAddress -> String+showHostAddress = show . RRAddr+++_test :: IO ()+_test = withDnsCache $ \ c -> do+    let r hn = do+          h <- resolveA c hn+          putStrLn $ hn ++ ": " ++ either id showHostAddress h+        rn n = replicateM_ n . r+    r "127.0.0.1"+    r "qwerqwer"+    r "qwer.google.com"+    rn 10 "www.huffingtonpost.com"+    rn 10 "twitter.com"+    rn 10 "wordpress.com"+    rn 10 "feedburner.com"+    rn 10 "feeds.feedburner.com"+--     replicateM_ 1000 $ do+--         resolveA c "www.blogger.com"+--         resolveA c "google.com"
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Vladimir Shabanov 2011-2012.+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Neil Mitchell nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,3 @@+#! /usr/bin/env runhaskell+import Distribution.Simple+main = defaultMain
+ hsdns-cache.cabal view
@@ -0,0 +1,42 @@+cabal-version:  >= 1.6+name:           hsdns-cache+version:        1.0.0+copyright:      Vladimir Shabanov 2013+author:         Vladimir Shabanov <vshabanoff@gmail.com>+maintainer:     Vladimir Shabanov <vshabanoff@gmail.com>+homepage:       https://github.com/vshabanov/hsdns-cache+license:        BSD3+category:       Network+license-file:   LICENSE+build-type:     Simple+synopsis:       Caching asynchronous DNS resolver.+description:+    .+    Caching asynchronous DNS resolver built on top of+    GNU ADNS <http://www.chiark.greenend.org.uk/~ian/adns/>.+    .+     * Resolves several IP addresses for one host (if available)+       in round-robin fashion.+    .+     * Limits number of parallel requests (so DNS resolving continues to work+       even under heavy load).+    .+     * Errors are cached too (for one minute).+    .+     * Handles CNAMEs (@hsdns@ returns error for them).+    .+    This cache is tested in a long running web-crawler+    (used in <http://bazqux.com>) so it should be safe to use it in real world+    applications.++source-repository head+    type:     git+    location: https://github.com/vshabanov/hsdns-cache++library+    build-depends: base == 4.*, text, unordered-containers, hsdns, network, time, SafeSemaphore++    exposed-modules:+        ADNS.Cache++    ghc-options: -O2 -Wall