packages feed

dns 0.3.8 → 1.0.0

raw patch · 10 files changed

+568/−248 lines, 10 filesdep +doctestdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: doctest

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Network.DNS.Lookup: lookupNSAuth :: Resolver -> Domain -> IO (Either DNSError [Domain])
+ Network.DNS.Lookup: lookupRDNS :: Resolver -> Domain -> IO (Either DNSError [Domain])
+ Network.DNS.Resolver: lookupAuth :: Resolver -> Domain -> TYPE -> IO (Either DNSError [RDATA])
+ Network.DNS.Types: SequenceNumberMismatch :: DNSError
+ Network.DNS.Types: TimeoutExpired :: DNSError
+ Network.DNS.Types: UnexpectedRDATA :: DNSError
+ Network.DNS.Types: data DNSError
- Network.DNS.Lookup: lookupA :: Resolver -> Domain -> IO (Maybe [IPv4])
+ Network.DNS.Lookup: lookupA :: Resolver -> Domain -> IO (Either DNSError [IPv4])
- Network.DNS.Lookup: lookupAAAA :: Resolver -> Domain -> IO (Maybe [IPv6])
+ Network.DNS.Lookup: lookupAAAA :: Resolver -> Domain -> IO (Either DNSError [IPv6])
- Network.DNS.Lookup: lookupAAAAviaMX :: Resolver -> Domain -> IO (Maybe [IPv6])
+ Network.DNS.Lookup: lookupAAAAviaMX :: Resolver -> Domain -> IO (Either DNSError [IPv6])
- Network.DNS.Lookup: lookupAviaMX :: Resolver -> Domain -> IO (Maybe [IPv4])
+ Network.DNS.Lookup: lookupAviaMX :: Resolver -> Domain -> IO (Either DNSError [IPv4])
- Network.DNS.Lookup: lookupMX :: Resolver -> Domain -> IO (Maybe [(Domain, Int)])
+ Network.DNS.Lookup: lookupMX :: Resolver -> Domain -> IO (Either DNSError [(Domain, Int)])
- Network.DNS.Lookup: lookupNS :: Resolver -> Domain -> IO (Maybe [Domain])
+ Network.DNS.Lookup: lookupNS :: Resolver -> Domain -> IO (Either DNSError [Domain])
- Network.DNS.Lookup: lookupPTR :: Resolver -> Domain -> IO (Maybe [Domain])
+ Network.DNS.Lookup: lookupPTR :: Resolver -> Domain -> IO (Either DNSError [Domain])
- Network.DNS.Lookup: lookupSRV :: Resolver -> Domain -> IO (Maybe [(Int, Int, Int, Domain)])
+ Network.DNS.Lookup: lookupSRV :: Resolver -> Domain -> IO (Either DNSError [(Int, Int, Int, Domain)])
- Network.DNS.Lookup: lookupTXT :: Resolver -> Domain -> IO (Maybe [ByteString])
+ Network.DNS.Lookup: lookupTXT :: Resolver -> Domain -> IO (Either DNSError [ByteString])
- Network.DNS.Resolver: lookup :: Resolver -> Domain -> TYPE -> IO (Maybe [RDATA])
+ Network.DNS.Resolver: lookup :: Resolver -> Domain -> TYPE -> IO (Either DNSError [RDATA])
- Network.DNS.Resolver: lookupRaw :: Resolver -> Domain -> TYPE -> IO (Maybe DNSFormat)
+ Network.DNS.Resolver: lookupRaw :: Resolver -> Domain -> TYPE -> IO (Either DNSError DNSFormat)

Files

