1 module Network.DNS.Message
27 import Control.Exception
30 import Data.Binary.BitPut as BP
31 import Data.Binary.Get as G
32 import Data.Binary.Put as P
33 import Data.Binary.Strict.BitGet as BG
34 import qualified Data.ByteString as BS
35 import qualified Data.ByteString.Char8 as C8 hiding (ByteString)
36 import qualified Data.ByteString.Lazy as LBS
38 import qualified Data.IntMap as IM
39 import Data.IntMap (IntMap)
43 replicateM' :: Monad m => Int -> (a -> m (b, a)) -> a -> m ([b], a)
44 replicateM' = worker []
46 worker :: Monad m => [b] -> Int -> (a -> m (b, a)) -> a -> m ([b], a)
47 worker soFar 0 _ a = return (reverse soFar, a)
48 worker soFar n f a = do (b, a') <- f a
49 worker (b : soFar) (n - 1) f a'
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 mkQueryType :: RecordType rt dt => rt -> SomeQT
116 putQ :: Question -> Put
118 = do putDomainName $ qName q
122 getQ :: DecompTable -> Get (Question, DecompTable)
124 = do (nm, dt') <- getDomainName dt
134 newtype DomainName = DN [DomainLabel] deriving (Eq, Show, Typeable)
135 type DomainLabel = BS.ByteString
137 nameToLabels :: DomainName -> [DomainLabel]
138 nameToLabels (DN ls) = ls
140 labelsToName :: [DomainLabel] -> DomainName
143 mkDomainName :: String -> DomainName
144 mkDomainName = labelsToName . mkLabels [] . notEmpty
146 notEmpty :: String -> String
147 notEmpty xs = assert (not $ null xs) xs
149 mkLabels :: [DomainLabel] -> String -> [DomainLabel]
150 mkLabels soFar [] = reverse (C8.empty : soFar)
151 mkLabels soFar xs = case break (== '.') xs of
153 -> mkLabels (C8.pack l : soFar) rest
154 _ -> error ("Illegal domain name: " ++ xs)
161 | AnyClass -- Only for queries
165 data RecordType rt dt => ResourceRecord rt dt
167 rrName :: !DomainName
169 , rrClass :: !RecordClass
173 deriving (Show, Eq, Typeable)
176 putRR :: forall rt dt. RecordType rt dt => ResourceRecord rt dt -> Put
177 putRR rr = do putDomainName $ rrName rr
178 putRecordType $ rrType rr
180 putWord32be $ rrTTL rr
183 putRecordData (undefined :: rt) (rrData rr)
184 putWord16be $ fromIntegral $ LBS.length dat
185 putLazyByteString dat
188 getRR :: forall rt dt. RecordType rt dt => DecompTable -> rt -> Get (ResourceRecord rt dt, DecompTable)
190 = do (nm, dt1) <- getDomainName dt
191 G.skip 2 -- record type
194 G.skip 2 -- data length
195 (dat, dt2) <- getRecordData (undefined :: rt) dt1
197 let rr = ResourceRecord {
207 data SomeRR = forall rt dt. RecordType rt dt => SomeRR (ResourceRecord rt dt)
209 instance Show SomeRR where
210 show (SomeRR rr) = show rr
212 instance Eq SomeRR where
213 (SomeRR a) == (SomeRR b) = Just a == cast b
216 putSomeRR :: SomeRR -> Put
217 putSomeRR (SomeRR rr) = putRR rr
219 getSomeRR :: DecompTable -> Get (SomeRR, DecompTable)
221 = do srt <- lookAhead $
222 do getDomainName dt -- skip
225 SomeRT rt -> getRR dt rt >>= \ (rr, dt') -> return (SomeRR rr, dt')
228 type DecompTable = IntMap BS.ByteString
231 getDomainName :: DecompTable -> Get (DomainName, DecompTable)
232 getDomainName = flip worker []
234 worker :: DecompTable -> [DomainLabel] -> Get (DomainName, DecompTable)
236 = do (l, dt') <- getDomainLabel dt
238 True -> return (labelsToName (reverse (l : soFar)), dt')
239 False -> worker dt' (l : soFar)
241 getDomainLabel :: DecompTable -> Get (DomainLabel, DecompTable)
243 = do header <- 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 let Just l = IM.lookup n dt
258 -> do offset <- liftM fromIntegral bytesRead
259 label <- getByteString n
260 let dt' = IM.insert offset label dt
263 getCharString :: Get BS.ByteString
264 getCharString = do len <- G.getWord8
265 getByteString (fromIntegral len)
267 putCharString :: BS.ByteString -> Put
268 putCharString = putDomainLabel
274 putDomainName :: DomainName -> Put
275 putDomainName = mapM_ putDomainLabel . nameToLabels
277 putDomainLabel :: DomainLabel -> Put
279 = do putWord8 $ fromIntegral $ BS.length l
282 class (Show rt, Show dt, Eq rt, Eq dt, Typeable rt, Typeable dt) => RecordType rt dt | rt -> dt where
284 putRecordType :: rt -> Put
285 putRecordData :: rt -> dt -> Put
286 getRecordData :: rt -> DecompTable -> Get (dt, DecompTable)
288 putRecordType = putWord16be . fromIntegral . rtToInt
290 data SomeRT = forall rt dt. RecordType rt dt => SomeRT rt
292 instance Show SomeRT where
293 show (SomeRT rt) = show rt
295 instance Eq SomeRT where
296 (SomeRT a) == (SomeRT b) = Just a == cast b
298 putSomeRT :: SomeRT -> Put
299 putSomeRT (SomeRT rt) = putRecordType rt
301 getSomeRT :: Get SomeRT
302 getSomeRT = do n <- liftM fromIntegral G.getWord16be
303 case IM.lookup n defaultRTTable of
305 -> fail ("Unknown resource record type: " ++ show n)
309 data CNAME = CNAME deriving (Show, Eq, Typeable)
310 instance RecordType CNAME DomainName where
312 putRecordData _ = putDomainName
313 getRecordData _ = getDomainName
315 data HINFO = HINFO deriving (Show, Eq, Typeable)
316 instance RecordType HINFO (BS.ByteString, BS.ByteString) where
318 putRecordData _ (cpu, os) = do putCharString cpu
320 getRecordData _ dt = do cpu <- getCharString
322 return ((cpu, os), dt)
351 instance Binary Message where
352 put m = do put $ msgHeader m
353 putWord16be $ fromIntegral $ length $ msgQuestions m
354 putWord16be $ fromIntegral $ length $ msgAnswers m
355 putWord16be $ fromIntegral $ length $ msgAuthorities m
356 putWord16be $ fromIntegral $ length $ msgAdditionals m
357 mapM_ putQ $ msgQuestions m
358 mapM_ putSomeRR $ msgAnswers m
359 mapM_ putSomeRR $ msgAuthorities m
360 mapM_ putSomeRR $ msgAdditionals m
363 nQ <- liftM fromIntegral G.getWord16be
364 nAns <- liftM fromIntegral G.getWord16be
365 nAth <- liftM fromIntegral G.getWord16be
366 nAdd <- liftM fromIntegral G.getWord16be
367 (qs , dt1) <- replicateM' nQ getQ IM.empty
368 (anss, dt2) <- replicateM' nAns getSomeRR dt1
369 (aths, dt3) <- replicateM' nAth getSomeRR dt2
370 (adds, _ ) <- replicateM' nAdd getSomeRR dt3
375 , msgAuthorities = aths
376 , msgAdditionals = adds
379 instance Binary Header where
380 put h = do putWord16be $ hdMessageID h
381 putLazyByteString flags
384 do putNBits 1 $ fromEnum $ hdMessageType h
385 putNBits 4 $ fromEnum $ hdOpcode h
386 putBit $ hdIsAuthoritativeAnswer h
387 putBit $ hdIsTruncated h
388 putBit $ hdIsRecursionDesired h
389 putBit $ hdIsRecursionAvailable h
390 putNBits 3 (0 :: Int)
391 putNBits 4 $ fromEnum $ hdResponseCode h
393 get = do mID <- G.getWord16be
394 flags <- getByteString 2
397 do qr <- liftM (toEnum . fromIntegral) $ getAsWord8 1
398 op <- liftM (toEnum . fromIntegral) $ getAsWord8 4
404 rc <- liftM (toEnum . fromIntegral) $ getAsWord8 4
409 , hdIsAuthoritativeAnswer = aa
411 , hdIsRecursionDesired = rd
412 , hdIsRecursionAvailable = ra
413 , hdResponseCode = rc
417 instance Enum MessageType where
419 fromEnum Response = 1
425 instance Enum Opcode where
426 fromEnum StandardQuery = 0
427 fromEnum InverseQuery = 1
428 fromEnum ServerStatusRequest = 2
430 toEnum 0 = StandardQuery
431 toEnum 1 = InverseQuery
432 toEnum 2 = ServerStatusRequest
435 instance Enum ResponseCode where
437 fromEnum FormatError = 1
438 fromEnum ServerFailure = 2
439 fromEnum NameError = 3
440 fromEnum NotImplemented = 4
444 toEnum 1 = FormatError
445 toEnum 2 = ServerFailure
447 toEnum 4 = NotImplemented
452 instance Enum RecordType where
472 fromEnum AnyType = 255
497 instance Enum RecordClass where
502 fromEnum AnyClass = 255
508 toEnum 255 = AnyClass
511 instance Binary RecordClass where
512 get = liftM (toEnum . fromIntegral) G.getWord16be
513 put = putWord16be . fromIntegral . fromEnum
516 defaultRTTable :: IntMap SomeRT
517 defaultRTTable = IM.fromList $ map toPair $
521 toPair :: SomeRT -> (Int, SomeRT)
522 toPair srt@(SomeRT rt) = (rtToInt rt, srt)