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)
newtype HostName = HN {
hostName ∷ ByteString
}
deriving (Typeable, Eq, Ord, Hashable)
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
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)
hostNameLabels ∷ HostName → [ByteString]
hostNameLabels = BS.split (A.ascii '.') . hostName
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
decompress ∷ Monad μ ⇒ Word16 → DecompT μ α → μ α
decompress i m = do
(_, _, x) ← runStateT m Map.empty $ Just i
return x
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)
instance Monad μ ⇒ Applicative (StateT k v μ) where
pure = return
(<*>) = ap
instance Monad μ ⇒ Monad (StateT k v μ) where
return = lift . return
m >>= f = StateT $ \ptrs offset → do
(ptrs', offset', x) ← runStateT m ptrs offset
runStateT (f x) ptrs' offset'
fail msg = lift $ fail msg
lift ∷ Monad μ ⇒ μ α → StateT k v μ α
lift m = StateT $ \ptrs offset → do
x ← m
return (ptrs, offset, x)
getOffset ∷ Monad μ ⇒ StateT k v μ (Maybe Word16)
getOffset = StateT $ \ptrs offset → return (ptrs, offset, offset)
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', ())
getEntries ∷ Monad μ ⇒ StateT k v μ (Map k v)
getEntries = StateT $ \ptrs offset → return (ptrs, offset, ptrs)
getEntry ∷ (Ord k, Monad μ) ⇒ k → StateT k v μ (Maybe v)
getEntry key = StateT $ \ptrs offset → do
return (ptrs, offset, Map.lookup key ptrs)
putEntry ∷ (Ord k, Monad μ) ⇒ k → v → StateT k v μ ()
putEntry key value = StateT $ \ptrs offset → do
return (Map.insert key value ptrs, offset, ())
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)
threadDecomp ∷ (∀ β . μ β → μ β) → DecompT μ α → DecompT μ α
threadDecomp f m = StateT $ \ptrs offset →
f $ runStateT m ptrs offset
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
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)
data HostAddr a = HostName !HostName
| HostAddr !a
deriving (Typeable, Show, Read, Eq, Ord)
type Host4Addr = HostAddr IP4
type Host6Addr = HostAddr IP6
aHostAddr ∷ Proxy HostAddr
aHostAddr = Proxy
aHostAddrOf ∷ Proxy a → Proxy (HostAddr a)
aHostAddrOf _ = Proxy
aHost4Addr ∷ Proxy Host4Addr
aHost4Addr = Proxy
aHost6Addr ∷ Proxy Host6Addr
aHost6Addr = Proxy
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
type DnsId = Word16
data DnsType α where
AddrDnsType ∷ DnsType IP4
Addr6DnsType ∷ DnsType IP6
NsDnsType ∷ DnsType HostName
CNameDnsType ∷ DnsType HostName
PtrDnsType ∷ DnsType HostName
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"
dnsTypeCode ∷ DnsType α → Word16
dnsTypeCode AddrDnsType = 1
dnsTypeCode Addr6DnsType = 28
dnsTypeCode NsDnsType = 2
dnsTypeCode CNameDnsType = 5
dnsTypeCode PtrDnsType = 12
dnsTypeCode MxDnsType = 15
data DnsData = ∀ α . DnsData { dnsType ∷ !(DnsType α)
, dnsData ∷ α
}
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
data DnsRecord = DnsRecord {
dnsRecOwner ∷ !HostName
,
dnsRecTtl ∷ !Word32
,
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
data DnsQType = ∀ α . StdDnsType (DnsType α)
| AllDnsType
deriving Typeable
instance Show DnsQType where
showsPrec p (StdDnsType t) = showParen (p > 10)
$ showString "StdDnsType "
. showsPrec (p + 1) t
showsPrec _ AllDnsType = showString "AllDnsType"
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
data DnsQuestion = DnsQuestion {
dnsQName ∷ !HostName
,
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
data DnsReq
= DnsReq {
dnsReqId ∷ !DnsId
,
dnsReqTruncd ∷ !Bool
,
dnsReqRec ∷ !Bool
,
dnsReqQuestion ∷ !DnsQuestion
}
| DnsInvReq { dnsReqId ∷ !DnsId
,
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
data DnsError = FormatDnsError
| FailureDnsError
| NoNameDnsError
| NotImplDnsError
| RefusedDnsError
| NameExistsDnsError
| RsExistsDnsError
| NoRsDnsError
| NotAuthDnsError
| NotInZoneDnsError
deriving (Typeable, Show, Read, Eq, Ord, Enum)
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
data DnsResp
= DnsResp {
dnsRespId ∷ !DnsId
,
dnsRespTruncd ∷ !Bool
,
dnsRespAuthd ∷ !Bool
,
dnsRespRec ∷ !Bool
,
dnsRespQuestion ∷ !DnsQuestion
,
dnsRespAnswers ∷ [DnsRecord]
,
dnsRespAuths ∷ [DnsRecord]
,
dnsRespExtras ∷ [DnsRecord]
}
| DnsErrResp { dnsRespId ∷ !DnsId
,
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