Network/DNS.hs view
@@ -1,29 +1,34 @@-{-|-  Thread-safe DNS library written in Haskell.--  This code is written in Haskell, not using FFI.--  Sample code for DNS lookup:--@-    import qualified Network.DNS as DNS (lookup)-    import Network.DNS hiding (lookup)-    main :: IO ()-    main = do-        rs <- makeResolvSeed defaultResolvConf-        withResolver rs $ \\resolver -> do-            DNS.lookup resolver \"www.example.com\" A >>= print-@--}-+-- | A thread-safe DNS library for both clients and servers written+--   in pure Haskell.+--   The Network.DNS module re-exports all other exposed modules for+--   convenience.+--   Applications will most likely use the high-level interface, while+--   library/daemon authors may need to use the lower-level one.+-- module Network.DNS (   -- * High level     module Network.DNS.Lookup+  -- | The "Network.DNS.Lookup" module contains simple functions to+  --   perform various DNS lookups. If you simply want to resolve a+  --   hostname ('lookupA'), or find a domain's MX record+  --   ('lookupMX'), this is the easiest way to do it.+   , module Network.DNS.Resolver+  -- | The "Network.DNS.Resolver" module is slightly more low-level+  --   than "Network.DNS.Lookup". If you need to do something unusual,+  --   you may need to use the 'lookup', 'lookupAuth', or 'lookupRaw'+  --   functions.+   , module Network.DNS.Types+  -- | All of the types that the other modules use.+   -- * Low level   , module Network.DNS.Decode+  -- | Decoding a response.+   , module Network.DNS.Encode+  -- | Encoding a query.+   ) where  import Network.DNS.Lookup@@ -31,5 +36,3 @@ import Network.DNS.Types import Network.DNS.Decode import Network.DNS.Encode--
Network/DNS/Decode.hs view
@@ -21,16 +21,15 @@  ---------------------------------------------------------------- -{-| Receiving DNS data from 'Socket' and parse it.-    The second argument is a buffer size for the socket.--}+-- | Receiving DNS data from 'Socket' and parse it.+ receive :: Socket -> IO DNSFormat receive sock = receiveDNSFormat $ sourceSocket sock  ---------------------------------------------------------------- -{-| Parsing DNS data.--}+-- | Parsing DNS data.+ decode :: BL.ByteString -> Either String DNSFormat decode bs = fst <$> runSGet decodeResponse bs 
Network/DNS/Encode.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE RecordWildCards #-}+ module Network.DNS.Encode (     encode   , composeQuery@@ -20,8 +21,8 @@  ---------------------------------------------------------------- -{-| Composing query. First argument is a number to identify response.--}+-- | Composing query. First argument is a number to identify response.+ composeQuery :: Int -> [Question] -> BL.ByteString composeQuery idt qs = encode qry   where@@ -36,8 +37,8 @@  ---------------------------------------------------------------- -{-| Composing DNS data.--}+-- | Composing DNS data.+ encode :: DNSFormat -> BL.ByteString encode fmt = runSPut (encodeDNSFormat fmt) 
Network/DNS/Internal.hs view
@@ -8,16 +8,12 @@  ---------------------------------------------------------------- -{-|-  Type for domain.--}+-- | Type for domain. type Domain = ByteString  ---------------------------------------------------------------- -{-|-  Types for resource records.--}+-- | Types for resource records. data TYPE = A | AAAA | NS | TXT | MX | CNAME | SOA | PTR | SRV           | UNKNOWN Int deriving (Eq, Show, Read) @@ -51,9 +47,19 @@  ---------------------------------------------------------------- -{-|-  Raw data format for DNS Query and Response.--}+-- | An enumeration of all possible DNS errors that can occur.+data DNSError =+  -- | The sequence number of the answer doesn't match our query. This+  --   could indicate foul play.+  SequenceNumberMismatch+  -- | The request simply timed out.+  | TimeoutExpired+  -- | The answer has the correct sequence number, but returned an+  --   unexpected RDATA format.+  | UnexpectedRDATA+  deriving (Eq, Show)++-- | Raw data format for DNS Query and Response. data DNSFormat = DNSFormat {     header     :: DNSHeader   , question   :: [Question]@@ -62,9 +68,7 @@   , additional :: [ResourceRecord]   } deriving (Eq, Show) -{-|-  Raw data format for the header of DNS Query and Response.--}+-- | Raw data format for the header of DNS Query and Response. data DNSHeader = DNSHeader {     identifier :: Int   , flags      :: DNSFlags@@ -74,9 +78,7 @@   , arCount    :: Int   } deriving (Eq, Show) -{-|-  Raw data format for the flags of DNS Query and Response.--}+-- | Raw data format for the flags of DNS Query and Response. data DNSFlags = DNSFlags {     qOrR         :: QorR   , opcode       :: OPCODE@@ -97,25 +99,19 @@  ---------------------------------------------------------------- -{-|-  Raw data format for DNS questions.--}+-- | Raw data format for DNS questions. data Question = Question {     qname  :: Domain   , qtype  :: TYPE   } deriving (Eq, Show) -{-|-  Making "Question".--}+-- | Making "Question". makeQuestion :: Domain -> TYPE -> Question makeQuestion = Question  ---------------------------------------------------------------- -{-|-  Raw data format for resource records.--}+-- | Raw data format for resource records. data ResourceRecord = ResourceRecord {     rrname :: Domain   , rrtype :: TYPE@@ -124,9 +120,7 @@   , rdata  :: RDATA   } deriving (Eq, Show) -{-|-  Raw data format for each type.--}+-- | Raw data format for each type. data RDATA = RD_NS Domain | RD_CNAME Domain | RD_MX Int Domain | RD_PTR Domain            | RD_SOA Domain Domain Int Int Int Int Int            | RD_A IPv4 | RD_AAAA IPv6 | RD_TXT ByteString
Network/DNS/Lookup.hs view
@@ -1,123 +1,411 @@-{-|-  Upper level DNS lookup functions.--}-+-- | Simple, high-level DNS lookup functions.+--+--   All of the lookup functions necessary run in IO, since they+--   interact with the network. The return types are similar, but+--   differ in what can be returned from a successful lookup.+--+--   We can think of the return type as \"either what I asked for, or+--   an error\". For example, the 'lookupA' function, if successful,+--   will return a list of 'IPv4'. The 'lookupMX' function will+--   instead return a list of @('Domain',Int)@ pairs, where each pair+--   represents a hostname and its associated priority.+--+--   The order of multiple results may not be consistent between+--   lookups. If you require consistent results, apply+--   'Data.List.sort' to the returned list.+--+--   The errors that can occur are the same for all lookups. Namely:+--+--     * Timeout+--+--     * Wrong sequence number (foul play?)+--+--     * Unexpected data in the response+--+--   If an error occurs, you should be able to pattern match on the+--   'DNSError' constructor to determine which of these is the case.+--+--   /Note/: A result of \"no records\" is not considered an+--   error. If you perform, say, an \'AAAA\' lookup for a domain with+--   no such records, the \"success\" result would be @Right []@.+--+--   We perform a successful lookup of \"www.example.com\":+--+--   >>> let hostname = Data.ByteString.Char8.pack "www.example.com"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> withResolver rs $ \resolver -> lookupA resolver hostname+--   Right [93.184.216.119]+--+--   The only error that we can easily cause is a timeout. We do this+--   by creating and utilizing a 'ResolvConf' which has a timeout of+--   one millisecond:+--+--   >>> let hostname = Data.ByteString.Char8.pack "www.example.com"+--   >>> let badrc = defaultResolvConf { resolvTimeout = 1 }+--   >>>+--   >>> rs <- makeResolvSeed badrc+--   >>> withResolver rs $ \resolver -> lookupA resolver hostname+--   Left TimeoutExpired+--+--   As is the convention, successful results will always be wrapped+--   in a 'Right', while errors will be wrapped in a 'Left'.+--+--   For convenience, you may wish to enable GHC's OverloadedStrings+--   extension. This will allow you to avoid calling+--   'Data.ByteString.Char8.pack' on each domain name. See+--   <http://www.haskell.org/ghc/docs/7.6.3/html/users_guide/type-class-extensions.html#overloaded-strings>+--   for more information.+-- module Network.DNS.Lookup (     lookupA, lookupAAAA   , lookupMX, lookupAviaMX, lookupAAAAviaMX   , lookupNS+  , lookupNSAuth   , lookupTXT   , lookupPTR+  , lookupRDNS   , lookupSRV   ) where -import Control.Applicative import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as BS (append, intercalate, pack, split) import Data.IP-import Data.Maybe import Network.DNS.Resolver as DNS import Network.DNS.Types  ---------------------------------------------------------------- -{-|-  Resolving 'IPv4' by 'A'.--}-lookupA :: Resolver -> Domain -> IO (Maybe [IPv4])-lookupA rlv dom = toV4 <$> DNS.lookup rlv dom A+-- | Look up all \'A\' records for the given hostname.+--+--   A straightforward example:+--+--   >>> let hostname = Data.ByteString.Char8.pack "www.mew.org"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> withResolver rs $ \resolver -> lookupA resolver hostname+--   Right [202.232.15.101]+--+--   This function will also follow a CNAME and resolve its target if+--   one exists for the queries hostname:+--+--   >>> let hostname = Data.ByteString.Char8.pack "www.kame.net"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> withResolver rs $ \resolver -> lookupA resolver hostname+--   Right [203.178.141.194]+--+lookupA :: Resolver -> Domain -> IO (Either DNSError [IPv4])+lookupA rlv dom = do+  erds <- DNS.lookup rlv dom A+  case erds of+    -- See lookupXviaMX for an explanation of this construct.+    Left err  -> return (Left err)+    Right rds -> return $ mapM unTag rds   where-    toV4 = fmap (map unTag)-    unTag (RD_A x) = x-    unTag _ = error "lookupA"+    unTag :: RDATA -> Either DNSError IPv4+    unTag (RD_A x) = Right x+    unTag _ = Left UnexpectedRDATA -{-|-  Resolving 'IPv6' by 'AAAA'.--}-lookupAAAA :: Resolver -> Domain -> IO (Maybe [IPv6])-lookupAAAA rlv dom = toV6 <$> DNS.lookup rlv dom AAAA++-- | Look up all (IPv6) \'AAAA\' records for the given hostname.+--+--   Examples:+--+--   >>> let hostname = Data.ByteString.Char8.pack "www.mew.org"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> withResolver rs $ \resolver -> lookupAAAA resolver hostname+--   Right [2001:240:11e:c00:00:00:00:101]+--+lookupAAAA :: Resolver -> Domain -> IO (Either DNSError [IPv6])+lookupAAAA rlv dom = do+  erds <- DNS.lookup rlv dom AAAA+  case erds of+    -- See lookupXviaMX for an explanation of this construct.+    Left err  -> return (Left err)+    Right rds -> return $ mapM unTag rds   where-    toV6 = fmap (map unTag)-    unTag (RD_AAAA x) = x-    unTag _ = error "lookupAAAA"+    unTag :: RDATA -> Either DNSError IPv6+    unTag (RD_AAAA x) = Right x+    unTag _ = Left UnexpectedRDATA  ---------------------------------------------------------------- -{-|-  Resolving 'Domain' and its preference by 'MX'.--}-lookupMX :: Resolver -> Domain -> IO (Maybe [(Domain,Int)])-lookupMX rlv dom = toMX <$> DNS.lookup rlv dom MX+-- | Look up all \'MX\' records for the given hostname. Two parts+--   constitute an MX record: a hostname , and an integer priority. We+--   therefore return each record as a @('Domain', Int)@.+--+--   In this first example, we look up the MX for the domain+--   \"example.com\". It has no MX (to prevent a deluge of spam from+--   examples posted on the internet). But remember, \"no results\" is+--   still a successful result.+--+--   >>> let hostname = Data.ByteString.Char8.pack "example.com"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> withResolver rs $ \resolver -> lookupMX resolver hostname+--   Right []+--+--   The domain \"mew.org\" does however have a single MX:+--+--   >>> let hostname = Data.ByteString.Char8.pack "mew.org"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> withResolver rs $ \resolver -> lookupMX resolver hostname+--   Right [("mail.mew.org.",10)]+--+--   Also note that all hostnames are returned with a trailing dot to+--   indicate the DNS root.+--+lookupMX :: Resolver -> Domain -> IO (Either DNSError [(Domain,Int)])+lookupMX rlv dom = do+  erds <- DNS.lookup rlv dom MX+  case erds of+    -- See lookupXviaMX for an explanation of this construct.+    Left err  -> return (Left err)+    Right rds -> return $ mapM unTag rds   where-    toMX = fmap (map unTag)-    unTag (RD_MX pr dm) = (dm,pr)-    unTag _ = error "lookupMX"+    unTag :: RDATA -> Either DNSError (Domain,Int)+    unTag (RD_MX pr dm) = Right (dm,pr)+    unTag _ = Left UnexpectedRDATA -{-|-  Resolving 'IPv4' by 'A' via 'MX'.--}-lookupAviaMX :: Resolver -> Domain -> IO (Maybe [IPv4])+-- | Look up all \'MX\' records for the given hostname, and then+--   resolve their hostnames to IPv4 addresses by calling+--   'lookupA'. The priorities are not retained.+--+--   Examples:+--+--   >>> import Data.List (sort)+--   >>> let hostname = Data.ByteString.Char8.pack "mixi.jp"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> ips <- withResolver rs $ \resolver -> lookupAviaMX resolver hostname+--   >>> fmap sort ips+--   Right [202.32.29.4,202.32.29.5]+--+--   Since there is more than one result, it is necessary to sort the+--   list in order to check for equality.+--+lookupAviaMX :: Resolver -> Domain -> IO (Either DNSError [IPv4]) lookupAviaMX rlv dom = lookupXviaMX rlv dom (lookupA rlv) -{-|-  Resolving 'IPv6' by 'AAAA' via 'MX'.--}-lookupAAAAviaMX :: Resolver -> Domain -> IO (Maybe [IPv6])+-- | Look up all \'MX\' records for the given hostname, and then+--   resolve their hostnames to IPv6 addresses by calling+--   'lookupAAAA'. The priorities are not retained.+--+lookupAAAAviaMX :: Resolver -> Domain -> IO (Either DNSError [IPv6]) lookupAAAAviaMX rlv dom = lookupXviaMX rlv dom (lookupAAAA rlv) -lookupXviaMX :: Show a => Resolver -> Domain -> (Domain -> IO (Maybe [a])) -> IO (Maybe [a])+lookupXviaMX :: Show a+             => Resolver+             -> Domain+             -> (Domain -> IO (Either DNSError [a]))+             -> IO (Either DNSError [a]) lookupXviaMX rlv dom func = do-    mdps <- lookupMX rlv dom-    maybe (return Nothing) lookup' mdps-  where-    lookup' dps = check . catMaybes <$> mapM (func . fst) dps-    check as = case as of-        []  -> Nothing-        ass -> Just (concat ass)+    edps <- lookupMX rlv dom+    case edps of+      -- We have to deconstruct and reconstruct the error so that the+      -- typechecker does not conclude that a ~ (Domain, Int).+      Left err -> return (Left err)+      Right dps -> do+        -- We'll get back a [Either DNSError a] here.+        responses <- mapM (func . fst) dps+        -- We can use 'sequence' to join all of the Eithers+        -- together. If any of them are (Left _), we'll get a Left+        -- overall. Otherwise, we'll get Right [a].+        let overall = sequence responses+        -- Finally, we use (fmap concat) to concatenate the responses+        -- if there were no errors.+        return $ fmap concat overall ++ ---------------------------------------------------------------- -{-|-  Resolving 'Domain' by 'NS'.--}-lookupNS :: Resolver -> Domain -> IO (Maybe [Domain])-lookupNS rlv dom = toNS <$> DNS.lookup rlv dom NS+-- | This function performs the real work for both 'lookupNS' and+--   'lookupNSAuth'. The only difference between those two is which+--   function, 'lookup' or 'lookupAuth', is used to perform the+--   lookup. We take either of those as our first parameter.+lookupNSImpl :: (Resolver -> Domain -> TYPE -> IO (Either DNSError [RDATA]))+             -> Resolver+             -> Domain+             -> IO (Either DNSError [Domain])+lookupNSImpl lookup_function rlv dom = do+  erds <- lookup_function rlv dom NS+  case erds of+    -- See lookupXviaMX for an explanation of this construct.+    Left err  -> return (Left err)+    Right rds -> return $ mapM unTag rds   where-    toNS = fmap (map unTag)-    unTag (RD_NS dm) = dm-    unTag _ = error "lookupNS"+    unTag :: RDATA -> Either DNSError Domain+    unTag (RD_NS dm) = Right dm+    unTag _ = Left UnexpectedRDATA +-- | Look up all \'NS\' records for the given hostname. The results+--   are taken from the ANSWER section of the response (as opposed to+--   AUTHORITY). For details, see e.g.+--   <http://www.zytrax.com/books/dns/ch15/>.+--+--   There will typically be more than one name server for a+--   domain. It is therefore extra important to sort the results if+--   you prefer them to be at all deterministic.+--+--   Examples:+--+--   >>> import Data.List (sort)+--   >>> let hostname = Data.ByteString.Char8.pack "mew.org"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> ns <- withResolver rs $ \resolver -> lookupNS resolver hostname+--   >>> fmap sort ns+--   Right ["ns1.mew.org.","ns2.mew.org."]+--+lookupNS :: Resolver -> Domain -> IO (Either DNSError [Domain])+lookupNS = lookupNSImpl DNS.lookup++-- | Look up all \'NS\' records for the given hostname. The results+--   are taken from the AUTHORITY section of the response and not the+--   usual ANSWER (use 'lookupNS' for that). For details, see e.g.+--   <http://www.zytrax.com/books/dns/ch15/>.+--+--   There will typically be more than one name server for a+--   domain. It is therefore extra important to sort the results if+--   you prefer them to be at all deterministic.+--+--   For an example, we can look up the nameservers for+--   \"example.com\" from one of the root servers, a.gtld-servers.net,+--   the IP address of which was found beforehand:+--+--   >>> import Data.List (sort)+--   >>> let hostname = Data.ByteString.Char8.pack "example.com"+--   >>>+--   >>> let ri = RCHostName "192.5.6.30" -- a.gtld-servers.net+--   >>> let rc = defaultResolvConf { resolvInfo = ri }+--   >>> rs <- makeResolvSeed rc+--   >>> ns <- withResolver rs $ \resolver -> lookupNSAuth resolver hostname+--   >>> fmap sort ns+--   Right ["a.iana-servers.net.","b.iana-servers.net."]+--+lookupNSAuth :: Resolver -> Domain -> IO (Either DNSError [Domain])+lookupNSAuth = lookupNSImpl DNS.lookupAuth++ ---------------------------------------------------------------- -{-|-  Resolving 'String' by 'TXT'.--}-lookupTXT :: Resolver -> Domain -> IO (Maybe [ByteString])-lookupTXT rlv dom = toTXT <$> DNS.lookup rlv dom TXT+-- | Look up all \'TXT\' records for the given hostname. The results+--   are free-form 'ByteString's.+--+--   Two common uses for \'TXT\' records are+--   <http://en.wikipedia.org/wiki/Sender_Policy_Framework> and+--   <http://en.wikipedia.org/wiki/DomainKeys_Identified_Mail>. As an+--   example, we find the SPF record for \"mew.org\":+--+--   >>> let hostname = Data.ByteString.Char8.pack "mew.org"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> withResolver rs $ \resolver -> lookupTXT resolver hostname+--   Right ["v=spf1 +mx -all"]+--+lookupTXT :: Resolver -> Domain -> IO (Either DNSError [ByteString])+lookupTXT rlv dom = do+  erds <- DNS.lookup rlv dom TXT+  case erds of+    -- See lookupXviaMX for an explanation of this construct.+    Left err  -> return (Left err)+    Right rds -> return $ mapM unTag rds   where-    toTXT = fmap (map unTag)-    unTag (RD_TXT x) = x-    unTag _ = error "lookupTXT"+    unTag :: RDATA -> Either DNSError ByteString+    unTag (RD_TXT x) = Right x+    unTag _ = Left UnexpectedRDATA  ---------------------------------------------------------------- -{-|-  Resolving 'Domain' and its preference by 'PTR'.--}-lookupPTR :: Resolver -> Domain -> IO (Maybe [Domain])-lookupPTR rlv dom = toPTR <$> DNS.lookup rlv dom PTR+-- | Look up all \'PTR\' records for the given hostname. To perform a+--   reverse lookup on an IP address, you must first reverse its+--   octets and then append the suffix \".in-addr.arpa.\"+--+--   We look up the PTR associated with the IP address+--   210.130.137.80, i.e., 80.137.130.210.in-addr.arpa:+--+--   >>> let hostname = Data.ByteString.Char8.pack "80.137.130.210.in-addr.arpa"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> withResolver rs $ \resolver -> lookupPTR resolver hostname+--   Right ["www-v4.iij.ad.jp."]+--+--   The 'lookupRDNS' function is more suited to this particular task.+--+lookupPTR :: Resolver -> Domain -> IO (Either DNSError [Domain])+lookupPTR rlv dom = do+  erds <- DNS.lookup rlv dom PTR+  case erds of+    -- See lookupXviaMX for an explanation of this construct.+    Left err  -> return (Left err)+    Right rds -> return $ mapM unTag rds   where-    toPTR = fmap (map unTag)-    unTag (RD_PTR dm) = dm-    unTag _ = error "lookupPTR"+    unTag :: RDATA -> Either DNSError Domain+    unTag (RD_PTR dm) = Right dm+    unTag _ = Left UnexpectedRDATA ++-- | Convenient wrapper around 'lookupPTR' to perform a reverse lookup+--   on a single IP address.+--+--   We repeat the example from 'lookupPTR', except now we pass the IP+--   address directly:+--+--   >>> let hostname = Data.ByteString.Char8.pack "210.130.137.80"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> withResolver rs $ \resolver -> lookupRDNS resolver hostname+--   Right ["www-v4.iij.ad.jp."]+--+lookupRDNS :: Resolver -> Domain -> IO (Either DNSError [Domain])+lookupRDNS rlv ip = lookupPTR rlv dom+  where+    -- ByteString constants.+    dot = BS.pack "."+    suffix = BS.pack ".in-addr.arpa"++    octets = BS.split '.' ip+    reverse_ip = BS.intercalate dot (reverse octets)+    dom = reverse_ip `BS.append` suffix+ ---------------------------------------------------------------- -{-|-  Resolving 'Domain' and its preference by 'SRV'.--}-lookupSRV :: Resolver -> Domain -> IO (Maybe [(Int,Int,Int,Domain)])-lookupSRV rlv dom = toSRV <$> DNS.lookup rlv dom SRV+-- | Look up all \'SRV\' records for the given hostname. A SRV record+--   comprises four fields,+--+--     * Priority (lower is more-preferred)+--+--     * Weight (relative frequency with which to use this record+--       amongst all results with the same priority)+--+--     * Port (the port on which the service is offered)+--+--     * Target (the hostname on which the service is offered)+--+--   The first three are integral, and the target is another DNS+--   hostname. We therefore return a four-tuple+--   @(Int,Int,Int,'Domain')@.+--+--   Examples:+--+--   >>> let hostname = Data.ByteString.Char8.pack "_sip._tcp.cisco.com"+--   >>>+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> withResolver rs $ \resolver -> lookupSRV resolver hostname+--   Right [(1,0,5060,"vcsgw.cisco.com.")]+--+lookupSRV :: Resolver -> Domain -> IO (Either DNSError [(Int,Int,Int,Domain)])+lookupSRV rlv dom = do+  erds <- DNS.lookup rlv dom SRV+  case erds of+    -- See lookupXviaMX for an explanation of this construct.+    Left err  -> return (Left err)+    Right rds -> return $ mapM unTag rds   where-    toSRV = fmap (map unTag)-    unTag (RD_SRV pri wei prt dm) = (pri,wei,prt,dm)-    unTag _ = error "lookupSRV"+    unTag :: RDATA -> Either DNSError (Int,Int,Int,Domain)+    unTag (RD_SRV pri wei prt dm) = Right (pri,wei,prt,dm)+    unTag _ = Left UnexpectedRDATA
Network/DNS/Resolver.hs view
@@ -1,20 +1,6 @@ {-# LANGUAGE CPP #-}-{-|-  DNS Resolver and lookup functions. -  Sample code:--@-    import qualified Network.DNS as DNS (lookup)-    import Network.DNS hiding (lookup)-    main :: IO ()-    main = do-        rs <- makeResolvSeed defaultResolvConf-        withResolver rs $ \\resolver -> do-            DNS.lookup resolver \"www.example.com\" A >>= print-@--}-+-- | DNS Resolver and generic (lower-level) lookup functions. module Network.DNS.Resolver (   -- * Documentation   -- ** Configuration for resolver@@ -24,7 +10,7 @@   -- ** Type and function for resolver   , Resolver(..), withResolver   -- ** Looking up functions-  , lookup, lookupRaw+  , lookup, lookupAuth, lookupRaw   ) where  import Control.Applicative@@ -49,15 +35,21 @@ #endif ---------------------------------------------------------------- -{-|-  Union type for 'FilePath' and 'HostName'. Specify 'FilePath' to-  \"resolv.conf\" or numeric IP address in 'String' form.--}++-- | Union type for 'FilePath' and 'HostName'. Specify 'FilePath' to+--   \"resolv.conf\" or numeric IP address in 'String' form.+--+--   /Warning/: Only numeric IP addresses are valid @RCHostName@s.+--+--   Example (using Google's public DNS cache):+--+--   >>> let cache = RCHostName "8.8.8.8"+-- data FileOrNumericHost = RCFilePath FilePath | RCHostName HostName -{-|-  Type for resolver configuration--}++-- | Type for resolver configuration. The easiest way to construct a+--   @ResolvConf@ object is to modify the 'defaultResolvConf'. data ResolvConf = ResolvConf {     resolvInfo :: FileOrNumericHost   , resolvTimeout :: Int@@ -65,12 +57,20 @@   , resolvBufsize :: Integer } -{-|-  Default 'ResolvConf'.-  'resolvInfo' is 'RCFilePath' \"\/etc\/resolv.conf\".-  'resolvTimeout' is 3,000,000 micro seconds.-  'resolvBufsize' is 512. (obsoleted)--}++-- | Return a default 'ResolvConf':+--+--     * 'resolvInfo' is 'RCFilePath' \"\/etc\/resolv.conf\".+--+--     * 'resolvTimeout' is 3,000,000 micro seconds.+--+--     * 'resolvBufsize' is 512. (obsoleted)+--+--  Example (use Google's public DNS cache instead of resolv.conf):+--+--   >>> let cache = RCHostName "8.8.8.8"+--   >>> let rc = defaultResolvConf { resolvInfo = cache }+-- defaultResolvConf :: ResolvConf defaultResolvConf = ResolvConf {     resolvInfo = RCFilePath "/etc/resolv.conf"@@ -80,18 +80,14 @@  ---------------------------------------------------------------- -{-|-  Abstract data type of DNS Resolver seed--}+-- | Abstract data type of DNS Resolver seed data ResolvSeed = ResolvSeed {     addrInfo :: AddrInfo   , rsTimeout :: Int   , rsBufsize :: Integer } -{-|-  Abstract data type of DNS Resolver--}+-- | Abstract data type of DNS Resolver data Resolver = Resolver {     genId   :: IO Int   , dnsSock :: Socket@@ -101,9 +97,13 @@  ---------------------------------------------------------------- -{-|-  Making 'ResolvSeed' from an IP address of a DNS cache server.--}++-- |  Make a 'ResolvSeed' from a 'ResolvConf'.+--+--    Examples:+--+--    >>> rs <- makeResolvSeed defaultResolvConf+-- makeResolvSeed :: ResolvConf -> IO ResolvSeed makeResolvSeed conf = ResolvSeed <$> addr                                  <*> pure (resolvTimeout conf)@@ -129,11 +129,10 @@  ---------------------------------------------------------------- -{-|-  Giving a thread-safe 'Resolver' to the function of the second-  argument. 'withResolver' should be passed to 'forkIO'.--} +-- | Giving a thread-safe 'Resolver' to the function of the second+--   argument. 'withResolver' should be passed to 'forkIO'. For+--   examples, see "Network.DNS.Lookup". withResolver :: ResolvSeed -> (Resolver -> IO a) -> IO a withResolver seed func = do   let ai = addrInfo seed@@ -152,29 +151,93 @@  ---------------------------------------------------------------- -{-|-  Looking up resource records of a domain.--}-lookup :: Resolver -> Domain -> TYPE -> IO (Maybe [RDATA])-lookup rlv dom typ = (>>= toRDATA) <$> lookupRaw rlv dom typ+-- | Looking up resource records of a domain. The first parameter is one of+--   the field accessors of the 'DNSFormat' type -- this allows you to+--   choose which section (answer, authority, or additional) you would like+--   to inspect for the result.++lookupSection :: (DNSFormat -> [ResourceRecord])+              -> Resolver+              -> Domain+              -> TYPE+              -> IO (Either DNSError [RDATA])+lookupSection section rlv dom typ = (>>= toRDATA) <$> lookupRaw rlv dom typ   where     {- CNAME hack     dom' = if "." `isSuffixOf` dom then dom else dom ++ "."     correct r = rrname r == dom' && rrtype r == typ     -}     correct r = rrtype r == typ-    listToMaybe [] = Nothing-    listToMaybe xs = Just xs-    toRDATA = listToMaybe . map rdata . filter correct . answer+    toRDATA = Right . map rdata . filter correct . section -{-|-  Looking up a domain and returning an entire DNS Response.--}-lookupRaw :: Resolver -> Domain -> TYPE -> IO (Maybe DNSFormat)+-- | Look up resource records for a domain, collecting the results+--   from the ANSWER section of the response.+--+--   We repeat an example from "Network.DNS.Lookup":+--+--   >>> let hostname = Data.ByteString.Char8.pack "www.example.com"+--   >>> rs <- makeResolvSeed defaultResolvConf+--   >>> withResolver rs $ \resolver -> lookup resolver hostname A+--   Right [93.184.216.119]+--+lookup :: Resolver -> Domain -> TYPE -> IO (Either DNSError [RDATA])+lookup = lookupSection answer++-- | Look up resource records for a domain, collecting the results+--   from the AUTHORITY section of the response.+lookupAuth :: Resolver -> Domain -> TYPE -> IO (Either DNSError [RDATA])+lookupAuth = lookupSection authority+++-- | Look up a name and return the entire DNS Response. Sample output+--   is included below, however it is /not/ tested -- the sequence+--   number is unpredictable (it has to be!).+--+--   The example code:+--+--   @+--   let hostname = Data.ByteString.Char8.pack \"www.example.com\"+--   rs <- makeResolvSeed defaultResolvConf+--   withResolver rs $ \resolver -> lookupRaw resolver hostname A+--   @+--+--   And the (formatted) expected output:+--+--   @+--   Right (DNSFormat+--           { header = DNSHeader+--                        { identifier = 1,+--                          flags = DNSFlags+--                                    { qOrR = QR_Response,+--                                      opcode = OP_STD,+--                                      authAnswer = False,+--                                      trunCation = False,+--                                      recDesired = True,+--                                      recAvailable = True,+--                                      rcode = NoErr },+--                          qdCount = 1,+--                          anCount = 1,+--                          nsCount = 0,+--                          arCount = 0},+--             question = [Question { qname = \"www.example.com.\",+--                                    qtype = A}],+--             answer = [ResourceRecord {rrname = \"www.example.com.\",+--                                       rrtype = A,+--                                       rrttl = 800,+--                                       rdlen = 4,+--                                       rdata = 93.184.216.119}],+--             authority = [],+--             additional = []})+--  @+--+lookupRaw :: Resolver -> Domain -> TYPE -> IO (Either DNSError DNSFormat) lookupRaw rlv dom typ = do     seqno <- genId rlv     sendAll sock (composeQuery seqno [q])-    (>>= check seqno) <$> timeout tm (receive sock)+    response <- timeout tm (receive sock)+    return $ case response of+               Nothing -> Left TimeoutExpired+               Just y  -> check seqno y   where     sock = dnsSock rlv     tm = dnsTimeout rlv@@ -182,9 +245,9 @@     check seqno res = do         let hdr = header res         if identifier hdr == seqno then-            Just res+            Right res           else-            Nothing+            Left SequenceNumberMismatch  #if mingw32_HOST_OS == 1     -- Windows does not support sendAll in Network.ByteString.Lazy.
Network/DNS/Types.hs view
@@ -1,12 +1,13 @@-{-|-  Data types for DNS Query and Response. For more information, see <http://www.ietf.org/rfc/rfc1035>.--}+-- | Data types for DNS Query and Response.+--   For more information, see <http://www.ietf.org/rfc/rfc1035>.  module Network.DNS.Types (   -- * Domain     Domain   -- * TYPE   , TYPE (..), intToType, typeToInt, toType+  -- * DNS Error+  , DNSError (..)   -- * DNS Format   , DNSFormat (..)   -- * DNS Header
dns.cabal view
@@ -1,11 +1,13 @@ Name:                   dns-Version:                0.3.8+Version:                1.0.0 Author:                 Kazu Yamamoto <kazu@iij.ad.jp> Maintainer:             Kazu Yamamoto <kazu@iij.ad.jp> License:                BSD3 License-File:           LICENSE Synopsis:               DNS library in Haskell-Description:            DNS library for clients and servers.+Description:+  A thread-safe DNS library for both clients and servers written+  in pure Haskell. Category:               Network Cabal-Version:          >= 1.10 Build-Type:             Simple@@ -85,6 +87,15 @@                       , network >= 2.3                       , network-conduit                       , random++Test-Suite doctest+  Type:                 exitcode-stdio-1.0+  Default-Language:     Haskell2010+  Hs-Source-Dirs:       test2+  Ghc-Options:          -Wall+  Main-Is:              doctests.hs+  Build-Depends:        base+                      , doctest  Source-Repository head   Type:                 git
test2/LookupSpec.hs view
@@ -2,66 +2,17 @@  module LookupSpec where -import Control.Applicative-import qualified Data.ByteString.Char8 as BS-import Data.List import Network.DNS as DNS import Test.Hspec  spec :: Spec spec = do-    describe "lookupA" $ do-        it "gets IPv4 addresses" $ do-            rs <- makeResolvSeed defaultResolvConf-            withResolver rs $ \resolver ->-                DNS.lookupA resolver "www.mew.org" `shouldReturn` Just ["202.232.15.101"] -        it "gets IPv4 addresses via CNAME" $ do-            rs <- makeResolvSeed defaultResolvConf-            withResolver rs $ \resolver ->-                DNS.lookupA resolver "www.kame.net" `shouldReturn` Just ["203.178.141.194"]-     describe "lookupAAAA" $ do         it "gets IPv6 addresses" $ do             rs <- makeResolvSeed defaultResolvConf             withResolver rs $ \resolver -> do-                DNS.lookupAAAA resolver "mew.org" `shouldReturn` Nothing-                DNS.lookupAAAA resolver "www.mew.org" `shouldReturn` Just ["2001:240:11e:c00::101"]--    describe "lookupNS" $ do-        it "gets NS" $ do-            rs <- makeResolvSeed defaultResolvConf-            withResolver rs $ \resolver -> do-                actual <- DNS.lookupNS resolver "mew.org"-                let expected = Just ["ns1.mew.org.", "ns2.mew.org."]-                -- The order of NS records is variable, so we sort the-                -- result.-                sort <$> actual `shouldBe` expected--    describe "lookupTXT" $ do-        it "gets TXT" $ do-            rs <- makeResolvSeed defaultResolvConf-            withResolver rs $ \resolver ->-                DNS.lookupTXT resolver "mew.org" `shouldReturn` Just ["v=spf1 +mx -all"]--    describe "lookupAviaMX" $ do-        it "gets IPv4 addresses via MX" $ do-            rs <- makeResolvSeed defaultResolvConf-            withResolver rs $ \resolver -> do-                as <- DNS.lookupAviaMX resolver "mixi.jp"-                sort <$> as `shouldBe` Just ["202.32.29.4", "202.32.29.5"]--    describe "lookupPTR" $ do-        it "gets PTR" $ do-            rs <- makeResolvSeed defaultResolvConf-            withResolver rs $ \resolver -> do-                let target = "210.130.137.80"-                    rev = BS.intercalate "." (reverse (BS.split '.' target))-                            `BS.append` ".in-addr.arpa"-                DNS.lookupPTR resolver rev `shouldReturn` Just ["www-v4.iij.ad.jp."]+                DNS.lookupAAAA resolver "mew.org"+                    `shouldReturn`+                    Right [] -    describe "lookupSRV" $ do-        it "gets SRV" $ do-            rs <- makeResolvSeed defaultResolvConf-            withResolver rs $ \resolver ->-                DNS.lookupSRV resolver "_sip._tcp.cisco.com" `shouldReturn` Just [(1,0,5060,"vcsgw.cisco.com.")]
+ test2/doctests.hs view
@@ -0,0 +1,9 @@+module Main where++import Test.DocTest++main :: IO ()+main = doctest [+    "-XOverloadedStrings"+  , "Network/DNS.hs"+  ]