{-# LANGUAGE CApiFFI #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} -- | -- Copyright: © 2017 Herbert Valerio Riedel -- License: GPLv3 -- module Network.DNS.FFI where import Control.Applicative as App import Control.DeepSeq import Control.Exception import Control.Monad import Data.Bits import Data.ByteString (ByteString) import qualified Data.ByteString as BS import qualified Data.Foldable as F import Data.Int import qualified Data.Traversable as T import Data.Word import Foreign.C.Types import Foreign.Marshal.Alloc import Foreign.Ptr import Foreign.Storable import Numeric (showHex) #include #include type DWORD = #type DWORD type WORD = #type WORD type BOOL = #type BOOL ---------------------------------------------------------------------------- -- | Cache time-to-live expressed in seconds newtype TTL = TTL Int32 deriving (Eq,Ord,Read,Show,NFData) -- | @\@ as per [RFC 1035, section 3.3](https://tools.ietf.org/html/rfc1035#section-3.3). -- -- A sequence of up to 255 octets -- -- The limit of 255 octets is caused by the encoding which uses by a -- prefixed octet denoting the length. newtype CharStr = CharStr ByteString deriving (Eq,Ord,Read,Show) instance NFData CharStr where rnf (CharStr !_) = () -- | @\@ as per [RFC 1035, section 3.3](https://tools.ietf.org/html/rfc1035#section-3.3). -- -- A domain-name represented as a series of labels separated by dots. newtype Name = Name ByteString deriving (Eq,Ord,Read,Show) instance NFData Name where rnf (Name !_) = () -- | An IPv6 address -- -- The IP address is represented in network order, -- i.e. @2606:2800:220:1:248:1893:25c8:1946@ is -- represented as @(IPv6 0x2606280002200001 0x248189325c81946)@. data IPv6 = IPv6 !Word64 !Word64 deriving (Eq,Ord,Read) instance Show IPv6 where showsPrec p (IPv6 hi lo) = showParen (p >= 11) (showString "IPv6 0x" . showHex hi . showString " 0x" . showHex lo) instance NFData IPv6 where rnf (IPv6 _ _) = () mkIPv6 :: Word32 -> Word32 -> Word32 -> Word32 -> IPv6 mkIPv6 a b c d = IPv6 (mkW64 a b) (mkW64 c d) where mkW64 x y = (fromIntegral (byteSwap32 x) `shiftL` 32) .|. fromIntegral (byteSwap32 y) :: Word64 -- | An IPv4 address -- -- The IP address is represented in network order, i.e. @127.0.0.1@ is -- represented as @(IPv4 0x7f000001)@. data IPv4 = IPv4 !Word32 deriving (Eq,Ord,Read) instance NFData IPv4 where rnf (IPv4 _) = () mkIPv4 :: Word32 -> IPv4 mkIPv4 a = IPv4 (byteSwap32 a) instance Show IPv4 where showsPrec p (IPv4 n) = showParen (p >= 11) (showString "IPv4 0x" . showHex n) -- | @SRV@ Record data as per [RFC 2782](https://tools.ietf.org/html/rfc2782) data SRV l = SRV { srvPriority :: !Word16 , srvWeight :: !Word16 , srvPort :: !Word16 , srvTarget :: !l } deriving (Eq,Read,Show,Functor,F.Foldable,T.Traversable) instance NFData l => NFData (SRV l) where rnf (SRV _ _ _ l) = rnf l ---------------------------------------------------------------------------- dnsQuery :: Bool -> Name -> DnsType -> IO (Either Int [DnsRecord]) dnsQuery exact (Name n) ty = do alloca $ \pst -> do BS.useAsCString n $ \n' -> bracket (c_dns_query n' (fromDnsType ty) pst) (c_free_record) $ \p0 -> do st <- peek pst if (st /= 0) then pure (Left (fromIntegral st)) else do tmp <- travRecs (peekRec n') p0 if exact then pure (Right [ r | (b,r) <- tmp, b ]) else pure (Right (map snd tmp)) foreign import capi safe "hs_windns.h hs_dns_query" c_dns_query :: Ptr CChar -> WORD -> Ptr CLong -> IO (Ptr DnsRecord) foreign import capi unsafe "hs_windns.h hs_free_record" c_free_record :: Ptr DnsRecord -> IO (Ptr DnsRecord) foreign import capi unsafe "hs_windns.h DnsNameCompare_A" c_dns_name_eq :: Ptr CChar -> Ptr CChar -> IO BOOL travRecs :: (Ptr DnsRecord -> IO a) -> Ptr DnsRecord -> IO [a] travRecs f p0 = go [] p0 where go acc p | p == nullPtr = App.pure (reverse acc) | otherwise = do x <- f p p' <- next p go (x:acc) p' next :: Ptr DnsRecord -> IO (Ptr DnsRecord) next = #{peek DNS_RECORDA, pNext} peekRec :: Ptr CChar -> Ptr DnsRecord -> IO (Bool,DnsRecord) peekRec n0 p = do drNamePtr <- #{peek DNS_RECORDA, pName} p same <- c_dns_name_eq n0 drNamePtr drName <- Name <$> BS.packCString drNamePtr drType <- toDnsType <$> #{peek DNS_RECORDA, wType} p drTTL <- TTL . fromIntegral <$> (#{peek DNS_RECORDA, dwTtl} p :: IO DWORD) drData <- case drType of DnsTypeA -> DnsDataA . mkIPv4 <$> #{peek DNS_RECORDA, Data.A.IpAddress} p DnsTypeAAAA -> DnsDataAAAA <$> (mkIPv6 <$> #{peek DNS_RECORDA, Data.AAAA.Ip6Address.IP6Dword[0]} p <*> #{peek DNS_RECORDA, Data.AAAA.Ip6Address.IP6Dword[1]} p <*> #{peek DNS_RECORDA, Data.AAAA.Ip6Address.IP6Dword[2]} p <*> #{peek DNS_RECORDA, Data.AAAA.Ip6Address.IP6Dword[3]} p) DnsTypeTXT -> do cnt <- #{peek DNS_RECORDA, Data.TXT.dwStringCount} p let ptr0 = #{ptr DNS_RECORDA, Data.TXT.pStringArray[0]} p tptrs <- forM [0.. fromIntegral (cnt :: DWORD)-1] (peekElemOff ptr0) DnsDataTXT <$> mapM (fmap CharStr . BS.packCString) tptrs DnsTypeCNAME -> do cnamptr <- #{peek DNS_RECORDA, Data.CNAME.pNameHost} p DnsDataCNAME . Name <$> BS.packCString cnamptr DnsTypeSRV -> do namptr <- #{peek DNS_RECORDA, Data.SRV.pNameTarget} p srvTarget <- Name <$> BS.packCString namptr srvPort <- #{peek DNS_RECORDA, Data.SRV.wPort} p srvWeight <- #{peek DNS_RECORDA, Data.SRV.wWeight} p srvPriority <- #{peek DNS_RECORDA, Data.SRV.wPriority} p pure $! DnsDataSRV (SRV {..}) DnsType w -> pure (DnsData w) evaluate $ force (same /= 0,DnsRecord{..}) data DnsRecord = DnsRecord { drName :: !Name , drType :: !DnsType , drTTL :: !TTL , drData :: !DnsData } deriving Show instance NFData DnsRecord where rnf (DnsRecord n y t d) = n `deepseq` y `deepseq` t `deepseq` d `deepseq` () data DnsData = DnsDataA !IPv4 | DnsDataAAAA !IPv6 | DnsDataTXT [CharStr] | DnsDataCNAME !Name | DnsDataSRV !(SRV Name) | DnsData !WORD -- unknown/unsupported deriving Show instance NFData DnsData where rnf (DnsDataA _) = () rnf (DnsDataAAAA {}) = () rnf (DnsDataTXT ts) = rnf ts rnf (DnsDataCNAME n) = rnf n rnf (DnsDataSRV srv) = rnf srv rnf (DnsData _) = () dnsDataType :: DnsData -> DnsType dnsDataType DnsDataA {} = DnsTypeA dnsDataType DnsDataAAAA {} = DnsTypeAAAA dnsDataType DnsDataTXT {} = DnsTypeTXT dnsDataType DnsDataCNAME {} = DnsTypeCNAME dnsDataType DnsDataSRV {} = DnsTypeSRV dnsDataType (DnsData w) = DnsType w data DnsType = DnsTypeA | DnsTypeAAAA | DnsTypeTXT | DnsTypeCNAME | DnsTypeSRV | DnsType !WORD deriving (Show) instance NFData DnsType where rnf t = seq t () eqType :: DnsType -> DnsType -> Bool eqType x y = fromDnsType x == fromDnsType y fromDnsType :: DnsType -> WORD fromDnsType x = case x of DnsTypeA -> #const DNS_TYPE_A DnsTypeAAAA -> #const DNS_TYPE_AAAA DnsTypeTXT -> #const DNS_TYPE_TEXT DnsTypeCNAME -> #const DNS_TYPE_CNAME DnsTypeSRV -> #const DNS_TYPE_SRV DnsType w -> w toDnsType :: WORD -> DnsType toDnsType w = case w of #{const DNS_TYPE_A } -> DnsTypeA #{const DNS_TYPE_AAAA} -> DnsTypeAAAA #{const DNS_TYPE_TEXT} -> DnsTypeTXT #{const DNS_TYPE_CNAME} -> DnsTypeCNAME #{const DNS_TYPE_SRV} -> DnsTypeSRV _ -> DnsType w #if !MIN_VERSION_base(4,7,0) byteSwap32 :: Word32 -> Word32 byteSwap32 x = ((x .&. 0x000000FF) `shiftL` 24) .|. ((x .&. 0x0000FF00) `shiftL` 8) .|. ((x .&. 0x00FF0000) `shiftR` 8) .|. ((x .&. 0xFF000000) `shiftR` 24); #endif