dnsbase-1.0.2.3: internal/Net/DNSBase/Decode/Internal/Message.hs
-- |
-- Module : Net.DNSBase.Decode.Internal.Message
-- Description : Decoder for the DNS message envelope (header, sections)
-- Copyright : (c) IIJ Innovation Institute Inc., 2009
-- (c) Viktor Dukhovni, 2020-2026
-- License : BSD-3-Clause
-- Maintainer : ietf-dane@dukhovni.org
-- Stability : unstable
{-# LANGUAGE RecordWildCards #-}
module Net.DNSBase.Decode.Internal.Message
( getMessage
) where
import Data.List (partition)
import Net.DNSBase.Decode.Internal.Domain
import Net.DNSBase.Decode.Internal.Option
import Net.DNSBase.Decode.Internal.RData
import Net.DNSBase.Decode.Internal.State
import Net.DNSBase.Internal.Domain
import Net.DNSBase.Internal.EDNS
import Net.DNSBase.Internal.Error
import Net.DNSBase.Internal.Flags
import Net.DNSBase.Internal.Message
import Net.DNSBase.Internal.Opcode
import Net.DNSBase.Internal.RCODE
import Net.DNSBase.Internal.RData
import Net.DNSBase.Internal.RR
import Net.DNSBase.Internal.RRCLASS
import Net.DNSBase.Internal.RRTYPE
import Net.DNSBase.Internal.Util
-- | Decoder for a complete DNSMessage, including EDNS pseudo-header information when present
getMessage :: RDataMap -> OptionMap -> SGet DNSMessage
getMessage dm om = local (setDecodeSection DnsHeaderSection) do
phd <- getPartialHeader
qdCount <- getInt16
anCount <- getInt16
nsCount <- getInt16
arCount <- getInt16
queries <- local (setDecodeSection DnsQuestionSection) $ getQueries qdCount
if | hasAnyFlags TCflag $ p_dnsMsgFl phd
-- Don't bother parsing RRs of truncated messages, we won't use
-- them, and they can be truncated in a way that raises parser
-- errors.
-> pure $ mkMsg phd No queries [] [] []
| otherwise -> do
if | q:_ <- queries -> () <$ setLastOwner (dnsTripleName q)
| otherwise -> pure ()
answers <- local (setDecodeSection DnsAnswerSection) $ getRRs dm Nothing anCount
authrrs <- local (setDecodeSection DnsAuthoritySection) $ getRRs dm Nothing nsCount
addnrrs <- local (setDecodeSection DnsAdditionalSection) $ getRRs dm (Just om) arCount
case partition isOpt addnrrs of
([], rrs) -> pure $ mkMsg phd No queries answers authrrs rrs
([optrr], rrs)
| RootDomain <- rrOwner optrr
, edns <- getEDNS optrr
-> pure $ mkMsg phd edns queries answers authrrs rrs
_ -> local (setDecodeSection DnsEDNSSection) $
failSGet "Multiple or bad additional section OPT records"
where
isOpt :: RR -> Bool
isOpt = (== OPT) . rdataType . rrData
getEDNS :: RR -> EDNSData
getEDNS rr
| Just (edns, ext_rc, ext_fl) <- optEDNS rr = Yes{..}
-- Should not happen, the OPT record should always be a T_OPT!
| otherwise = No
optEDNS :: RR -> Maybe (EDNS, Word8, Word16)
optEDNS (RR _ vcl vttl rd)
| Just (T_OPT opts) <- fromRData rd
, ext_rc <- fromIntegral $ (vttl `shiftR` 24) .&. 0xff
, vers <- fromIntegral $ (vttl `shiftR` 16) .&. 0xff
, ext_fl <- fromIntegral $ vttl .&. 0xffff
= Just (EDNS vers (coerce vcl) opts, ext_rc, ext_fl)
| otherwise
= Nothing
-- | Decoder for a list of question (query) fields appearing within a DNS
-- message. The integer parameter corresponds to the reported QDCOUNT of the
-- message, which should never be more than 1; this decoder neither tests nor
-- enforces this constraint and will attempt to decode exactly as many
-- questions as are reported to exist.
getQueries :: Int -> SGet [DnsTriple]
getQueries n = replicateM n getQuery
where
getQuery :: SGet DnsTriple
getQuery = DnsTriple <$> getDomainNC <*> getType <*> getClass
where
getType = RRTYPE <$> get16
getClass = RRCLASS <$> get16
-- | Decoder for a known-length list of resource records
getRRs :: RDataMap -> Maybe OptionMap -> Int -> SGet [RR]
getRRs dm om n = replicateM n (getRR dm om)
-- | Decoder for a 'PartialHeader' contained in the header of a DNS message
getPartialHeader :: SGet PartialHeader
getPartialHeader =
makeHeader <$> decodeMsgId <*> getOpRFlags
where
makeHeader mid (oc,rc,fl) = PartialHeader mid oc rc fl
decodeMsgId = get16
getOpRFlags :: SGet (Opcode, PartialRCODE, PartialDNSFlags)
getOpRFlags = do
raw <- get16
return $ ( extractOpcode raw
, extractRCODE raw
, makeDNSFlags raw
)
type PartialRCODE = RCODE
type PartialDNSFlags = DNSFlags
-- | Data type representing the absence or presence of
-- an OPT record, which individually represents the extended bits of
-- the DNS flags and RCODE contained in the EDNS pseudo-header
-- and the remaining EDNS data
data EDNSData = No
| Yes { ext_fl :: Word16
, ext_rc :: Word8
, edns :: EDNS
} deriving (Eq)
-- | Component of DNS message header that is extracted directly from
-- leading bytes of the DNS message (i.e. without parsing EDNS pseudo-header)
data PartialHeader = PartialHeader {
p_dnsMsgId :: QueryID
, p_dnsMsgOp :: Opcode
, p_dnsMsgRC :: PartialRCODE
, p_dnsMsgFl :: PartialDNSFlags
} deriving (Eq, Show)
-- | Assemble a 'DNSMessage' from a 'PartialHeader' (the basic DNS
-- header bits decoded earlier in the parse) and a possibly-vacuous
-- 'EDNSData'. The EDNS OPT pseudo-RR sits at the wire trailer
-- (in the additional section) but is logically header material,
-- contributing the upper bits of the extended 'RCODE' and the
-- extended flags. The questions and the three RR sections
-- complete the message.
mkMsg :: PartialHeader
-> EDNSData
-> [DnsTriple]
-> [RR] -> [RR] -> [RR]
-> DNSMessage
mkMsg PartialHeader{..} No dnsMsgQu dnsMsgAn dnsMsgNs dnsMsgAr =
DNSMessage {..}
where
dnsMsgId = p_dnsMsgId
dnsMsgOp = p_dnsMsgOp
dnsMsgRC = p_dnsMsgRC
dnsMsgFl = p_dnsMsgFl
dnsMsgEx = Nothing
mkMsg PartialHeader{..} Yes{..} dnsMsgQu dnsMsgAn dnsMsgNs dnsMsgAr =
DNSMessage {..}
where
dnsMsgId = p_dnsMsgId
dnsMsgOp = p_dnsMsgOp
dnsMsgRC = extendRCODE p_dnsMsgRC ext_rc
dnsMsgFl = extendFlags p_dnsMsgFl ext_fl
dnsMsgEx = Just edns