module Network.DNS.Internal where
import Control.Exception (Exception)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Builder as L
import qualified Data.ByteString.Lazy as L
import Data.IP (IP, IPv4, IPv6)
import Data.Maybe (fromMaybe)
import Data.Typeable (Typeable)
import Data.Word (Word8)
type Domain = ByteString
type Query = L.ByteString
data TYPE = A
| AAAA
| NS
| TXT
| MX
| CNAME
| SOA
| PTR
| SRV
| DNAME
| OPT
| DS
| RRSIG
| NSEC
| DNSKEY
| NSEC3
| NSEC3PARAM
| TLSA
| CDS
| CDNSKEY
| CSYNC
| 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)
, (OPT, 41)
, (DS, 43)
, (RRSIG, 46)
, (NSEC, 47)
, (DNSKEY, 48)
, (NSEC3, 40)
, (NSEC3PARAM, 51)
, (TLSA, 52)
, (CDS, 59)
, (CDNSKEY, 60)
, (CSYNC, 62)
]
data OPTTYPE = ClientSubnet
| OUNKNOWN Int
deriving (Eq)
orDB :: [(OPTTYPE, Int)]
orDB = [
(ClientSubnet, 8)
]
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 (error "typeToInt") $ lookup t rrDB
intToOptType :: Int -> OPTTYPE
intToOptType n = fromMaybe (OUNKNOWN n) $ rookup n orDB
optTypeToInt :: OPTTYPE -> Int
optTypeToInt (OUNKNOWN x) = x
optTypeToInt t = fromMaybe (error "optTypeToInt") $ lookup t orDB
data DNSError =
SequenceNumberMismatch
| RetryLimitExceeded
| TimeoutExpired
| UnexpectedRDATA
| IllegalDomain
| FormatError
| ServerFailure
| NameError
| NotImplemented
| OperationRefused
| BadOptRecord
deriving (Eq, Show, Typeable)
instance Exception DNSError
data DNSMessage = DNSMessage {
header :: DNSHeader
, question :: [Question]
, answer :: [ResourceRecord]
, authority :: [ResourceRecord]
, additional :: [ResourceRecord]
} deriving (Eq, Show)
type DNSFormat = DNSMessage
data DNSHeader = DNSHeader {
identifier :: Int
, flags :: DNSFlags
} deriving (Eq, Show)
data DNSFlags = DNSFlags {
qOrR :: QorR
, opcode :: OPCODE
, authAnswer :: Bool
, trunCation :: Bool
, recDesired :: Bool
, recAvailable :: Bool
, rcode :: RCODE
, authenData :: Bool
} deriving (Eq, Show)
data QorR = QR_Query | QR_Response deriving (Eq, Show)
data OPCODE
= OP_STD
| OP_INV
| OP_SSR
deriving (Eq, Show, Enum, Bounded)
data RCODE
= NoErr
| FormatErr
| ServFail
| NameErr
| NotImpl
| Refused
| BadOpt
deriving (Eq, Ord, Show, Enum, Bounded)
data Question = Question {
qname :: Domain
, qtype :: TYPE
} deriving (Eq, Show)
makeQuestion :: Domain -> TYPE -> Question
makeQuestion = Question
data ResourceRecord = ResourceRecord {
rrname :: Domain
, rrtype :: TYPE
, rrttl :: Int
, rdata :: RData
}
| OptRecord {
orudpsize :: Int
, ordnssecok :: Bool
, orversion :: Int
, rdata :: RData
}
deriving (Eq,Show)
data RData = 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_OPT [OData]
| RD_OTH ByteString
| RD_TLSA Word8 Word8 Word8 ByteString
deriving (Eq, Ord)
instance Show RData where
show (RD_NS dom) = BS.unpack dom
show (RD_MX prf dom) = show prf ++ " " ++ BS.unpack dom
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_OPT od) = show od
show (RD_OTH is) = show is
show (RD_TLSA use sel mtype dgst) = show use ++ " " ++ show sel ++ " " ++ show mtype ++ " " ++ (BS.unpack $ L.toStrict . L.toLazyByteString . L.byteStringHex $ dgst)
data OData = OD_ClientSubnet Int Int IP
| OD_Unknown Int ByteString
deriving (Eq,Show,Ord)
defaultQuery :: DNSMessage
defaultQuery = DNSMessage {
header = DNSHeader {
identifier = 0
, flags = DNSFlags {
qOrR = QR_Query
, opcode = OP_STD
, authAnswer = False
, trunCation = False
, recDesired = True
, recAvailable = False
, rcode = NoErr
, authenData = False
}
}
, question = []
, answer = []
, authority = []
, additional = []
}
defaultResponse :: DNSMessage
defaultResponse =
let hd = header defaultQuery
flg = flags hd
in defaultQuery {
header = hd {
flags = flg {
qOrR = QR_Response
, authAnswer = True
, recAvailable = True
, authenData = False
}
}
}
responseA :: Int -> Question -> [IPv4] -> DNSMessage
responseA ident q ips =
let hd = header defaultResponse
dom = qname q
an = fmap (ResourceRecord dom A 300 . RD_A) ips
in defaultResponse {
header = hd { identifier=ident }
, question = [q]
, answer = an
}
responseAAAA :: Int -> Question -> [IPv6] -> DNSMessage
responseAAAA ident q ips =
let hd = header defaultResponse
dom = qname q
an = fmap (ResourceRecord dom AAAA 300 . RD_AAAA) ips
in defaultResponse {
header = hd { identifier=ident }
, question = [q]
, answer = an
}