packages feed

dns 3.0.2 → 3.0.3

raw patch · 12 files changed

+233/−93 lines, 12 filesdep −conduitdep −conduit-extradep ~networkPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies removed: conduit, conduit-extra

Dependency ranges changed: network

API changes (from Hackage documentation)

+ Network.DNS.Types: DecodeError :: String -> DNSError
+ Network.DNS.Types: RD_NSEC3PARAM :: Word8 -> Word8 -> Word16 -> ByteString -> RData
+ Network.DNS.Types: maxUdpSize :: Word16
+ Network.DNS.Types: minUdpSize :: Word16
- Network.DNS.Decode: decode :: ByteString -> Either String DNSMessage
+ Network.DNS.Decode: decode :: ByteString -> Either DNSError DNSMessage
- Network.DNS.Decode: decodeDNSFlags :: ByteString -> Either String DNSFlags
+ Network.DNS.Decode: decodeDNSFlags :: ByteString -> Either DNSError DNSFlags
- Network.DNS.Decode: decodeDNSHeader :: ByteString -> Either String DNSHeader
+ Network.DNS.Decode: decodeDNSHeader :: ByteString -> Either DNSError DNSHeader
- Network.DNS.Decode: decodeDomain :: ByteString -> Either String Domain
+ Network.DNS.Decode: decodeDomain :: ByteString -> Either DNSError Domain
- Network.DNS.Decode: decodeMailbox :: ByteString -> Either String Mailbox
+ Network.DNS.Decode: decodeMailbox :: ByteString -> Either DNSError Mailbox
- Network.DNS.Decode: decodeMany :: ByteString -> Either String ([DNSMessage], ByteString)
+ Network.DNS.Decode: decodeMany :: ByteString -> Either DNSError ([DNSMessage], ByteString)
- Network.DNS.Decode: decodeResourceRecord :: ByteString -> Either String ResourceRecord
+ Network.DNS.Decode: decodeResourceRecord :: ByteString -> Either DNSError ResourceRecord

Files

