{-# LANGUAGE UnicodeSyntax #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE DoAndIfThenElse #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE TypeFamilies #-} -- | This module provides Domain Name System data structures and -- (de)serialization routines. module Network.DNS ( HostName , aHostName , hostName , hostNameLabels , arpaHostName , HostAddr(..) , Host4Addr , Host6Addr , aHostAddr , aHostAddrOf , aHost4Addr , aHost6Addr , aHostAddrIP , DnsId , DnsType(..) , dnsTypeCode , DnsData(..) , DnsRecord(..) , DnsQType(..) , dnsQTypeCode , DnsQuestion(..) , DnsReq(..) , DnsError(..) , DnsResp(..) ) where import Data.Typeable import Data.Proxy (Proxy(..)) import Data.Foldable (forM_) import Data.Hashable import Data.Word import Data.Bits import Data.Char (chr, ord) import Data.Map (Map) import qualified Data.Map as Map import Data.ByteString (ByteString) import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BS8 import qualified Data.ByteString.Lazy as BSL import Data.Binary (Binary) import qualified Data.Binary as B import qualified Data.Binary.Put as B import qualified Data.Binary.Get as B import Data.Serialize (Serialize) import qualified Data.Serialize as S import Text.Parser.Combinators as P import Text.Parser.Char as P import Text.Printer ((<>)) import qualified Text.Printer as T import Data.Textual (Printable, toAscii, toUtf8, Textual) import qualified Data.Textual as T import qualified Text.Ascii as A import Text.Printf import qualified Text.Read as TR import Network.IP.Addr import Control.Applicative ((<$>), Applicative(..), (<|>)) import Control.Monad (void, unless, ap, foldM) -- | Host name. newtype HostName = HN { -- | Host name as a 'ByteString'. hostName ∷ ByteString } deriving (Typeable, Eq, Ord, Hashable) -- | 'HostName' proxy value. aHostName ∷ Proxy HostName aHostName = Proxy instance Show HostName where showsPrec p (HN bs) = showParen (p > 10) $ showString "fromJust " . (showParen True $ showString "fromString " . showsPrec 10 (BS8.unpack bs)) instance Read HostName where readPrec = TR.parens $ TR.prec 10 $ do TR.Ident "fromJust" ← TR.lexP TR.step $ TR.parens $ TR.prec 10 $ do TR.Ident "fromString" ← TR.lexP TR.String s ← TR.lexP Just n ← return $ T.fromString s return n instance Printable HostName where print (HN bs) = T.ascii bs {-# RULES "toAscii/HostName" toAscii = hostName #-} {-# RULES "toUtf8/HostName" toUtf8 = hostName #-} instance Textual HostName where textual = go [] (0 ∷ Int) False [] (0 ∷ Int) "host name" where alphaNumOrDashOrDot c = A.isAlphaNum c || c == '-' || c == '.' go !ls !ncs _ _ 0 = optional (P.satisfy A.isAlpha) >>= \case Just c → if ncs == 255 then P.unexpected "Host name is too long" else go ls (ncs + 1) False [A.ascii c] 1 Nothing → P.unexpected "A letter expected" go !ls !ncs !dash !lcs !nlcs = optional (P.satisfy alphaNumOrDashOrDot) >>= \case Just '.' → if dash then P.unexpected "Label ends with a dash" else if ncs == 255 then P.unexpected "Host name is too long" else go (reverse (A.ascii '.' : lcs) : ls) (ncs + 1) False [] 0 Just c → if nlcs == 63 then P.unexpected "Label is too long" else if ncs == 255 then P.unexpected "Host name is too long" else go ls (ncs + 1) (c == '-') (A.ascii c : lcs) (nlcs + 1) Nothing → return $ HN $ BS.pack $ concat $ reverse $ reverse lcs : ls instance Printable (InetAddr HostName) where print (InetAddr n p) = T.print n <> T.char7 ':' <> T.print p instance Textual (InetAddr HostName) where textual = InetAddr <$> T.textual <*> (P.char ':' *> T.textual) -- | List the 'HostName' labels: -- -- @ -- 'hostNameLabels' ('Data.Maybe.fromJust' ('Data.Textual.fromString' /"www.google.com"/)) = [/"www"/, /"google"/, /"com"/] -- @ hostNameLabels ∷ HostName → [ByteString] hostNameLabels = BS.split (A.ascii '.') . hostName -- | Host name for reverse DNS lookups. -- -- @ -- 'Text.Printer.toString' ('arpaHostName' ('IPv4' ('ip4FromOctets' /1/ /2/ /3/ /4/))) = /"4.3.2.1.in-addr.arpa"/ -- 'Text.Printer.toString' ('arpaHostName' ('IPv6' ('ip6FromWords' /1/ /2/ /3/ /4/ /5/ /6/ /7/ /8/))) = /"8.0.0.0.7.0.0.0.6.0.0.0.5.0.0.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.ip6.arpa"/ -- @ arpaHostName ∷ IP → HostName arpaHostName (IPv4 a) = HN $ BS8.pack $ printf "%i.%i.%i.%i.in-addr.arpa" o4 o3 o2 o1 where (o1, o2, o3, o4) = ip4ToOctets a arpaHostName (IPv6 a) = HN $ BS8.pack $ digits (reverse $ ip6ToWordList a) ++ "ip6.arpa" where digits (w : ws) = [d4, '.', d3, '.', d2, '.', d1, '.'] ++ digits ws where d1 = toDigit $ w `shiftR` 12 d2 = toDigit $ w `shiftR` 8 .&. 0xF d3 = toDigit $ w `shiftR` 4 .&. 0xF d4 = toDigit $ w .&. 0xF toDigit n | n < 10 = chr $ ord '0' + fromIntegral n | otherwise = chr $ ord 'a' + fromIntegral n - 10 digits [] = [] newtype StateT k v μ α = StateT { runStateT ∷ Map k v → Maybe Word16 → μ (Map k v, Maybe Word16, α) } type CompT μ α = StateT [ByteString] Word16 μ α type DecompT μ α = StateT Word16 HostName μ α compress ∷ Monad μ ⇒ Word16 → CompT μ α → μ α compress i m = do (_, _, x) ← runStateT m Map.empty $ Just i return x {-# INLINE compress #-} decompress ∷ Monad μ ⇒ Word16 → DecompT μ α → μ α decompress i m = do (_, _, x) ← runStateT m Map.empty $ Just i return x {-# INLINE decompress #-} instance Monad μ ⇒ Functor (StateT k v μ) where fmap f m = StateT $ \ptrs offset → do (ptrs', offset', x) ← runStateT m ptrs offset return (ptrs', offset', f x) {-# INLINE fmap #-} instance Monad μ ⇒ Applicative (StateT k v μ) where pure = return {-# INLINE pure #-} (<*>) = ap {-# INLINE (<*>) #-} instance Monad μ ⇒ Monad (StateT k v μ) where return = lift . return {-# INLINE return #-} m >>= f = StateT $ \ptrs offset → do (ptrs', offset', x) ← runStateT m ptrs offset runStateT (f x) ptrs' offset' {-# INLINE (>>=) #-} fail msg = lift $ fail msg {-# INLINE fail #-} lift ∷ Monad μ ⇒ μ α → StateT k v μ α lift m = StateT $ \ptrs offset → do x ← m return (ptrs, offset, x) {-# INLINE lift #-} getOffset ∷ Monad μ ⇒ StateT k v μ (Maybe Word16) getOffset = StateT $ \ptrs offset → return (ptrs, offset, offset) {-# INLINE getOffset #-} incOffset ∷ Monad μ ⇒ Word16 → StateT k v μ () incOffset n = StateT $ \ptrs offset → do let offset' = case offset of Just i | i' ← i + n, i' >= i && i' <= 0x3FFF → Just i' _ → Nothing return (ptrs, offset', ()) {-# INLINE incOffset #-} getEntries ∷ Monad μ ⇒ StateT k v μ (Map k v) getEntries = StateT $ \ptrs offset → return (ptrs, offset, ptrs) {-# INLINE getEntries #-} getEntry ∷ (Ord k, Monad μ) ⇒ k → StateT k v μ (Maybe v) getEntry key = StateT $ \ptrs offset → do return (ptrs, offset, Map.lookup key ptrs) {-# INLINE getEntry #-} putEntry ∷ (Ord k, Monad μ) ⇒ k → v → StateT k v μ () putEntry key value = StateT $ \ptrs offset → do return (Map.insert key value ptrs, offset, ()) {-# INLINE putEntry #-} evalComp ∷ Monad μ ⇒ (∀ α . μ α → (α, ByteString)) → CompT μ () → CompT μ ByteString evalComp run m = StateT $ \ptrs offset → do let ((ptrs', offset', _), bs) = run $ runStateT m ptrs offset return (ptrs', offset', bs) {-# INLINE evalComp #-} threadDecomp ∷ (∀ β . μ β → μ β) → DecompT μ α → DecompT μ α threadDecomp f m = StateT $ \ptrs offset → f $ runStateT m ptrs offset {-# INLINE threadDecomp #-} class (Functor (GetM s), Monad (GetM s), Functor (PutM s), Monad (PutM s)) ⇒ Serializer s where type GetM s ∷ ★ → ★ type PutM s ∷ ★ → ★ putWord8 ∷ s → Word8 → PutM s () putWord16be ∷ s → Word16 → PutM s () putWord32be ∷ s → Word32 → PutM s () putIP4 ∷ s → IP4 → PutM s () putIP6 ∷ s → IP6 → PutM s () putByteString ∷ s → ByteString → PutM s () runPutM ∷ s → PutM s α → (α, ByteString) getWord8 ∷ s → GetM s Word8 getWord16be ∷ s → GetM s Word16 getWord32be ∷ s → GetM s Word32 getIP4 ∷ s → GetM s IP4 getIP6 ∷ s → GetM s IP6 getByteString ∷ s → Int → GetM s ByteString isolate ∷ s → Int → GetM s α → GetM s α data BinarySerializer = BinarySerializer instance Serializer BinarySerializer where type GetM BinarySerializer = B.Get type PutM BinarySerializer = B.PutM putWord8 _ = B.putWord8 putWord16be _ = B.putWord16be putWord32be _ = B.putWord32be putIP4 _ = B.put putIP6 _ = B.put putByteString _ = B.putByteString runPutM _ p = (r, BSL.toStrict bs) where (r, bs) = B.runPutM p getWord8 _ = B.getWord8 getWord16be _ = B.getWord16be getWord32be _ = B.getWord32be getIP4 _ = B.get getIP6 _ = B.get getByteString _ = B.getBytes isolate _ = undefined data CerealSerializer = CerealSerializer instance Serializer CerealSerializer where type GetM CerealSerializer = S.Get type PutM CerealSerializer = S.PutM putWord8 _ = S.putWord8 putWord16be _ = S.putWord16be putWord32be _ = S.putWord32be putIP4 _ = S.put putIP6 _ = S.put putByteString _ = S.putByteString runPutM _ = S.runPutM getWord8 _ = S.getWord8 getWord16be _ = S.getWord16be getWord32be _ = S.getWord32be getIP4 _ = S.get getIP6 _ = S.get getByteString _ = S.getBytes isolate _ = S.isolate serializeHostName ∷ Serializer s ⇒ s → HostName → CompT (PutM s) () serializeHostName s = go . hostNameLabels where go [] = do lift $ putWord8 s 0 incOffset 1 go labels@(label : labels') = do entry ← getEntry labels case entry of Nothing → do let ll = BS.length label offset ← getOffset lift $ putWord8 s $ fromIntegral ll lift $ putByteString s label incOffset $ 1 + fromIntegral ll forM_ offset $ putEntry labels go labels' Just ptr → do lift $ putWord16be s $ 0xC000 .|. ptr incOffset 2 guard' ∷ Monad μ ⇒ String → Bool → μ () guard' msg test = unless test $ fail msg {-# INLINE guard' #-} deserializeHostName ∷ Serializer s ⇒ s → DecompT (GetM s) HostName deserializeHostName s = go [] where folder suffix (label, offset) = do forM_ offset $ \i → putEntry i (HN suffix') return suffix' where suffix' = BS.append label $ BS.cons (A.ascii '.') suffix go labels = do offset ← getOffset w ← lift $ getWord8 s incOffset 1 if w .&. 0xC0 == 0xC0 then do w' ← lift $ getWord8 s incOffset 1 let ptr = fromIntegral (w .&. 0x3F) `shiftL` 8 .|. fromIntegral w' entry ← getEntry ptr case entry of Nothing → do entries ← getEntries fail $ "Invalid pointer " ++ show ptr ++ ": pointer map is " ++ show (Map.elems entries) Just (HN suffix1) → HN <$> foldM folder suffix1 labels else if w == 0 then do guard' "Hostname with zero labels" $ not $ null labels let (lastLabel, lastOffset) : labels' = labels forM_ lastOffset $ \i → putEntry i (HN lastLabel) HN <$> foldM folder lastLabel labels' else do guard' "Label is too long" $ w <= 63 label ← lift $ getByteString s $ fromIntegral w incOffset $ fromIntegral w go ((BS.map A.toLower8 label, offset) : labels) -- | Host address. Either a host name or an IP address. data HostAddr a = HostName {-# UNPACK #-} !HostName | HostAddr !a deriving (Typeable, Show, Read, Eq, Ord) type Host4Addr = HostAddr IP4 type Host6Addr = HostAddr IP6 -- | 'HostAddr' proxy value. aHostAddr ∷ Proxy HostAddr aHostAddr = Proxy -- | 'HostAddr' /a/ proxy value. aHostAddrOf ∷ Proxy a → Proxy (HostAddr a) aHostAddrOf _ = Proxy -- | 'Host4Addr' proxy value. aHost4Addr ∷ Proxy Host4Addr aHost4Addr = Proxy -- | 'Host6Addr' proxy value. aHost6Addr ∷ Proxy Host6Addr aHost6Addr = Proxy -- | 'HostAddr' 'IP' proxy value. aHostAddrIP ∷ Proxy (HostAddr IP) aHostAddrIP = Proxy instance Printable a ⇒ Printable (HostAddr a) where print (HostName name) = T.print name print (HostAddr addr) = T.print addr instance Textual a ⇒ Textual (HostAddr a) where textual = P.try (HostName <$> T.textual) <|> (HostAddr <$> T.textual) instance Printable (InetAddr a) ⇒ Printable (InetAddr (HostAddr a)) where print (InetAddr (HostName n) p) = T.print $ InetAddr n p print (InetAddr (HostAddr a) p) = T.print $ InetAddr a p instance Textual (InetAddr a) ⇒ Textual (InetAddr (HostAddr a)) where textual = P.try (InetAddr <$> (HostName <$> T.textual) <*> (P.char ':' *> T.textual)) <|> T.textual -- | Message identifier. type DnsId = Word16 -- | Resource Record type. data DnsType α where -- IPv4 address record (/A/) AddrDnsType ∷ DnsType IP4 -- IPv6 address record (/AAAA/) Addr6DnsType ∷ DnsType IP6 -- Name server record (/NS/) NsDnsType ∷ DnsType HostName -- Canonical name record (/CNAME/) CNameDnsType ∷ DnsType HostName -- Pointer record (/PTR/) PtrDnsType ∷ DnsType HostName -- Mail exchange record (/MX/) MxDnsType ∷ DnsType (Word16, HostName) deriving instance Typeable1 DnsType deriving instance Eq (DnsType α) instance Show (DnsType α) where showsPrec _ AddrDnsType = showString "AddrDnsType" showsPrec _ Addr6DnsType = showString "Addr6DnsType" showsPrec _ NsDnsType = showString "NsDnsType" showsPrec _ CNameDnsType = showString "CNameDnsType" showsPrec _ PtrDnsType = showString "PtrDnsType" showsPrec _ MxDnsType = showString "MxDnsType" -- | Numeric representation of a Resource Record type. dnsTypeCode ∷ DnsType α → Word16 dnsTypeCode AddrDnsType = 1 dnsTypeCode Addr6DnsType = 28 dnsTypeCode NsDnsType = 2 dnsTypeCode CNameDnsType = 5 dnsTypeCode PtrDnsType = 12 dnsTypeCode MxDnsType = 15 -- | Resource Record data. data DnsData = ∀ α . DnsData { dnsType ∷ !(DnsType α) -- ^ The type , dnsData ∷ α -- ^ The data } deriving Typeable instance Show DnsData where showsPrec p (DnsData {..}) = showParen (p > 10) $ showString "DnsData {dnsType = " . showsPrec (p + 1) dnsType . showString ", dnsData = " . case dnsType of AddrDnsType → showsPrec p' dnsData Addr6DnsType → showsPrec p' dnsData NsDnsType → showsPrec p' dnsData CNameDnsType → showsPrec p' dnsData PtrDnsType → showsPrec p' dnsData MxDnsType → showsPrec p' dnsData . showString "}" where p' = 10 ∷ Int -- | Resource Record. data DnsRecord = DnsRecord { -- | Record owner dnsRecOwner ∷ {-# UNPACK #-} !HostName , -- | Maximum caching time in secords dnsRecTtl ∷ {-# UNPACK #-} !Word32 , -- | Record data dnsRecData ∷ !DnsData } deriving (Typeable, Show) serializeDnsRecord ∷ Serializer s ⇒ s → DnsRecord → CompT (PutM s) () serializeDnsRecord s (DnsRecord {..}) | DnsData tp dt ← dnsRecData = do serializeHostName s dnsRecOwner lift $ putWord16be s $ dnsTypeCode tp lift $ putWord16be s 1 lift $ putWord32be s dnsRecTtl incOffset 10 d ← evalComp (runPutM s) $ case tp of AddrDnsType → lift (putIP4 s dt) >> incOffset 4 Addr6DnsType → lift (putIP6 s dt) >> incOffset 16 NsDnsType → serializeHostName s dt CNameDnsType → serializeHostName s dt PtrDnsType → serializeHostName s dt MxDnsType → do lift $ putWord16be s $ fst dt incOffset 2 serializeHostName s $ snd dt lift $ putWord16be s $ fromIntegral $ BS.length d lift $ putByteString s d deserializeDnsRecord ∷ Serializer s ⇒ s → DecompT (GetM s) DnsRecord deserializeDnsRecord s = do owner ← deserializeHostName s code ← lift $ getWord16be s void $ lift $ getWord16be s ttl ← lift $ getWord32be s len ← lift $ fromIntegral <$> getWord16be s incOffset 10 dd ← threadDecomp (isolate s len) $ case code of 1 → fmap (DnsData AddrDnsType) $ incOffset 4 >> lift (getIP4 s) 2 → DnsData NsDnsType <$> deserializeHostName s 5 → DnsData CNameDnsType <$> deserializeHostName s 12 → DnsData PtrDnsType <$> deserializeHostName s 28 → fmap (DnsData Addr6DnsType) $ incOffset 16 >> lift (getIP6 s) _ → fail "Unsupported type" return $ DnsRecord owner ttl dd -- | DNS query type. data DnsQType = ∀ α . StdDnsType (DnsType α) -- ^ Record type | AllDnsType -- ^ All record types deriving Typeable instance Show DnsQType where showsPrec p (StdDnsType t) = showParen (p > 10) $ showString "StdDnsType " . showsPrec (p + 1) t showsPrec _ AllDnsType = showString "AllDnsType" -- | Numeric representation of a DNS query type. dnsQTypeCode ∷ DnsQType → Word16 dnsQTypeCode (StdDnsType t) = dnsTypeCode t dnsQTypeCode AllDnsType = 255 instance Eq DnsQType where t1 == t2 = dnsQTypeCode t1 == dnsQTypeCode t2 instance Ord DnsQType where t1 `compare` t2 = dnsQTypeCode t1 `compare` dnsQTypeCode t2 putDnsQType ∷ Serializer s ⇒ s → DnsQType → PutM s () putDnsQType s = putWord16be s . dnsQTypeCode getDnsQType ∷ Serializer s ⇒ s → GetM s DnsQType getDnsQType s = getWord16be s >>= \case 1 → return $ StdDnsType AddrDnsType 2 → return $ StdDnsType NsDnsType 5 → return $ StdDnsType CNameDnsType 12 → return $ StdDnsType PtrDnsType 28 → return $ StdDnsType Addr6DnsType 255 → return AllDnsType _ → fail "Unsupported query type" instance Binary DnsQType where put = putDnsQType BinarySerializer get = getDnsQType BinarySerializer instance Serialize DnsQType where put = putDnsQType CerealSerializer get = getDnsQType CerealSerializer -- | DNS question. data DnsQuestion = DnsQuestion { -- | Ask about the specified host name dnsQName ∷ {-# UNPACK #-} !HostName , -- | Query type dnsQType ∷ !DnsQType } deriving (Typeable, Show, Eq, Ord) serializeDnsQuestion ∷ Serializer s ⇒ s → DnsQuestion → CompT (PutM s) () serializeDnsQuestion s (DnsQuestion {..}) = do serializeHostName s dnsQName lift $ do putDnsQType s dnsQType putWord16be s 1 incOffset 4 deserializeDnsQuestion ∷ Serializer s ⇒ s → DecompT (GetM s) DnsQuestion deserializeDnsQuestion s = do q ← DnsQuestion <$> deserializeHostName s <*> lift (getDnsQType s) c ← lift $ getWord16be s guard' "Unsupported class in a question" $ c == 1 incOffset 4 return q -- | Request message. data DnsReq -- | Standard query = DnsReq { -- | Message identifier dnsReqId ∷ {-# UNPACK #-} !DnsId , -- | Truncation flag dnsReqTruncd ∷ !Bool , -- | Recursion flag dnsReqRec ∷ !Bool , -- | Question dnsReqQuestion ∷ {-# UNPACK #-} !DnsQuestion } -- | Inverse query | DnsInvReq { dnsReqId ∷ {-# UNPACK #-} !DnsId , -- | IP address dnsReqInv ∷ !IP } deriving (Typeable, Show) anyHostName ∷ HostName anyHostName = HN "any" putDnsReq ∷ Serializer s ⇒ s → DnsReq → PutM s () putDnsReq s (DnsReq {..}) = do putWord16be s dnsReqId putWord8 s $ if dnsReqRec then 1 else 0 .|. if dnsReqTruncd then 2 else 0 putWord8 s 0 putWord16be s 1 putWord16be s 0 putWord16be s 0 putWord16be s 0 compress 12 $ serializeDnsQuestion s dnsReqQuestion putDnsReq s (DnsInvReq {..}) = do putWord16be s dnsReqId putWord8 s 8 putWord8 s 0 putWord16be s 0 putWord16be s 1 putWord16be s 0 putWord16be s 0 compress 12 $ serializeDnsRecord s $ DnsRecord { dnsRecOwner = anyHostName , dnsRecTtl = 0 , dnsRecData = case dnsReqInv of IPv4 a → DnsData AddrDnsType a IPv6 a → DnsData Addr6DnsType a } getDnsReq ∷ Serializer s ⇒ s → GetM s DnsReq getDnsReq s = do i ← getWord16be s w ← getWord8 s void $ getWord8 s guard' "Not a request" $ w .&. 128 == 0 let rec = w .&. 1 /= 0 truncd = w .&. 2 /= 0 opcode = w `shiftR` 3 .&. 0xF case opcode of 0 → do getWord16be s >>= guard' "No questions in query" . (== 1) getWord16be s >>= guard' "Answers in query" . (== 0) getWord16be s >>= guard' "Authorities in query" . (== 0) getWord16be s >>= guard' "Extras in query" . (== 0) decompress 12 $ do q ← deserializeDnsQuestion s return $ DnsReq { dnsReqId = i , dnsReqTruncd = truncd , dnsReqRec = rec , dnsReqQuestion = q } 1 → do getWord16be s >>= guard' "Questions in inverse query" . (== 0) getWord16be s >>= guard' "No answers in inverse query" . (== 1) getWord16be s >>= guard' "Authorities in inverse query" . (== 0) getWord16be s >>= guard' "Extras in inverse query" . (== 0) DnsRecord {dnsRecData} ← decompress 12 $ deserializeDnsRecord s case dnsRecData of DnsData AddrDnsType a → return $ DnsInvReq { dnsReqId = i, dnsReqInv = IPv4 a } DnsData Addr6DnsType a → return $ DnsInvReq { dnsReqId = i, dnsReqInv = IPv6 a } _ → fail "Invalid answer RR in inverse query" _ → fail "Invalid opcode in request" instance Binary DnsReq where put = putDnsReq BinarySerializer get = getDnsReq BinarySerializer instance Serialize DnsReq where put = putDnsReq CerealSerializer get = getDnsReq CerealSerializer -- | Errors returned in responses. data DnsError = FormatDnsError | FailureDnsError | NoNameDnsError | NotImplDnsError | RefusedDnsError | NameExistsDnsError | RsExistsDnsError | NoRsDnsError | NotAuthDnsError | NotInZoneDnsError deriving (Typeable, Show, Read, Eq, Ord, Enum) -- | Numerical representation of an error. dnsErrorCode ∷ DnsError → Word8 dnsErrorCode FormatDnsError = 1 dnsErrorCode FailureDnsError = 2 dnsErrorCode NoNameDnsError = 3 dnsErrorCode NotImplDnsError = 4 dnsErrorCode RefusedDnsError = 5 dnsErrorCode NameExistsDnsError = 6 dnsErrorCode RsExistsDnsError = 7 dnsErrorCode NoRsDnsError = 8 dnsErrorCode NotAuthDnsError = 9 dnsErrorCode NotInZoneDnsError = 10 -- | Response message. data DnsResp -- | Normal response. = DnsResp { -- | Request identifer dnsRespId ∷ {-# UNPACK #-} !DnsId , -- | Truncation flag dnsRespTruncd ∷ !Bool , -- | Authoritative answer flag dnsRespAuthd ∷ !Bool , -- | Recursive query support flag dnsRespRec ∷ !Bool , -- | Request question dnsRespQuestion ∷ {-# UNPACK #-} !DnsQuestion , -- | Answer records dnsRespAnswers ∷ [DnsRecord] , -- | Authority records dnsRespAuths ∷ [DnsRecord] , -- | Additional records dnsRespExtras ∷ [DnsRecord] } -- | Error response. | DnsErrResp { dnsRespId ∷ {-# UNPACK #-} !DnsId , -- | Error dnsRespError ∷ !DnsError } deriving (Typeable, Show) putDnsResp ∷ Serializer s ⇒ s → DnsResp → PutM s () putDnsResp s (DnsResp {..}) = do putWord16be s dnsRespId putWord8 s $ 128 .|. if dnsRespTruncd then 2 else 0 .|. if dnsRespAuthd then 4 else 0 putWord8 s $ if dnsRespRec then 128 else 0 putWord16be s 1 putWord16be s $ fromIntegral $ length dnsRespAnswers putWord16be s $ fromIntegral $ length dnsRespAuths putWord16be s $ fromIntegral $ length dnsRespExtras compress 12 $ do serializeDnsQuestion s dnsRespQuestion forM_ dnsRespAnswers (serializeDnsRecord s) forM_ dnsRespAuths (serializeDnsRecord s) forM_ dnsRespExtras (serializeDnsRecord s) putDnsResp s (DnsErrResp {..}) = do putWord16be s dnsRespId putWord8 s 8 putWord8 s $ dnsErrorCode dnsRespError putWord16be s 0 putWord16be s 0 putWord16be s 0 putWord16be s 0 getDnsResp ∷ Serializer s ⇒ s → GetM s DnsResp getDnsResp s = do i ← getWord16be s w ← getWord8 s guard' "Not a response" $ w .&. 128 /= 0 w' ← getWord8 s let truncd = w .&. 2 /= 0 authd = w .&. 4 /= 0 rec = w' .&. 128 /= 0 ec = w' .&. 0xF case ec of 0 → do getWord16be s >>= guard' "No question in a response" . (== 1) anc ← getWord16be s nsc ← getWord16be s arc ← getWord16be s decompress 12 $ do q ← deserializeDnsQuestion s ans ← mapM (const $ deserializeDnsRecord s) [1 .. anc] nss ← mapM (const $ deserializeDnsRecord s) [1 .. nsc] ars ← mapM (const $ deserializeDnsRecord s) [1 .. arc] return $ DnsResp { dnsRespId = i , dnsRespTruncd = truncd , dnsRespAuthd = authd , dnsRespRec = rec , dnsRespQuestion = q , dnsRespAnswers = ans , dnsRespAuths = nss , dnsRespExtras = ars } _ → do void $ getWord16be s void $ getWord16be s void $ getWord16be s void $ getWord16be s DnsErrResp i <$> case ec of 1 → return FormatDnsError 2 → return FailureDnsError 3 → return NoNameDnsError 4 → return NotImplDnsError 5 → return RefusedDnsError 6 → return NameExistsDnsError 7 → return RsExistsDnsError 8 → return NoRsDnsError 9 → return NotAuthDnsError 10 → return NotInZoneDnsError _ → fail "Unknown error code in a response" instance Binary DnsResp where put = putDnsResp BinarySerializer get = getDnsResp BinarySerializer instance Serialize DnsResp where put = putDnsResp CerealSerializer get = getDnsResp CerealSerializer