dns-3.0.0: Network/DNS/Resolver.hs
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BangPatterns #-}
-- | Resolver related data types.
module Network.DNS.Resolver (
-- * Configuration for resolver
ResolvConf
, defaultResolvConf
-- ** Accessors
, resolvInfo
, resolvTimeout
, resolvRetry
, resolvEDNS
, resolvConcurrent
, resolvCache
-- ** Specifying DNS servers
, FileOrNumericHost(..)
-- ** Configuring cache
, CacheConf
, defaultCacheConf
, maximumTTL
, pruningDelay
-- * Intermediate data type for resolver
, ResolvSeed
, makeResolvSeed
-- * Type and function for resolver
, Resolver
, withResolver
, withResolvers
) where
#if !defined(mingw32_HOST_OS)
#define POSIX
#else
#define WIN
#endif
#if __GLASGOW_HASKELL__ < 709
#define GHC708
#endif
import qualified Data.ByteString as BS
import Control.Exception as E
import Control.Monad (forM, replicateM)
import Data.Maybe (isJust, maybe)
import qualified Crypto.Random as C
import Data.IORef (IORef)
import qualified Data.IORef as I
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NE
import Data.Word (Word16)
import Network.BSD (getProtocolNumber)
import Network.DNS.Transport
import Network.DNS.Types
import Network.DNS.Types.Internal
import Network.DNS.Memo
import Network.Socket (AddrInfoFlag(..), AddrInfo(..), PortNumber(..), HostName, SocketType(Datagram), getAddrInfo, defaultHints)
import Prelude hiding (lookup)
#ifdef GHC708
import Control.Applicative ((<$>), (<*>), pure)
#endif
#if defined(WIN)
import qualified Data.List.Split as Split
import Foreign.C.String
import Foreign.Marshal.Alloc (allocaBytes)
import Data.Word
#else
import Data.Char (isSpace)
import Data.List (isPrefixOf)
#endif
----------------------------------------------------------------
-- | Make a 'ResolvSeed' from a 'ResolvConf'.
--
-- Examples:
--
-- >>> rs <- makeResolvSeed defaultResolvConf
--
makeResolvSeed :: ResolvConf -> IO ResolvSeed
makeResolvSeed conf = ResolvSeed conf <$> findAddresses
where
findAddresses :: IO (NonEmpty AddrInfo)
findAddresses = case resolvInfo conf of
RCHostName numhost -> (:| []) <$> makeAddrInfo numhost Nothing
RCHostPort numhost mport -> (:| []) <$> makeAddrInfo numhost (Just mport)
RCHostNames nss -> mkAddrs nss
RCFilePath file -> getDefaultDnsServers file >>= mkAddrs
mkAddrs [] = E.throwIO BadConfiguration
mkAddrs (l:ls) = (:|) <$> makeAddrInfo l Nothing <*> forM ls (`makeAddrInfo` Nothing)
getDefaultDnsServers :: FilePath -> IO [String]
#if defined(WIN)
foreign import ccall "getWindowsDefDnsServers" getWindowsDefDnsServers :: CString -> Int -> IO Word32
getDefaultDnsServers _ = do
allocaBytes 128 $ \cString -> do
res <- getWindowsDefDnsServers cString 128
case res of
0 -> do
addresses <- peekCString cString
return $ filter (not . null) . Split.splitOn "," $ addresses
_ -> do
-- TODO: Do proper error handling here.
return mempty
#else
getDefaultDnsServers file = toAddresses <$> readFile file
where
toAddresses :: String -> [String]
toAddresses cs = map extract (filter ("nameserver" `isPrefixOf`) (lines cs))
extract = reverse . dropWhile isSpace . reverse . dropWhile isSpace . drop 11
#endif
makeAddrInfo :: HostName -> Maybe PortNumber -> IO AddrInfo
makeAddrInfo addr mport = do
proto <- getProtocolNumber "udp"
let flags = [AI_ADDRCONFIG, AI_NUMERICHOST, AI_PASSIVE]
hints = defaultHints {
addrFlags = if isJust mport then AI_NUMERICSERV : flags else flags
, addrSocketType = Datagram
, addrProtocol = proto
}
serv = maybe "domain" show mport
head <$> getAddrInfo (Just hints) (Just addr) (Just serv)
----------------------------------------------------------------
-- | Giving a thread-safe 'Resolver' to the function of the second
-- argument.
withResolver :: ResolvSeed -> (Resolver -> IO a) -> IO a
withResolver seed f = makeResolver seed >>= f
{-# DEPRECATED withResolvers "Use withResolver with resolvConcurrent set to True" #-}
-- | Giving thread-safe 'Resolver's to the function of the second
-- argument. For each 'Resolver', multiple lookups must be done
-- sequentially. 'Resolver's can be used concurrently.
withResolvers :: [ResolvSeed] -> ([Resolver] -> IO a) -> IO a
withResolvers seeds f = mapM makeResolver seeds >>= f
makeResolver :: ResolvSeed -> IO Resolver
makeResolver seed = do
let n = NE.length $ nameservers seed
refs <- replicateM n (C.drgNew >>= I.newIORef)
let gens = NE.fromList $ map getRandom refs
case resolvCache $ resolvconf seed of
Just cacheconf -> do
c <- newCache $ pruningDelay cacheconf
return $ Resolver seed gens $ Just c
Nothing -> return $ Resolver seed gens Nothing
getRandom :: IORef C.ChaChaDRG -> IO Word16
getRandom ref = I.atomicModifyIORef' ref $ \gen ->
let (bs, gen') = C.randomBytesGenerate 2 gen
[u,l] = map fromIntegral $ BS.unpack bs
!seqno = u * 256 + l
in (gen', seqno)