Changelog.md view
@@ -1,3 +1,9 @@+# 3.0.3++- Implementing NSEC3PARAM [#109](https://github.com/kazu-yamamoto/dns/pull/109)+- Fixing an example of DNS server.+- Improving DNS decoder [#111](https://github.com/kazu-yamamoto/dns/pull/111)+ # 3.0.2  - Supporting conduit 1.3 [#105](https://github.com/kazu-yamamoto/dns/pull/105)
Network/DNS/Decode.hs view
@@ -20,12 +20,12 @@  -- | Decoding DNS query or response. -decode :: ByteString -> Either String DNSMessage+decode :: ByteString -> Either DNSError DNSMessage decode bs = fst <$> runSGet getResponse bs  -- | Parse many length-encoded DNS records, for example, from TCP traffic. -decodeMany :: ByteString -> Either String ([DNSMessage], ByteString)+decodeMany :: ByteString -> Either DNSError ([DNSMessage], ByteString) decodeMany bs = do     ((bss, _), leftovers) <- runSGetWithLeftovers lengthEncoded bs     msgs <- mapM decode bss@@ -38,21 +38,21 @@       getNByteString len  -- | Decoding DNS flags.-decodeDNSFlags :: ByteString -> Either String DNSFlags+decodeDNSFlags :: ByteString -> Either DNSError DNSFlags decodeDNSFlags bs = fst <$> runSGet getDNSFlags bs  -- | Decoding DNS header.-decodeDNSHeader :: ByteString -> Either String DNSHeader+decodeDNSHeader :: ByteString -> Either DNSError DNSHeader decodeDNSHeader bs = fst <$> runSGet getHeader bs  -- | Decoding domain.-decodeDomain :: ByteString -> Either String Domain+decodeDomain :: ByteString -> Either DNSError Domain decodeDomain bs = fst <$> runSGet getDomain bs  -- | Decoding mailbox.-decodeMailbox :: ByteString -> Either String Mailbox+decodeMailbox :: ByteString -> Either DNSError Mailbox decodeMailbox bs = fst <$> runSGet getMailbox bs  -- | Decoding resource record.-decodeResourceRecord :: ByteString -> Either String ResourceRecord+decodeResourceRecord :: ByteString -> Either DNSError ResourceRecord decodeResourceRecord bs = fst <$> runSGet getResourceRecord bs
Network/DNS/Decode/Internal.hs view
@@ -33,7 +33,6 @@                   <*> getResourceRecords nsCount                   <*> getResourceRecords arCount - ----------------------------------------------------------------  getDNSFlags :: SGet DNSFlags@@ -83,8 +82,8 @@  getQuery :: SGet Question getQuery = Question <$> getDomain-                       <*> getTYPE-                       <*  ignoreClass+                    <*> getTYPE+                    <*  ignoreClass  getResourceRecords :: Int -> SGet [ResourceRecord] getResourceRecords n = replicateM n getResourceRecord@@ -174,6 +173,7 @@     decodeDval = getNByteString (len - 4) -- getRData NULL len = const RD_NULL <$> getNByteString len+-- getRData DNSKEY len = RD_DNSKEY <$> decodeKeyFlags                                 <*> decodeKeyProto                                 <*> decodeKeyAlg@@ -184,6 +184,22 @@     decodeKeyAlg    = get8     decodeKeyBytes  = getNByteString (len - 4) --+getRData NSEC3PARAM len = RD_NSEC3PARAM <$> decodeHashAlg+                                <*> decodeFlags+                                <*> decodeIterations+                                <*> decodeSalt+  where+    decodeHashAlg    = get8+    decodeFlags      = get8+    decodeIterations = get16+    decodeSalt       = do+        let n = len - 5+        slen <- get8+        guard $ fromIntegral slen == n+        if (n == 0)+        then return B.empty+        else getNByteString n+-- getRData _  len = UnknownRData <$> getNByteString len  getOData :: OptCode -> Int -> SGet OData@@ -202,44 +218,54 @@ ----------------------------------------------------------------  getDomain :: SGet Domain-getDomain = getDomain' '.'+getDomain = do+    lim <- B.length <$> getInput+    getDomain' '.' lim 0  getMailbox :: SGet Mailbox-getMailbox = getDomain' '@'+getMailbox = do+    lim <- B.length <$> getInput+    getDomain' '@' lim 0  -- | Get a domain name, using sep1 as the separate between the 1st and 2nd -- label.  Subsequent labels (and always the trailing label) are terminated -- with a ".".-getDomain' :: Char -> SGet ByteString-getDomain' sep1 = do-    pos <- getPosition-    c <- getInt8-    let n = getValue c-    -- Syntax hack to avoid using MultiWayIf-    case () of-        _ | c == 0 -> return "." -- Perhaps the root domain?-        _ | isPointer c -> do-            d <- getInt8-            let offset = n * 256 + d-            mo <- pop offset-            case mo of-                Nothing -> fail $ "getDomain: " ++ show offset-                -- A pointer may refer to another pointer.-                -- So, register this position for the domain.-                Just o -> push pos o >> return o-        -- As for now, extended labels have no use.-        -- This may change some time in the future.-        _ | isExtLabel c -> return ""-        _ -> do-            hs <- getNByteString n-            ds <- getDomain' '.'-            let dom =-                    case ds of -- avoid trailing ".."-                        "." -> hs `BS.append` "."-                        _   -> hs `BS.append` BS.singleton sep1 `BS.append` ds-            push pos dom-            return dom+getDomain' :: Char -> Int -> Int -> SGet ByteString+getDomain' sep1 lim loopcnt+  -- 127 is the logical limitation of pointers.+  | loopcnt >= 127 = fail "pointer recursion limit exceeded"+  | otherwise      = do+      pos <- getPosition+      c <- getInt8+      let n = getValue c+      getdomain pos c n   where+    getdomain pos c n+      | c == 0 = return "." -- Perhaps the root domain?+      | isPointer c = do+          d <- getInt8+          let offset = n * 256 + d+          when (offset >= lim) $ fail "pointer is too large"+          mo <- pop offset+          case mo of+              Nothing -> do+                  target <- B.drop offset <$> getInput+                  case runSGet (getDomain' sep1 lim (loopcnt + 1)) target of+                        Left (DecodeError err) -> fail err+                        Left err               -> fail $ show err+                        Right o  -> push pos (fst o) >> return (fst o)+              Just o -> push pos o >> return o+      -- As for now, extended labels have no use.+      -- This may change some time in the future.+      | isExtLabel c = return ""+      | otherwise = do+          hs <- getNByteString n+          ds <- getDomain' '.' lim (loopcnt + 1)+          let dom = case ds of -- avoid trailing ".."+                  "." -> hs `BS.append` "."+                  _   -> hs `BS.append` BS.singleton sep1 `BS.append` ds+          push pos dom+          return dom     getValue c = c .&. 0x3f     isPointer c = testBit c 7 && testBit c 6     isExtLabel c = not (testBit c 7) && testBit c 6
Network/DNS/Encode.hs view
@@ -166,6 +166,12 @@         , put8 a         , putByteString k         ]+    (RD_NSEC3PARAM h f i s) -> mconcat+        [ put8 h+        , put8 f+        , put16 i+        , putByteStringWithLength s+        ]     UnknownRData bytes -> putByteString bytes  putOData :: OData -> SPut
Network/DNS/IO.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}  module Network.DNS.IO (     -- * Receiving from socket@@ -26,51 +27,79 @@ #define GHC708 #endif -import qualified Control.Monad.State as ST+import qualified Control.Exception as E import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy.Char8 as LBS import Data.Char (ord)-import Data.Conduit (($$+), ($$+-), ConduitM, (.|), runConduit)-import Data.Conduit.Attoparsec (sinkParser)-import qualified Data.Conduit.Binary as CB-import Data.Conduit.Network (sourceSocket) import Data.IP (IPv4, IPv6) import Network.Socket (Socket)+import System.IO.Error + #if defined(WIN) && defined(GHC708)-import Network.Socket (send)+import Network.Socket (send, recv) import qualified Data.ByteString.Char8 as BS #else-import Network.Socket.ByteString (sendAll)+import Network.Socket.ByteString (sendAll, recv) #endif -import Network.DNS.Decode.Internal (getResponse)+import Network.DNS.Decode (decode) import Network.DNS.Encode (encode) import Network.DNS.Imports-import Network.DNS.StateBinary (PState, initialState) import Network.DNS.Types  ---------------------------------------------------------------- -sink :: ConduitM ByteString o IO (DNSMessage, PState)-sink = sinkParser $ ST.runStateT getResponse initialState- -- | Receiving DNS data from 'Socket' and parse it.  receive :: Socket -> IO DNSMessage-receive sock = fst <$> runConduit (sourceSocket sock .| sink)+receive sock = do+    let bufsiz = fromIntegral maxUdpSize+    bs <- recv sock bufsiz `E.catch` \e -> E.throwIO $ NetworkFailure e+    case decode bs of+        Left  e   -> E.throwIO e+        Right msg -> return msg  -- | Receive and parse a single virtual-circuit (TCP) query or response. --   It is up to the caller to implement any desired timeout.  receiveVC :: Socket -> IO DNSMessage receiveVC sock = do-    (src, lenbytes) <- sourceSocket sock $$+ CB.take 2-    let len = case map ord $ LBS.unpack lenbytes of-                [hi, lo] -> 256 * hi + lo-                _        -> 0-    fst <$> (src $$+- CB.isolate len .| sink)+    len <- toLen <$> recvDNS sock 2+    bs <- recvDNS sock len+    case decode bs of+        Left e    -> E.throwIO e+        Right msg -> return msg+  where+    toLen bs = case map ord $ BS.unpack bs of+        [hi, lo] -> 256 * hi + lo+        _        -> 0              -- never reached++recvDNS :: Socket -> Int -> IO ByteString+recvDNS sock len = recv1 `E.catch` \e -> E.throwIO $ NetworkFailure e+  where+    recv1 = do+        bs1 <- recvCore len+        if BS.length bs1 == len then+            return bs1+          else do+            loop bs1+    loop bs0 = do+        let left = len - BS.length bs0+        bs1 <- recvCore left+        let bs = bs0 `BS.append` bs1+        if BS.length bs == len then+            return bs+          else+            loop bs+    eofE = mkIOError eofErrorType "connection terminated" Nothing Nothing+    recvCore len0 = do+        bs <- recv sock len0+        if bs == "" then+            E.throwIO eofE+          else+            return bs  ---------------------------------------------------------------- 
Network/DNS/Lookup.hs view
@@ -309,12 +309,16 @@  ---------------------------------------------------------------- --- | Look up the \'SOA\' record for the given domain. The results include the---   domain, mailbox, serial number, refresh time, retry time, expiration---   time, and minimum TTL.+-- | Look up the \'SOA\' record for the given domain. The result 7-tuple+--   consists of the \'mname\', \'rname\', \'serial\', \'refresh\', \'retry\',+--   \'expire\' and \'minimum\' fields of the SOA record. -----   This can be useful to validate TTLs for a server or get an abuse---   contact address for a domain.+--   An \@ separator is used between the first and second labels of the+--   \'rname\' field.  Since \'rname\' is an email address, it often contains+--   periods within its first label.  Presently, the trailing period is not+--   removed from the domain part of the \'rname\', but this may change in the+--   future.  Users should be prepared to remove any trailing period before+--   using the \'rname\` as a contact email address. -- --   >>> rs <- makeResolvSeed defaultResolvConf --   >>> withResolver rs $ \resolver -> lookupSOA resolver "mew.org"
Network/DNS/Memo.hs view
@@ -68,6 +68,7 @@ copy (RD_DS t a dt dv)    = RD_DS t a dt $ B.copy dv copy (RD_DNSKEY f p a k)  = RD_DNSKEY f p a $ B.copy k copy (RD_TLSA a b c dgst) = RD_TLSA a b c $ B.copy dgst+copy (RD_NSEC3PARAM a b c salt) = RD_NSEC3PARAM a b c $ B.copy salt copy (UnknownRData is)    = UnknownRData $ B.copy is  copyOData :: OData -> OData
Network/DNS/StateBinary.hs view
@@ -24,6 +24,7 @@   , getInt32   , getNByteString   , getPosition+  , getInput   , wsPop   , wsPush   , wsPosition@@ -122,6 +123,7 @@ data PState = PState {     psDomain :: IntMap Domain   , psPosition :: Int+  , psInput :: ByteString   }  ----------------------------------------------------------------@@ -129,15 +131,18 @@ getPosition :: SGet Int getPosition = psPosition <$> ST.get +getInput :: SGet ByteString+getInput = psInput <$> ST.get+ addPosition :: Int -> SGet () addPosition n = do-    PState dom pos <- ST.get-    ST.put $ PState dom (pos + n)+    PState dom pos inp <- ST.get+    ST.put $ PState dom (pos + n) inp  push :: Int -> Domain -> SGet () push n d = do-    PState dom pos <- ST.get-    ST.put $ PState (IM.insert n d dom) pos+    PState dom pos inp <- ST.get+    ST.put $ PState (IM.insert n d dom) pos inp  pop :: Int -> SGet (Maybe Domain) pop n = IM.lookup n . psDomain <$> ST.get@@ -188,19 +193,24 @@  ---------------------------------------------------------------- -initialState :: PState-initialState = PState IM.empty 0+initialState :: ByteString -> PState+initialState inp = PState IM.empty 0 inp -runSGet :: SGet a -> ByteString -> Either String (a, PState)-runSGet parser bs = A.eitherResult $ A.parse (ST.runStateT parser initialState) bs+runSGet :: SGet a -> ByteString -> Either DNSError (a, PState)+runSGet parser inp = toResult $ A.parse (ST.runStateT parser $ initialState inp) inp+  where+    toResult :: A.Result r -> Either DNSError r+    toResult (A.Done _ r)        = Right r+    toResult (A.Fail _ _ msg)    = Left $ DecodeError msg+    toResult (A.Partial _)       = Left $ DecodeError "incomplete input" -runSGetWithLeftovers :: SGet a -> ByteString -> Either String ((a, PState), ByteString)-runSGetWithLeftovers parser bs = toResult $ A.parse (ST.runStateT parser initialState) bs+runSGetWithLeftovers :: SGet a -> ByteString -> Either DNSError ((a, PState), ByteString)+runSGetWithLeftovers parser inp = toResult $ A.parse (ST.runStateT parser $ initialState inp) inp   where-    toResult :: A.Result r -> Either String (r, ByteString)-    toResult (A.Done i r) = Right (r, i)-    toResult (A.Partial f) = toResult $ f BS.empty-    toResult (A.Fail _ _ err) = Left err+    toResult :: A.Result r -> Either DNSError (r, ByteString)+    toResult (A.Done     i r) = Right (r, i)+    toResult (A.Partial  f)   = toResult $ f BS.empty+    toResult (A.Fail _ _ err) = Left $ DecodeError err  runSPut :: SPut -> ByteString runSPut = LBS.toStrict . BB.toLazyByteString . flip ST.evalState initialWState
Network/DNS/Types.hs view
@@ -80,6 +80,8 @@   -- * EDNS0   , EDNS0   , defaultEDNS0+  , maxUdpSize+  , minUdpSize   -- ** Accessors   , udpSize   , extRCODE@@ -114,13 +116,14 @@ type Domain = ByteString  -- | Type for a mailbox encoded on the wire as a DNS name, but the first label--- is conceptually the user name, and sometimes has internal '.' characters--- that are not label separators.+-- is conceptually the user name, and sometimes has contains internal periods+-- that are not label separators. Therefore, in mailboxes \@ is used as the+-- separator between the first and second labels. type Mailbox = ByteString  ---------------------------------------------------------------- -#if __GLASGOW_HASKELL__ >= 800+#if __GLASGOW_HASKELL__ >= 802 -- | Types for resource records. newtype TYPE = TYPE {     -- | From type to number.@@ -352,6 +355,7 @@     -- | Network failure.   | NetworkFailure IOException     -- | Error is unknown+  | DecodeError String   | UnknownDNSError   deriving (Eq, Show, Typeable) @@ -418,7 +422,7 @@  ---------------------------------------------------------------- -#if __GLASGOW_HASKELL__ >= 800+#if __GLASGOW_HASKELL__ >= 802 -- | Response code including EDNS0's 12bit ones. newtype RCODE = RCODE {     -- | From rcode to number.@@ -646,7 +650,7 @@            | RD_DNSKEY Word16 Word8 Word8 ByteString                                  -- ^ DNSKEY (RFC4034)            --RD_NSEC3-           --RD_NSEC3PARAM+           | RD_NSEC3PARAM Word8 Word8 Word16 ByteString            | RD_TLSA Word8 Word8 Word8 ByteString                                  -- ^ TLSA (RFC6698)            --RD_CDS@@ -674,6 +678,10 @@   show (RD_DS t a dt dv) = show t ++ " " ++ show a ++ " " ++ show dt ++ " " ++ hexencode dv   show RD_NULL = "NULL"   show (RD_DNSKEY f p a k) = show f ++ " " ++ show p ++ " " ++ show a ++ " " ++ b64encode k+  show (RD_NSEC3PARAM h f i s) = show h ++ " " ++ show f ++ " " ++ show i ++ " " ++ showSalt s+    where+      showSalt ""    = "-"+      showSalt salt  = hexencode salt  hexencode :: ByteString -> String hexencode = BS.unpack . L.toStrict . L.toLazyByteString . L.byteStringHex@@ -737,7 +745,7 @@   , options  :: [OData]   } deriving (Eq, Show) -#if __GLASGOW_HASKELL__ >= 800+#if __GLASGOW_HASKELL__ >= 802 -- | Default information for EDNS0. -- -- >>> defaultEDNS0@@ -751,6 +759,22 @@ defaultEDNS0 :: EDNS0 defaultEDNS0 = EDNS0 4096 NoErr False [] +-- | Maximum UDP size. If 'udpSize' of 'EDNS0' is larger than this,+--   'fromEDNS0' uses this value instead.+--+-- >>> maxUdpSize+-- 16384+maxUdpSize :: Word16+maxUdpSize = 16384++-- | Minimum UDP size. If 'udpSize' of 'EDNS0' is smaller than this,+--   'fromEDNS0' uses this value instead.+--+-- >>> minUdpSize+-- 512+minUdpSize :: Word16+minUdpSize = 512+ -- | Generating a resource record for the additional section based on EDNS0. -- 'DNSFlags' is not generated. -- Just set the same 'RCODE' to 'DNSFlags'.@@ -759,7 +783,7 @@   where     name'  = "."     type'  = OPT-    class' = udpSize edns+    class' = maxUdpSize `min` (minUdpSize `max` udpSize edns)     ttl0'   = fromIntegral (fromRCODE (extRCODE edns) .&. 0x0ff0) `shiftL` 20     ttl'       | dnssecOk edns = ttl0' `setBit` 15@@ -779,7 +803,7 @@  ---------------------------------------------------------------- -#if __GLASGOW_HASKELL__ >= 800+#if __GLASGOW_HASKELL__ >= 802 -- | EDNS0 Option Code (RFC 6891). newtype OptCode = OptCode {     -- | From option code to number.
dns.cabal view
@@ -1,5 +1,5 @@ Name:                   dns-Version:                3.0.2+Version:                3.0.3 Author:                 Kazu Yamamoto <kazu@iij.ad.jp> Maintainer:             Kazu Yamamoto <kazu@iij.ad.jp> License:                BSD3@@ -41,8 +41,6 @@                       , base64-bytestring                       , binary                       , bytestring-                      , conduit >= 1.1-                      , conduit-extra >= 1.1                       , containers                       , cryptonite                       , iproute >= 1.3.2@@ -63,10 +61,12 @@   Ghc-Options:          -Wall   Main-Is:              Spec.hs   Other-Modules:        LookupSpec+                        IOSpec   Build-Depends:        dns                       , base                       , bytestring                       , hspec+                      , network  Test-Suite spec   Type:                 exitcode-stdio-1.0
test/DecodeSpec.hs view
@@ -56,13 +56,16 @@  tripleDecodeTest :: ByteString -> IO () tripleDecodeTest hexbs =-    ecase (decode $ fromHexString hexbs) fail $ \ x1 ->-        ecase (decode $ encode x1) fail $ \ x2 ->-            ecase (decode $ encode x2) fail $ \ x3 ->+    ecase (decode $ fromHexString hexbs) fail' $ \ x1 ->+        ecase (decode $ encode x1) fail' $ \ x2 ->+            ecase (decode $ encode x2) fail' $ \ x3 ->                 x3 `shouldBe` x2+  where+    fail' (DecodeError err) = fail err+    fail' _                 = error "fail'"  ecase :: Either a b -> (a -> c) -> (b -> c) -> c-ecase (Left a) f _ = f a+ecase (Left  a) f _ = f a ecase (Right b) _ g = g b  ----------------------------------------------------------------
+ test2/IOSpec.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE OverloadedStrings #-}++module IOSpec where++import Network.DNS.IO as DNS+import Network.DNS.Types as DNS+import Network.Socket hiding (send)+import Test.Hspec++spec :: Spec+spec = describe "send/receive" $ do++    it "resolves well with UDP" $ do+        let hints = defaultHints { addrFamily = AF_INET, addrSocketType = Datagram, addrFlags = [AI_NUMERICHOST]}+        addr:_ <- getAddrInfo (Just hints) (Just "8.8.8.8") (Just "domain")+        sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)+        connect sock $ addrAddress addr+        let qry = encodeQuestions 1 [Question "www.mew.org" A] [] False+        send sock qry+        ans <- receive sock+        identifier (header ans) `shouldBe` 1++    it "resolves well with TCP" $ do+        let hints = defaultHints { addrFamily = AF_INET, addrSocketType = Stream, addrFlags = [AI_NUMERICHOST]}+        addr:_ <- getAddrInfo (Just hints) (Just "8.8.8.8") (Just "domain")+        sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)+        connect sock $ addrAddress addr+        let qry = encodeQuestions 1 [Question "www.mew.org" A] [] False+        sendVC sock qry+        ans <- receiveVC sock+        identifier (header ans) `shouldBe` 1