1 module Network.DNS.Message
32 import Control.Exception
35 import Data.Binary.BitPut as BP
36 import Data.Binary.Get as G
37 import Data.Binary.Put as P'
38 import Data.Binary.Strict.BitGet as BG
39 import qualified Data.ByteString as BS
40 import qualified Data.ByteString.Char8 as C8 hiding (ByteString)
42 import qualified Data.IntMap as IM
43 import Data.IntMap (IntMap)
44 import qualified Data.Map as M
47 import Network.DNS.Packer as P
48 import Network.DNS.Unpacker as U
55 , msgQuestions :: ![Question]
56 , msgAnswers :: ![SomeRR]
57 , msgAuthorities :: ![SomeRR]
58 , msgAdditionals :: ![SomeRR]
64 hdMessageID :: !MessageID
65 , hdMessageType :: !MessageType
67 , hdIsAuthoritativeAnswer :: !Bool
68 , hdIsTruncated :: !Bool
69 , hdIsRecursionDesired :: !Bool
70 , hdIsRecursionAvailable :: !Bool
71 , hdResponseCode :: !ResponseCode
73 -- These fields are supressed in this data structure:
81 type MessageID = Word16
107 , qClass :: !RecordClass
113 putQ :: Question -> Packer CompTable ()
115 = do putDomainName $ qName q
119 getQ :: Unpacker DecompTable Question
120 getQ = do nm <- getDomainName
130 newtype DomainName = DN [DomainLabel] deriving (Eq, Show, Ord, Typeable)
131 type DomainLabel = BS.ByteString
133 rootName :: DomainName
134 rootName = DN [BS.empty]
136 isRootName :: DomainName -> Bool
137 isRootName (DN [_]) = True
140 consLabel :: DomainLabel -> DomainName -> DomainName
141 consLabel x (DN ys) = DN (x:ys)
143 unconsLabel :: DomainName -> (DomainLabel, DomainName)
144 unconsLabel (DN (x:xs)) = (x, DN xs)
145 unconsLabel x = error ("Illegal use of unconsLabel: " ++ show x)
147 mkDomainName :: String -> DomainName
148 mkDomainName = DN . mkLabels [] . notEmpty
150 notEmpty :: String -> String
151 notEmpty xs = assert (not $ null xs) xs
153 mkLabels :: [DomainLabel] -> String -> [DomainLabel]
154 mkLabels soFar [] = reverse (C8.empty : soFar)
155 mkLabels soFar xs = case break (== '.') xs of
157 -> mkLabels (C8.pack l : soFar) rest
158 _ -> error ("Illegal domain name: " ++ xs)
165 | AnyClass -- Only for queries
169 data RecordType rt dt => ResourceRecord rt dt
171 rrName :: !DomainName
173 , rrClass :: !RecordClass
177 deriving (Show, Eq, Typeable)
180 data SomeRR = forall rt dt. RecordType rt dt => SomeRR (ResourceRecord rt dt)
182 instance Show SomeRR where
183 show (SomeRR rr) = show rr
185 instance Eq SomeRR where
186 (SomeRR a) == (SomeRR b) = Just a == cast b
189 putSomeRR :: SomeRR -> Packer CompTable ()
190 putSomeRR (SomeRR rr) = putResourceRecord rr
192 getSomeRR :: Unpacker DecompTable SomeRR
193 getSomeRR = do srt <- U.lookAhead $
194 do getDomainName -- skip
198 -> getResourceRecord rt >>= return . SomeRR
200 type CompTable = Map DomainName Int
201 type DecompTable = IntMap DomainName
204 getDomainName :: Unpacker DecompTable DomainName
205 getDomainName = worker
207 worker :: Unpacker DecompTable DomainName
209 = do offset <- U.bytesRead
210 hdr <- getLabelHeader
213 -> do dt <- U.getState
214 case IM.lookup n dt of
218 -> fail ("Illegal offset of label pointer: " ++ show (n, dt))
222 -> do label <- U.getByteString n
224 let name = consLabel label rest
225 U.modifyState $ IM.insert offset name
228 getLabelHeader :: Unpacker s LabelHeader
230 = do header <- U.lookAhead $ U.getByteString 1
235 n <- liftM fromIntegral (getAsWord8 6)
237 ( True, True) -> return $ Offset n
238 (False, False) -> return $ Length n
239 _ -> fail "Illegal label header"
242 -> do header' <- U.getByteString 2 -- Pointers have 2 octets.
244 = runBitGet header' $
246 n <- liftM fromIntegral (getAsWord16 14)
254 getCharString :: Unpacker s BS.ByteString
255 getCharString = do len <- U.getWord8
256 U.getByteString (fromIntegral len)
258 putCharString :: BS.ByteString -> Packer s ()
259 putCharString xs = do P.putWord8 $ fromIntegral $ BS.length xs
266 putDomainName :: DomainName -> Packer CompTable ()
268 = do ct <- P.getState
269 case M.lookup name ct of
271 -> do let ptr = runBitPut $
275 P.putLazyByteString ptr
277 -> do offset <- bytesWrote
278 P.modifyState $ M.insert name offset
280 let (label, rest) = unconsLabel name
284 if isRootName rest then
290 class (Show rt, Show dt, Eq rt, Eq dt, Typeable rt, Typeable dt) => RecordType rt dt | rt -> dt where
292 putRecordData :: rt -> dt -> Packer CompTable ()
293 getRecordData :: rt -> Unpacker DecompTable dt
295 putRecordType :: rt -> Packer s ()
296 putRecordType = P.putWord16be . fromIntegral . rtToInt
298 putResourceRecord :: ResourceRecord rt dt -> Packer CompTable ()
300 = do putDomainName $ rrName rr
301 putRecordType $ rrType rr
302 putBinary $ rrClass rr
303 P.putWord32be $ rrTTL rr
305 -- First, write a dummy data length.
309 -- Second, write data.
310 putRecordData (rrType rr) (rrData rr)
312 -- Third, rewrite the dummy length to an actual value.
313 offset' <- bytesWrote
315 $ P.putWord16be (fromIntegral (offset' - offset - 2))
317 getResourceRecord :: rt -> Unpacker DecompTable (ResourceRecord rt dt)
319 = do name <- getDomainName
320 U.skip 2 -- record type
323 U.skip 2 -- data length
324 dat <- getRecordData rt
325 return $ ResourceRecord {
333 data SomeRT = forall rt dt. RecordType rt dt => SomeRT rt
335 instance Show SomeRT where
336 show (SomeRT rt) = show rt
338 instance Eq SomeRT where
339 (SomeRT a) == (SomeRT b) = Just a == cast b
341 putSomeRT :: SomeRT -> Packer s ()
342 putSomeRT (SomeRT rt) = putRecordType rt
344 getSomeRT :: Unpacker s SomeRT
345 getSomeRT = do n <- liftM fromIntegral U.getWord16be
346 case IM.lookup n defaultRTTable of
348 -> fail ("Unknown resource record type: " ++ show n)
352 data A = A deriving (Show, Eq, Typeable)
353 instance RecordType A HostAddress where
355 putRecordData _ = P.putWord32be
356 getRecordData _ = U.getWord32be
358 data NS = NS deriving (Show, Eq, Typeable)
359 instance RecordType NS DomainName where
361 putRecordData _ = putDomainName
362 getRecordData _ = getDomainName
364 data CNAME = CNAME deriving (Show, Eq, Typeable)
365 instance RecordType CNAME DomainName where
367 putRecordData _ = putDomainName
368 getRecordData _ = getDomainName
370 data HINFO = HINFO deriving (Show, Eq, Typeable)
371 instance RecordType HINFO (BS.ByteString, BS.ByteString) where
373 putRecordData _ (cpu, os) = do putCharString cpu
375 getRecordData _ = do cpu <- getCharString
407 instance Binary Message where
408 put m = P.liftToBinary M.empty $
409 do putBinary $ msgHeader m
410 P.putWord16be $ fromIntegral $ length $ msgQuestions m
411 P.putWord16be $ fromIntegral $ length $ msgAnswers m
412 P.putWord16be $ fromIntegral $ length $ msgAuthorities m
413 P.putWord16be $ fromIntegral $ length $ msgAdditionals m
414 mapM_ putQ $ msgQuestions m
415 mapM_ putSomeRR $ msgAnswers m
416 mapM_ putSomeRR $ msgAuthorities m
417 mapM_ putSomeRR $ msgAdditionals m
419 get = U.liftToBinary IM.empty $
421 nQ <- liftM fromIntegral U.getWord16be
422 nAns <- liftM fromIntegral U.getWord16be
423 nAth <- liftM fromIntegral U.getWord16be
424 nAdd <- liftM fromIntegral U.getWord16be
425 qs <- replicateM nQ getQ
426 anss <- replicateM nAns getSomeRR
427 aths <- replicateM nAth getSomeRR
428 adds <- replicateM nAdd getSomeRR
433 , msgAuthorities = aths
434 , msgAdditionals = adds
437 instance Binary Header where
438 put h = do P'.putWord16be $ hdMessageID h
439 P'.putLazyByteString flags
442 do putNBits 1 $ fromEnum $ hdMessageType h
443 putNBits 4 $ fromEnum $ hdOpcode h
444 putBit $ hdIsAuthoritativeAnswer h
445 putBit $ hdIsTruncated h
446 putBit $ hdIsRecursionDesired h
447 putBit $ hdIsRecursionAvailable h
448 putNBits 3 (0 :: Int)
449 putNBits 4 $ fromEnum $ hdResponseCode h
451 get = do mID <- G.getWord16be
452 flags <- G.getByteString 2
455 do qr <- liftM (toEnum . fromIntegral) $ getAsWord8 1
456 op <- liftM (toEnum . fromIntegral) $ getAsWord8 4
462 rc <- liftM (toEnum . fromIntegral) $ getAsWord8 4
467 , hdIsAuthoritativeAnswer = aa
469 , hdIsRecursionDesired = rd
470 , hdIsRecursionAvailable = ra
471 , hdResponseCode = rc
475 instance Enum MessageType where
477 fromEnum Response = 1
483 instance Enum Opcode where
484 fromEnum StandardQuery = 0
485 fromEnum InverseQuery = 1
486 fromEnum ServerStatusRequest = 2
488 toEnum 0 = StandardQuery
489 toEnum 1 = InverseQuery
490 toEnum 2 = ServerStatusRequest
493 instance Enum ResponseCode where
495 fromEnum FormatError = 1
496 fromEnum ServerFailure = 2
497 fromEnum NameError = 3
498 fromEnum NotImplemented = 4
502 toEnum 1 = FormatError
503 toEnum 2 = ServerFailure
505 toEnum 4 = NotImplemented
510 instance Enum RecordType where
530 fromEnum AnyType = 255
555 instance Enum RecordClass where
560 fromEnum AnyClass = 255
566 toEnum 255 = AnyClass
569 instance Binary RecordClass where
570 get = liftM (toEnum . fromIntegral) G.getWord16be
571 put = P'.putWord16be . fromIntegral . fromEnum
574 defaultRTTable :: IntMap SomeRT
575 defaultRTTable = IM.fromList $ map toPair $
578 , wrapRecordType CNAME
579 , wrapRecordType HINFO
582 toPair :: SomeRT -> (Int, SomeRT)
583 toPair srt@(SomeRT rt) = (rtToInt rt, srt)
586 wrapQueryType :: RecordType rt dt => rt -> SomeQT
587 wrapQueryType = SomeRT
589 wrapRecordType :: RecordType rt dt => rt -> SomeRT
590 wrapRecordType = SomeRT
592 wrapRecord :: RecordType rt dt => ResourceRecord rt dt -> SomeRR