1 module Network.DNS.Message
45 import Control.Exception
48 import Data.Binary.BitPut as BP
49 import Data.Binary.Get as G
50 import Data.Binary.Put as P'
51 import Data.Binary.Strict.BitGet as BG
52 import qualified Data.ByteString as BS
53 import qualified Data.ByteString.Char8 as C8 hiding (ByteString)
55 import qualified Data.IntMap as IM
56 import Data.IntMap (IntMap)
57 import qualified Data.Map as M
60 import Network.DNS.Packer as P
61 import Network.DNS.Unpacker as U
68 , msgQuestions :: ![Question]
69 , msgAnswers :: ![SomeRR]
70 , msgAuthorities :: ![SomeRR]
71 , msgAdditionals :: ![SomeRR]
77 hdMessageID :: !MessageID
78 , hdMessageType :: !MessageType
80 , hdIsAuthoritativeAnswer :: !Bool
81 , hdIsTruncated :: !Bool
82 , hdIsRecursionDesired :: !Bool
83 , hdIsRecursionAvailable :: !Bool
84 , hdResponseCode :: !ResponseCode
86 -- These fields are supressed in this data structure:
94 type MessageID = Word16
104 | ServerStatusRequest
120 , qClass :: !RecordClass
126 putQ :: Question -> Packer CompTable ()
128 = do putDomainName $ qName q
132 getQ :: Unpacker DecompTable Question
133 getQ = do nm <- getDomainName
143 newtype DomainName = DN [DomainLabel] deriving (Eq, Show, Ord, Typeable)
144 type DomainLabel = BS.ByteString
146 rootName :: DomainName
147 rootName = DN [BS.empty]
149 isRootName :: DomainName -> Bool
150 isRootName (DN [_]) = True
153 consLabel :: DomainLabel -> DomainName -> DomainName
154 consLabel x (DN ys) = DN (x:ys)
156 unconsLabel :: DomainName -> (DomainLabel, DomainName)
157 unconsLabel (DN (x:xs)) = (x, DN xs)
158 unconsLabel x = error ("Illegal use of unconsLabel: " ++ show x)
160 mkDomainName :: String -> DomainName
161 mkDomainName = DN . mkLabels [] . notEmpty
163 notEmpty :: String -> String
164 notEmpty xs = assert (not $ null xs) xs
166 mkLabels :: [DomainLabel] -> String -> [DomainLabel]
167 mkLabels soFar [] = reverse (C8.empty : soFar)
168 mkLabels soFar xs = case break (== '.') xs of
170 -> mkLabels (C8.pack l : soFar) rest
171 _ -> error ("Illegal domain name: " ++ xs)
178 | AnyClass -- Only for queries
182 data RecordType rt dt => ResourceRecord rt dt
184 rrName :: !DomainName
186 , rrClass :: !RecordClass
190 deriving (Show, Eq, Typeable)
193 data SomeRR = forall rt dt. RecordType rt dt => SomeRR (ResourceRecord rt dt)
195 instance Show SomeRR where
196 show (SomeRR rr) = show rr
198 instance Eq SomeRR where
199 (SomeRR a) == (SomeRR b) = Just a == cast b
202 putSomeRR :: SomeRR -> Packer CompTable ()
203 putSomeRR (SomeRR rr) = putResourceRecord rr
205 getSomeRR :: Unpacker DecompTable SomeRR
206 getSomeRR = do srt <- U.lookAhead $
207 do getDomainName -- skip
211 -> getResourceRecord rt >>= return . SomeRR
213 type CompTable = Map DomainName Int
214 type DecompTable = IntMap DomainName
217 getDomainName :: Unpacker DecompTable DomainName
218 getDomainName = worker
220 worker :: Unpacker DecompTable DomainName
222 = do offset <- U.bytesRead
223 hdr <- getLabelHeader
226 -> do dt <- U.getState
227 case IM.lookup n dt of
231 -> fail ("Illegal offset of label pointer: " ++ show (n, dt))
235 -> do label <- U.getByteString n
237 let name = consLabel label rest
238 U.modifyState $ IM.insert offset name
241 getLabelHeader :: Unpacker s LabelHeader
243 = do header <- U.lookAhead $ U.getByteString 1
248 n <- liftM fromIntegral (getAsWord8 6)
250 ( True, True) -> return $ Offset n
251 (False, False) -> return $ Length n
252 _ -> fail "Illegal label header"
255 -> do header' <- U.getByteString 2 -- Pointers have 2 octets.
257 = runBitGet header' $
259 n <- liftM fromIntegral (getAsWord16 14)
267 getCharString :: Unpacker s BS.ByteString
268 getCharString = do len <- U.getWord8
269 U.getByteString (fromIntegral len)
271 putCharString :: BS.ByteString -> Packer s ()
272 putCharString xs = do P.putWord8 $ fromIntegral $ BS.length xs
279 putDomainName :: DomainName -> Packer CompTable ()
281 = do ct <- P.getState
282 case M.lookup name ct of
284 -> do let ptr = runBitPut $
288 P.putLazyByteString ptr
290 -> do offset <- bytesWrote
291 P.modifyState $ M.insert name offset
293 let (label, rest) = unconsLabel name
297 if isRootName rest then
303 class (Show rt, Show dt, Eq rt, Eq dt, Typeable rt, Typeable dt) => RecordType rt dt | rt -> dt where
305 putRecordData :: rt -> dt -> Packer CompTable ()
306 getRecordData :: rt -> Unpacker DecompTable dt
308 putRecordType :: rt -> Packer s ()
309 putRecordType = P.putWord16be . fromIntegral . rtToInt
311 putRecordDataWithLength :: rt -> dt -> Packer CompTable ()
312 putRecordDataWithLength rt dt
313 = do -- First, write a dummy data length.
317 -- Second, write data.
320 -- Third, rewrite the dummy length to an actual value.
321 offset' <- bytesWrote
323 $ P.putWord16be (fromIntegral (offset' - offset - 2))
325 putResourceRecord :: ResourceRecord rt dt -> Packer CompTable ()
327 = do putDomainName $ rrName rr
328 putRecordType $ rrType rr
329 putBinary $ rrClass rr
330 P.putWord32be $ rrTTL rr
331 putRecordDataWithLength (rrType rr) (rrData rr)
333 getRecordDataWithLength :: rt -> Unpacker DecompTable dt
334 getRecordDataWithLength rt
335 = do len <- U.getWord16be
336 offset <- U.bytesRead
337 dat <- getRecordData rt
338 offset' <- U.bytesRead
340 let consumed = offset' - offset
341 when (consumed /= len)
342 $ fail ("getRecordData " ++ show rt ++ " consumed " ++ show consumed ++
343 " bytes but it had to consume " ++ show len ++ " bytes")
347 getResourceRecord :: rt -> Unpacker DecompTable (ResourceRecord rt dt)
349 = do name <- getDomainName
350 U.skip 2 -- record type
353 dat <- getRecordDataWithLength rt
354 return $ ResourceRecord {
362 data SomeRT = forall rt dt. RecordType rt dt => SomeRT rt
364 instance Show SomeRT where
365 show (SomeRT rt) = show rt
367 instance Eq SomeRT where
368 (SomeRT a) == (SomeRT b) = Just a == cast b
370 putSomeRT :: SomeRT -> Packer s ()
371 putSomeRT (SomeRT rt) = putRecordType rt
373 getSomeRT :: Unpacker s SomeRT
374 getSomeRT = do n <- liftM fromIntegral U.getWord16be
375 case IM.lookup n defaultRTTable of
377 -> fail ("Unknown resource record type: " ++ show n)
383 soaMasterNameServer :: !DomainName
384 , soaResponsibleMailbox :: !DomainName
385 , soaSerialNumber :: !Word32
386 , soaRefreshInterval :: !Word32
387 , soaRetryInterval :: !Word32
388 , soaExpirationLimit :: !Word32
389 , soaMinimumTTL :: !Word32
391 deriving (Show, Eq, Typeable)
393 data A = A deriving (Show, Eq, Typeable)
394 instance RecordType A HostAddress where
396 putRecordData _ = P.putWord32be
397 getRecordData _ = U.getWord32be
399 data NS = NS deriving (Show, Eq, Typeable)
400 instance RecordType NS DomainName where
402 putRecordData _ = putDomainName
403 getRecordData _ = getDomainName
405 data MD = MD deriving (Show, Eq, Typeable)
406 instance RecordType MD DomainName where
408 putRecordData _ = putDomainName
409 getRecordData _ = getDomainName
411 data MF = MF deriving (Show, Eq, Typeable)
412 instance RecordType MF DomainName where
414 putRecordData _ = putDomainName
415 getRecordData _ = getDomainName
417 data CNAME = CNAME deriving (Show, Eq, Typeable)
418 instance RecordType CNAME DomainName where
420 putRecordData _ = putDomainName
421 getRecordData _ = getDomainName
423 data SOA = SOA deriving (Show, Eq, Typeable)
424 instance RecordType SOA SOAFields where
426 putRecordData _ = \ soa ->
427 do putDomainName $ soaMasterNameServer soa
428 putDomainName $ soaResponsibleMailbox soa
429 P.putWord32be $ soaSerialNumber soa
430 P.putWord32be $ soaRefreshInterval soa
431 P.putWord32be $ soaRetryInterval soa
432 P.putWord32be $ soaExpirationLimit soa
433 P.putWord32be $ soaMinimumTTL soa
434 getRecordData _ = do master <- getDomainName
435 mail <- getDomainName
436 serial <- U.getWord32be
437 refresh <- U.getWord32be
438 retry <- U.getWord32be
439 expire <- U.getWord32be
442 soaMasterNameServer = master
443 , soaResponsibleMailbox = mail
444 , soaSerialNumber = serial
445 , soaRefreshInterval = refresh
446 , soaRetryInterval = retry
447 , soaExpirationLimit = expire
448 , soaMinimumTTL = ttl
451 data MB = MB deriving (Show, Eq, Typeable)
452 instance RecordType MB DomainName where
454 putRecordData _ = putDomainName
455 getRecordData _ = getDomainName
457 data MG = MG deriving (Show, Eq, Typeable)
458 instance RecordType MG DomainName where
460 putRecordData _ = putDomainName
461 getRecordData _ = getDomainName
463 data MR = MR deriving (Show, Eq, Typeable)
464 instance RecordType MR DomainName where
466 putRecordData _ = putDomainName
467 getRecordData _ = getDomainName
469 data NULL = NULL deriving (Show, Eq, Typeable)
470 instance RecordType NULL BS.ByteString where
472 putRecordData _ _ = fail "putRecordData NULL can't be defined"
473 getRecordData _ = fail "getRecordData NULL can't be defined"
474 putRecordDataWithLength _ = \ dat ->
475 do P.putWord16be $ fromIntegral $ BS.length dat
477 getRecordDataWithLength _ = do len <- U.getWord16be
478 U.getByteString $ fromIntegral len
480 data PTR = PTR deriving (Show, Eq, Typeable)
481 instance RecordType PTR DomainName where
483 putRecordData _ = putDomainName
484 getRecordData _ = getDomainName
486 data HINFO = HINFO deriving (Show, Eq, Typeable)
487 instance RecordType HINFO (BS.ByteString, BS.ByteString) where
489 putRecordData _ = \ (cpu, os) ->
492 getRecordData _ = do cpu <- getCharString
496 data MINFO = MINFO deriving (Show, Eq, Typeable)
497 instance RecordType MINFO (DomainName, DomainName) where
499 putRecordData _ = \ (r, e) ->
502 getRecordData _ = do r <- getDomainName
506 data MX = MX deriving (Show, Eq, Typeable)
507 instance RecordType MX (Word16, DomainName) where
509 putRecordData _ = \ (pref, exch) ->
510 do P.putWord16be pref
512 getRecordData _ = do pref <- U.getWord16be
513 exch <- getDomainName
516 data TXT = TXT deriving (Show, Eq, Typeable)
517 instance RecordType TXT [BS.ByteString] where
519 putRecordData _ = mapM_ putCharString
520 getRecordData _ = fail "getRecordData TXT can't be defined"
522 getRecordDataWithLength _ = U.getWord16be >>= worker [] . fromIntegral
524 worker :: [BS.ByteString] -> Int -> Unpacker s [BS.ByteString]
525 worker soFar 0 = return (reverse soFar)
526 worker soFar n = do str <- getCharString
527 worker (str : soFar) (0 `max` n - 1 - BS.length str)
556 instance Binary Message where
557 put m = P.liftToBinary M.empty $
558 do putBinary $ msgHeader m
559 P.putWord16be $ fromIntegral $ length $ msgQuestions m
560 P.putWord16be $ fromIntegral $ length $ msgAnswers m
561 P.putWord16be $ fromIntegral $ length $ msgAuthorities m
562 P.putWord16be $ fromIntegral $ length $ msgAdditionals m
563 mapM_ putQ $ msgQuestions m
564 mapM_ putSomeRR $ msgAnswers m
565 mapM_ putSomeRR $ msgAuthorities m
566 mapM_ putSomeRR $ msgAdditionals m
568 get = U.liftToBinary IM.empty $
570 nQ <- liftM fromIntegral U.getWord16be
571 nAns <- liftM fromIntegral U.getWord16be
572 nAth <- liftM fromIntegral U.getWord16be
573 nAdd <- liftM fromIntegral U.getWord16be
574 qs <- replicateM nQ getQ
575 anss <- replicateM nAns getSomeRR
576 aths <- replicateM nAth getSomeRR
577 adds <- replicateM nAdd getSomeRR
582 , msgAuthorities = aths
583 , msgAdditionals = adds
586 instance Binary Header where
587 put h = do P'.putWord16be $ hdMessageID h
588 P'.putLazyByteString flags
591 do putNBits 1 $ fromEnum $ hdMessageType h
592 putNBits 4 $ fromEnum $ hdOpcode h
593 putBit $ hdIsAuthoritativeAnswer h
594 putBit $ hdIsTruncated h
595 putBit $ hdIsRecursionDesired h
596 putBit $ hdIsRecursionAvailable h
597 putNBits 3 (0 :: Int)
598 putNBits 4 $ fromEnum $ hdResponseCode h
600 get = do mID <- G.getWord16be
601 flags <- G.getByteString 2
604 do qr <- liftM (toEnum . fromIntegral) $ getAsWord8 1
605 op <- liftM (toEnum . fromIntegral) $ getAsWord8 4
611 rc <- liftM (toEnum . fromIntegral) $ getAsWord8 4
616 , hdIsAuthoritativeAnswer = aa
618 , hdIsRecursionDesired = rd
619 , hdIsRecursionAvailable = ra
620 , hdResponseCode = rc
624 instance Enum MessageType where
626 fromEnum Response = 1
632 instance Enum Opcode where
633 fromEnum StandardQuery = 0
634 fromEnum InverseQuery = 1
635 fromEnum ServerStatusRequest = 2
637 toEnum 0 = StandardQuery
638 toEnum 1 = InverseQuery
639 toEnum 2 = ServerStatusRequest
642 instance Enum ResponseCode where
644 fromEnum FormatError = 1
645 fromEnum ServerFailure = 2
646 fromEnum NameError = 3
647 fromEnum NotImplemented = 4
651 toEnum 1 = FormatError
652 toEnum 2 = ServerFailure
654 toEnum 4 = NotImplemented
659 instance Enum RecordType where
672 fromEnum HINFO = 13 /
673 fromEnum MINFO = 14 /
679 fromEnum AnyType = 255
682 instance Enum RecordClass where
687 fromEnum AnyClass = 255
693 toEnum 255 = AnyClass
696 instance Binary RecordClass where
697 get = liftM (toEnum . fromIntegral) G.getWord16be
698 put = P'.putWord16be . fromIntegral . fromEnum
701 defaultRTTable :: IntMap SomeRT
702 defaultRTTable = IM.fromList $ map toPair $
705 , wrapRecordType CNAME
706 , wrapRecordType HINFO
709 toPair :: SomeRT -> (Int, SomeRT)
710 toPair srt@(SomeRT rt) = (rtToInt rt, srt)
713 wrapQueryType :: RecordType rt dt => rt -> SomeQT
714 wrapQueryType = SomeRT
716 wrapRecordType :: RecordType rt dt => rt -> SomeRT
717 wrapRecordType = SomeRT
719 wrapRecord :: RecordType rt dt => ResourceRecord rt dt -> SomeRR