module Network.DNS.Internal where
import Control.Exception (Exception)
import Control.Applicative
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BS
import Data.Char (toUpper)
import Data.IP (IPv4, IPv6)
import Data.Maybe (fromMaybe)
import Data.Typeable (Typeable)
import Data.Foldable (Foldable)
import Data.Traversable
type Domain = ByteString
data TYPE = A | AAAA | NS | TXT | MX | CNAME | SOA | PTR | SRV | DNAME
| UNKNOWN Int deriving (Eq, Show, Read)
rrDB :: [(TYPE, Int)]
rrDB = [
(A, 1)
, (NS, 2)
, (CNAME, 5)
, (SOA, 6)
, (PTR, 12)
, (MX, 15)
, (TXT, 16)
, (AAAA, 28)
, (SRV, 33)
, (DNAME, 39)
]
rookup :: (Eq b) => b -> [(a,b)] -> Maybe a
rookup _ [] = Nothing
rookup key ((x,y):xys)
| key == y = Just x
| otherwise = rookup key xys
intToType :: Int -> TYPE
intToType n = fromMaybe (UNKNOWN n) $ rookup n rrDB
typeToInt :: TYPE -> Int
typeToInt (UNKNOWN x) = x
typeToInt t = fromMaybe 0 $ lookup t rrDB
toType :: String -> TYPE
toType = read . map toUpper
data DNSError =
SequenceNumberMismatch
| TimeoutExpired
| UnexpectedRDATA
| IllegalDomain
| FormatError
| ServerFailure
| NameError
| NotImplemented
| OperationRefused
deriving (Eq, Show, Typeable)
instance Exception DNSError
data DNSMessage a = DNSFormat {
header :: DNSHeader
, question :: [Question]
, answer :: [RR a]
, authority :: [RR a]
, additional :: [RR a]
} deriving (Eq, Show, Functor, Foldable)
type DNSFormat = DNSMessage RDATA
instance Traversable DNSMessage where
sequenceA dns = liftA3 build answer' authority' additional'
where
answer' = traverse sequenceA $ answer dns
authority' = traverse sequenceA $ authority dns
additional' = traverse sequenceA $ additional dns
build ans auth add = cast { answer = ans
, authority = auth
, additional = add }
where
cast = error "unhandled case in sequenceA (DNSMessage)" <$> dns
dnsMapWithType :: (TYPE -> a -> b) -> DNSMessage a -> DNSMessage b
dnsMapWithType parse dns =
cast { answer = mapParse $ answer dns
, authority = mapParse $ authority dns
, additional = mapParse $ additional dns
}
where
cast = error "unhandled case in dnsMapWithType" <$> dns
mapParse = map (rrMapWithType parse)
dnsTraverseWithType ::
Applicative f =>
(TYPE -> a -> f b) -> DNSMessage a -> f (DNSMessage b)
dnsTraverseWithType parse = sequenceA . dnsMapWithType parse
data DNSHeader = DNSHeader {
identifier :: Int
, flags :: DNSFlags
, qdCount :: Int
, anCount :: Int
, nsCount :: Int
, arCount :: Int
} deriving (Eq, Show)
data DNSFlags = DNSFlags {
qOrR :: QorR
, opcode :: OPCODE
, authAnswer :: Bool
, trunCation :: Bool
, recDesired :: Bool
, recAvailable :: Bool
, rcode :: RCODE
} deriving (Eq, Show)
data QorR = QR_Query | QR_Response deriving (Eq, Show)
data OPCODE = OP_STD | OP_INV | OP_SSR deriving (Eq, Show, Enum)
data RCODE = NoErr | FormatErr | ServFail | NameErr | NotImpl | Refused deriving (Eq, Show, Enum)
data Question = Question {
qname :: Domain
, qtype :: TYPE
} deriving (Eq, Show)
makeQuestion :: Domain -> TYPE -> Question
makeQuestion = Question
data RR a = ResourceRecord {
rrname :: Domain
, rrtype :: TYPE
, rrttl :: Int
, rdlen :: Int
, rdata :: a
} deriving (Eq, Show, Functor, Foldable)
type ResourceRecord = RR RDATA
data RD a = RD_NS Domain | RD_CNAME Domain | RD_DNAME Domain
| RD_MX Int Domain | RD_PTR Domain
| RD_SOA Domain Domain Int Int Int Int Int
| RD_A IPv4 | RD_AAAA IPv6 | RD_TXT ByteString
| RD_SRV Int Int Int Domain
| RD_OTH a deriving (Eq, Functor, Foldable)
type RDATA = RD [Int]
instance Traversable RD where
sequenceA (RD_OTH a) = RD_OTH <$> a
sequenceA rd = pure cast
where
cast = error "unhandled case in squenceA (RD)" <$> rd
instance Show a => Show (RD a) where
show (RD_NS dom) = BS.unpack dom
show (RD_MX prf dom) = BS.unpack dom ++ " " ++ show prf
show (RD_CNAME dom) = BS.unpack dom
show (RD_DNAME dom) = BS.unpack dom
show (RD_A a) = show a
show (RD_AAAA aaaa) = show aaaa
show (RD_TXT txt) = BS.unpack txt
show (RD_SOA mn _ _ _ _ _ mi) = BS.unpack mn ++ " " ++ show mi
show (RD_PTR dom) = BS.unpack dom
show (RD_SRV pri wei prt dom) = show pri ++ " " ++ show wei ++ " " ++ show prt ++ BS.unpack dom
show (RD_OTH is) = show is
instance Traversable RR where
sequenceA rr = (\x -> fmap (const x) rr) <$> rdata rr
rrMapWithType :: (TYPE -> a -> b) -> RR a -> RR b
rrMapWithType parse rr = parse (rrtype rr) <$> rr
defaultQuery :: DNSFormat
defaultQuery = DNSFormat {
header = DNSHeader {
identifier = 0
, flags = DNSFlags {
qOrR = QR_Query
, opcode = OP_STD
, authAnswer = False
, trunCation = False
, recDesired = True
, recAvailable = False
, rcode = NoErr
}
, qdCount = 0
, anCount = 0
, nsCount = 0
, arCount = 0
}
, question = []
, answer = []
, authority = []
, additional = []
}
defaultResponse :: DNSFormat
defaultResponse =
let hd = header defaultQuery
flg = flags hd
in defaultQuery {
header = hd {
flags = flg {
qOrR = QR_Response
, authAnswer = True
, recAvailable = True
}
}
}
responseA :: Int -> Question -> IPv4 -> DNSFormat
responseA ident q ip =
let hd = header defaultResponse
dom = qname q
an = ResourceRecord dom A 300 4 (RD_A ip)
in defaultResponse {
header = hd { identifier=ident, qdCount = 1, anCount = 1 }
, question = [q]
, answer = [an]
}
responseAAAA :: Int -> Question -> IPv6 -> DNSFormat
responseAAAA ident q ip =
let hd = header defaultResponse
dom = qname q
an = ResourceRecord dom AAAA 300 16 (RD_AAAA ip)
in defaultResponse {
header = hd { identifier=ident, qdCount = 1, anCount = 1 }
, question = [q]
, answer = [an]
}