dnsbase-1.0.0.0: src/Net/DNSBase/Resolver.hs
{-|
Module : Net.DNSBase.Resolver
Description : Stub resolver configuration and per-thread handles
Copyright : (c) IIJ Innovation Institute Inc., 2009
(c) Viktor Dukhovni, 2020-2026
License : BSD-3-Clause
Maintainer : ietf-dane@dukhovni.org
Stability : unstable
The resolver is built in three stages:
1. 'ResolverConf' carries the caller's choices: nameserver
source, per-attempt timeout, retry budget, default
'QueryControls', and any user-registered RR-type or EDNS
option codecs. Build one by adjusting fields of
'defaultResolvConf'.
2. 'makeResolvSeed' turns a 'ResolverConf' into a 'ResolvSeed':
nameserver hostnames are resolved to addresses, and the
user's codec registrations are merged with the library's
built-in defaults. The seed is immutable and safe to share
across threads.
3. 'withResolver' produces a per-thread 'Resolver' handle from
a shared seed. A 'Resolver' carries thread-local mutable
state (a CSPRNG for query IDs) and /must not/ be shared
between threads — programs that issue queries concurrently
should call 'withResolver' once per worker thread.
A minimal example:
> main :: IO ()
> main = do
> seed <- makeResolvSeed defaultResolvConf >>= either throwIO pure
> withResolver seed \ r ->
> lookupA r $$(dnLit8 "example.com") >>= \ case
> Left e -> throwIO e
> Right a -> print a
The codec set baked into the seed can be extended at
conf-build time via 'registerRRtype' / 'registerEdnsOption'
(new RR-type or EDNS-option codecs) and the four
'extendRRwithType' / 'extendRRwithValue' /
'extendEdnsOptionWithType' / 'extendEdnsOptionWithValue'
combinators (extensions onto codecs that admit them). See
"Net.DNSBase.Extensible" for worked examples.
-}
{-# LANGUAGE RecordWildCards #-}
module Net.DNSBase.Resolver
( -- * Resolver configuration
ResolverConf
, defaultResolvConf
, setResolverConfTimeout
, setResolverConfRetries
, setResolverConfQueryControls
, setResolverConfSource
, NameserverConf(..)
, NameserverSpec(..)
-- * Resolver seed
, ResolvSeed
, makeResolvSeed
-- * Resolver instance
, Resolver(resolvSeed, resolvRng)
, withResolver
-- * Look up 'RRTYPE' by name.
, RRtypeNames
, confTypeNames
, rrtypeLookup
-- * Controls.
-- ** Query controls.
, QueryControls(..)
, EdnsControls
-- ** Extending the codec set.
--
-- $extending
, TypeExtensible(..)
, KnownRData(..)
, registerRRtype
, extendRRwithType
, extendRRwithValue
, KnownEdnsOption(..)
, registerEdnsOption
, extendEdnsOptionWithType
, extendEdnsOptionWithValue
-- * Chained-composition opt-in
--
-- $dnsio
, DNSIO
, runDNSIO
, liftDNS
) where
import qualified Data.ByteString as B
import qualified Data.ByteString.Builder.Extra as B
import qualified Data.ByteString.Lazy as LB
import qualified Data.ByteString.Short as SB
import qualified Data.IntMap.Strict as IM
import qualified Data.Map.Strict as M
import qualified Data.Type.Equality as R
import qualified Type.Reflection as R
import Data.Char (chr)
import Data.String (fromString)
import Data.Void (Void)
import Network.Socket ( AddrInfo(..), AddrInfoFlag(..), HostName, PortNumber )
import Network.Socket ( ServiceName, SocketType(Datagram) )
import Network.Socket ( defaultHints, getAddrInfo )
import Numeric (readDec)
import Numeric.Natural (Natural)
import GHC.IO.Exception (IOErrorType(..))
import System.IO.Error (ioeSetErrorString, mkIOError, tryIOError)
import Net.DNSBase.Decode.Internal.Option
import Net.DNSBase.Decode.Internal.State
import Net.DNSBase.EDNS.Internal.Option
import Net.DNSBase.EDNS.Internal.OptNum
import Net.DNSBase.Encode.Internal.State
import Net.DNSBase.Internal.Error
import Net.DNSBase.Internal.Present
import Net.DNSBase.Internal.RData
import Net.DNSBase.Internal.RRTYPE
import Net.DNSBase.Internal.Util
import Net.DNSBase.Nat16
import Net.DNSBase.Resolver.Internal.Parser
import Net.DNSBase.Resolver.Internal.Types
import Net.DNSBase.EDNS.Option.ECS
import Net.DNSBase.EDNS.Option.EDE
import Net.DNSBase.EDNS.Option.NSID
import Net.DNSBase.EDNS.Option.Secalgs
import Net.DNSBase.Extensible (TypeExtensible(..), ValueExtensible(..))
-- Built-in RData type modules
import Net.DNSBase.RData.A
import Net.DNSBase.RData.CAA
import Net.DNSBase.RData.CSYNC
import Net.DNSBase.RData.Dnssec
-- Re-exported by Dnssec
-- import Net.DNSBase.RData.NSEC
import Net.DNSBase.RData.Obsolete
import Net.DNSBase.RData.SOA
import Net.DNSBase.RData.SRV
import Net.DNSBase.RData.SVCB
import Net.DNSBase.RData.TLSA
import Net.DNSBase.RData.TXT
-- Re-exported by Obsolete
-- import Net.DNSBase.RData.WKS
import Net.DNSBase.RData.XNAME
----
-- $dnsio
-- The primary API (e.g. 'makeResolvSeed', 'Net.DNSBase.Lookup.lookupAnswers') returns
-- @'IO' ('Either' 'DNSError' a)@: each call's error half is explicit
-- at the type level and the user's surrounding code stays in plain
-- 'IO'. For programs that prefer transformer-style composition of
-- many DNS calls with short-circuit error handling, 'DNSIO' is a thin
-- alias for @'ExceptT' 'DNSError' 'IO'@; 'runDNSIO' and 'liftDNS'
-- convert between the two forms.
-- Set Resolver timeout
setResolverConfTimeout :: Int -> ResolverConf -> ResolverConf
setResolverConfTimeout t rc = rc {rcTimeout = t}
-- Set Resolver retries
setResolverConfRetries :: Int -> ResolverConf -> ResolverConf
setResolverConfRetries n rc = rc {rcRetries = n}
-- Set Resolver configuration source
setResolverConfSource :: NameserverConf -> ResolverConf -> ResolverConf
setResolverConfSource s rc = rc {rcSource = s}
-- Set Resolver query controls
setResolverConfQueryControls :: QueryControls -> ResolverConf -> ResolverConf
setResolverConfQueryControls q rc = rc {rcQryCtls = q}
-- $extending
-- The resolver knows about a set of RR-type and EDNS-option
-- codecs at conf-build time. 'registerRRtype' and
-- 'registerEdnsOption' install a fresh codec entry at the
-- type's default extension value; the four
-- 'extendRRwithType' / 'extendRRwithValue' /
-- 'extendEdnsOptionWithType' / 'extendEdnsOptionWithValue'
-- combinators fold typed or value-driven extensions onto an
-- already-extensible codec's existing entry. See
-- "Net.DNSBase.Extensible" for worked examples and the design
-- behind the two extension flavours.
--
-- User registrations take precedence over the library's
-- built-in codec set, except at a small number of /protected/
-- code points (e.g. the SVCB @mandatory@ key), where attempted
-- user additions are silently ignored.
-- | Register a decoder for an RR-type.
-- If the RR's data type is itself extensible, you can
-- use 'extendRRwithType' or 'extendRRwithValue' to
-- apply additional extensions on top.
--
-- The registration takes precedence over the library's built-in
-- codec at the same RR-type code, except at protected code
-- points (e.g. RR-type 0 and 65535, or the OPT pseudo-RR), where
-- the registration is silently ignored.
registerRRtype :: forall a -> KnownRData a
=> ResolverConf -> ResolverConf
registerRRtype a rc =
rc { rcRDataMap = IM.insert key entry (rcRDataMap rc) }
where
key = fromIntegral @Word16 . coerce $ rdType a
entry = RDataCodec (Proxy @a) (rdataExtensionVal a)
-- | Extend the registered codec of a type-extensible RR type @t@
-- with an additional typed extension @b@. If @t@ is not yet
-- known it is automatically registered, in either case 'extendByType'
-- is then applied to the existing decoder state to fold in @b@.
--
-- This is how one adds an extra SvcParam-key decoder for SVCB
-- and/or HTTPS records.
--
-- > conf
-- > & extendRRwithType T_svcb MyParamType
-- > & extendRRwithType T_https MyParamType
--
extendRRwithType :: forall t ->
( KnownRData t
, TypeExtensible t (RDataExtensionVal t)
)
=> forall b -> TypeExtensionArg t b
=> ResolverConf -> ResolverConf
extendRRwithType t b rc =
rc { rcRDataMap = IM.insert key entry (rcRDataMap rc) }
where
key = fromIntegral @Word16 . coerce $ rdType t
baseline = case IM.lookup key (rcRDataMap rc) of
Just (RDataCodec (_ :: Proxy a) opts)
| Just R.Refl <- R.testEquality (R.typeRep @t) (R.typeRep @a)
-> opts
_ -> rdataExtensionVal t
entry = RDataCodec (Proxy @t) (extendByType t b baseline)
-- | Register an EDNS option decoder.
-- If the EDNS option\'s data type is itself extensible, you can
-- use 'extendEdnsOptionWithType' or 'extendEdnsOptionWithValue' to
-- apply additional extensions on top.
--
-- The registration takes precedence over the library's built-in
-- decoder at the same option code (if any) after the merge step
-- in 'makeResolvSeed'.
registerEdnsOption :: forall a -> KnownEdnsOption a
=> ResolverConf -> ResolverConf
registerEdnsOption a rc =
rc { rcOptionMap = IM.insert key entry (rcOptionMap rc) }
where
key = fromIntegral @Word16 . coerce $ optNum a
entry = OptionCodec (Proxy @a) (optionExtensionVal a)
-- | Extend the registered decoder for a type-extensible EDNS option
-- type @t@ with an additional typed extension @b@. If @t@ is not
-- yet present in the resolver configuration, it is first registered,
-- in either case 'extendByType' is then applied fold in @b@.
--
-- This is the EDNS-option-side parallel to 'extendRRwithType'.
extendEdnsOptionWithType :: forall t ->
( KnownEdnsOption t
, TypeExtensible t (OptionExtensionVal t)
)
=> forall b -> TypeExtensionArg t b
=> ResolverConf -> ResolverConf
extendEdnsOptionWithType t b rc =
rc { rcOptionMap = IM.insert key entry (rcOptionMap rc) }
where
key = fromIntegral @Word16 . coerce $ optNum t
baseline = case IM.lookup key (rcOptionMap rc) of
Just (OptionCodec (_ :: Proxy a) opts)
| Just R.Refl <- R.testEquality (R.typeRep @t) (R.typeRep @a)
-> opts
_ -> optionExtensionVal t
entry = OptionCodec (Proxy @t) (extendByType t b baseline)
-- | Extend the registered codec for RR type @t@ with a
-- caller-supplied value @v@, whose type satisfies @t@'s
-- 'ValueExtensionArg' constraint. Parallel to
-- 'extendRRwithType', but for instances whose extension table
-- is keyed by runtime data rather than user-supplied types.
extendRRwithValue :: forall t ->
( KnownRData t
, ValueExtensible t (RDataExtensionVal t)
)
=> forall b. ValueExtensionArg t b
=> b -> ResolverConf -> ResolverConf
extendRRwithValue t v rc =
rc { rcRDataMap = IM.insert key entry (rcRDataMap rc) }
where
key = fromIntegral @Word16 . coerce $ rdType t
baseline = case IM.lookup key (rcRDataMap rc) of
Just (RDataCodec (_ :: Proxy a) opts)
| Just R.Refl <- R.testEquality (R.typeRep @t) (R.typeRep @a)
-> opts
_ -> rdataExtensionVal t
entry = RDataCodec (Proxy @t) (extendByValue t v baseline)
-- | Extend the registered codec for EDNS option type @t@ with
-- a caller-supplied value @v@, whose type satisfies @t@'s
-- 'ValueExtensionArg' constraint. The EDNS-option-side
-- parallel to 'extendRRwithValue'. This is the canonical way
-- to add an EDE info-code → friendly-name mapping:
--
-- > conf
-- > & extendEdnsOptionWithValue O_ede (33, "Frobnicated")
-- > & extendEdnsOptionWithValue O_ede (34, "Bogosity")
extendEdnsOptionWithValue :: forall t ->
( KnownEdnsOption t
, ValueExtensible t (OptionExtensionVal t)
)
=> forall b. ValueExtensionArg t b
=> b -> ResolverConf -> ResolverConf
extendEdnsOptionWithValue t v rc =
rc { rcOptionMap = IM.insert key entry (rcOptionMap rc) }
where
key = fromIntegral @Word16 . coerce $ optNum t
baseline = case IM.lookup key (rcOptionMap rc) of
Just (OptionCodec (_ :: Proxy a) opts)
| Just R.Refl <- R.testEquality (R.typeRep @t) (R.typeRep @a)
-> opts
_ -> optionExtensionVal t
entry = OptionCodec (Proxy @t) (extendByValue t v baseline)
-- | Build a 'ResolvSeed' from a 'ResolverConf'. The seed is
-- immutable and safely shared across threads; each thread then
-- calls 'withResolver' to obtain its own 'Resolver'.
--
-- The configured nameservers are resolved to socket addresses,
-- and the user's codec registrations (from 'registerRRtype',
-- 'extendRRwithType' and 'registerEdnsOption') are combined with the
-- library's built-in codec set. At each RR-type or EDNS option
-- code point, the user-registered codec takes precedence over the
-- library default — except at a small set of /protected/ code
-- points (e.g. the SVCB @mandatory@ key), where any attempted
-- user override is silently ignored.
--
-- Returns @'Left' err@ if the configured nameservers cannot be
-- resolved or the configuration file cannot be parsed.
--
-- Example:
--
-- >>> seed <- makeResolvSeed defaultResolvConf >>= either throwIO pure
--
makeResolvSeed :: ResolverConf -> IO (Either DNSError ResolvSeed)
makeResolvSeed conf = runDNSIO do
seedServers <- findAddresses
let !seedRDataMap = reservedCodecs
`IM.union` (rcRDataMap conf `IM.union` defaultCodecs)
!seedOptionMap = rcOptionMap conf `IM.union` baseOptions
seedConfig = conf
pure ResolvSeed{..}
where
findAddresses :: DNSIO (NonEmpty Nameserver)
findAddresses = case rcSource conf of
HostList rs -> join <$> mapM getNameserverAddresses rs
SourceFile file -> getDefaultNameservers file >>= mkAddrs
getNameserverAddresses (NameserverSpec h mp) = makeAddrInfo (Just h) mp
-- When /etc/resolv.conf contains no addresses, default to the loopback address,
-- by passing 'Nothing' for the server name.
mkAddrs [] = makeAddrInfo Nothing Nothing
mkAddrs (l:ls) = join <$> mapM getNameserverAddresses (l :| ls)
-- | Default resolver configuration, with nameserver list from
-- @\/etc\/resolv.conf@ and no user-registered codec extensions.
defaultResolvConf :: ResolverConf
defaultResolvConf = ResolverConf
{ rcSource = SourceFile "/etc/resolv.conf"
, rcTimeout = 3_000_000 -- 3 seconds
, rcRetries = 3
, rcQryCtls = mempty
, rcRDataMap = IM.empty
, rcOptionMap = IM.empty
}
-- | Determines whether a HostName is a valid IPv4 or IPv6 address
--
-- Also false if input is an IPv4 or IPv6 address with trailing characters,
-- or in the (impossible) case of multiple valid parses
isAddr :: HostName -> Bool
isAddr addr =
case reads @IP addr of
[(_,r)] -> null r
_ -> False
makeAddrInfo :: Maybe HostName -> Maybe PortNumber -> DNSIO (NonEmpty Nameserver)
makeAddrInfo maddr mport = do
let flags | addrLiteral = AI_NUMERICHOST : defaultFlags
| otherwise = defaultFlags
hints = defaultHints {addrFlags = flags, addrSocketType = Datagram}
serv = maybe "53" show mport
-- getAddrInfo should never return an empty list (it raises an IO exception instead),
-- but just in case, handle empty results.
withExceptT BadNameserver (getAddrInfo' hints maddr serv) >>= \ case
a : as -> pure $ Nameserver addrName <$> a :| as
_ -> let host = fromMaybe defaultHostName maddr
ioe = mkIOError NoSuchThing host Nothing Nothing
in throwE $ BadNameserver $ ioeSetErrorString ioe "Host unknown"
where
defaultFlags = [AI_NUMERICSERV, AI_ADDRCONFIG]
defaultHostName = "localhost"
addrLiteral = maybe False isAddr maddr
addrName | addrLiteral = Nothing
| otherwise = maddr <|> Just defaultHostName
getAddrInfo' :: AddrInfo -> Maybe HostName -> ServiceName -> ExceptT IOError IO [AddrInfo]
getAddrInfo' h a s = ExceptT $ tryIOError (getAddrInfo (Just h) a (Just s))
---------- RRTYPE lookups
-- | Mapping from @RRTYPE@ name to 'RRTYPE' code.
newtype RRtypeNames = RRNames_ (M.Map SB.ShortByteString RRTYPE)
-- | Attempt to find an RRTYPE' by name. The lookup map can be constructed
-- via 'confTypeNames', and should be reused for multiple lookups when
-- possible.
--
-- - The input name is not case-senstive.
-- - Names of the form @TYPE@/num/ (with /num/ the type number) are supported,
-- and return the corresponding 'RRTYPE'.
rrtypeLookup :: B.ByteString
-> RRtypeNames
-> Maybe RRTYPE
rrtypeLookup ((,) <$> B.length <*> B.unpack -> (len, ws)) (coerce -> m)
| t@(Just _) <- M.lookup name m
= t
| SB.isPrefixOf rrtypePrefix name
, digits <- map (chr . fromIntegral) $ drop (SB.length rrtypePrefix) ws
, [(w, "")] <- readDec @Natural digits
, w <= fromIntegral @Word16 @Natural maxBound
= Just $! RRTYPE $ fromIntegral w
| otherwise
= Nothing
where
name = foldShort len ws
-- | Construct a map of type names to 'RRTYPE' from a given
-- 'ResolvSeed' value. This will include both the names of all
-- the registered known types and the names of all known RRtypes,
-- whether implemented or not.
--
-- The map is is not cached, compute it once and reuse for
-- repeated queries.
--
confTypeNames :: Maybe ResolvSeed -> RRtypeNames
confTypeNames cnf =
coerce $ maybe M.empty cnfMap cnf <> M.fromList knownNames
where
cnfMap ResolvSeed {seedRDataMap = dm} =
M.fromList $ map (uncurry mkPair) $ IM.toList dm
where
mkPair k (RDataCodec p _) =
(proxyName p, RRTYPE $ fromIntegral k)
proxyName :: forall a. KnownRData a
=> Proxy a -> SB.ShortByteString
proxyName _ = buildShort $ rdTypePres a mempty
knownNames = [ (name, t)
| t <- [A .. rrtypeMax]
, let name = buildShort $ present t mempty
, not $ SB.isPrefixOf rrtypePrefix name ]
buildShort = (foldShort <$> LB.length <*> LB.unpack) . buildLazy
buildLazy = B.toLazyByteStringWith (B.untrimmedStrategy 16 32) mempty
foldShort :: Integral a => a -> [Word8] -> SB.ShortByteString
foldShort len = fst <$> SB.unfoldrN (fromIntegral len) low8
where
low8 [] = Nothing
low8 (w:ws) | w - 0x41 < 26 = Just (w + 0x20, ws)
| otherwise = Just (w, ws)
rrtypePrefix :: SB.ShortByteString
rrtypePrefix = fromString "type"
-------------------------------
--- RData and Option codec maps
-- | Placeholder for reserved RRTYPEs.
type Reserved :: Nat -> Type
data Reserved n = Reserved_ Void deriving (Eq, Ord, Show)
instance (Nat16 n) => KnownRData (Reserved n) where
rdType _ = RRTYPE $ natToWord16 n
rdTypePres _ = present @String "Reserved" . present (natToWord16 n)
rdEncode _ = failWith $ ReservedType $ RRTYPE $ natToWord16 n
rdDecode _ _ = const do
failSGet $ "Reserved RDATA type: " ++ show (natToWord16 n)
instance (Nat16 n) => Presentable (Reserved n) where
present _ = undefined
-- Internal helper: build the 'RDataMap' entry for type @a@ from
-- its 'Net.DNSBase.RData.rdataExtensionVal'.
rdataMapEntry :: forall a -> KnownRData a => (Int, RDataCodec)
rdataMapEntry a =
( fromIntegral @Word16 . coerce $ rdType a
, RDataCodec (Proxy @a) (rdataExtensionVal a)
)
-- | Reserved RR-type slots: codes that the protocol forbids as
-- RDATA (RFC 6895 reserved code 0 and sentinel 65535), that the
-- library handles outside the RDATA codec map (OPT, code 41), or
-- that are meta-query types with no carriable RDATA (NXNAME,
-- TKEY, TSIG, IXFR, AXFR, MAILB, MAILA, ANY).
--
-- The merge step in 'Net.DNSBase.Resolver.makeResolvSeed' protects these entries from
-- user override: any user-supplied registration at one of these
-- codes is dropped in favour of the reserved entry.
reservedCodecs :: RDataMap
reservedCodecs = IM.fromList
[ rdataMapEntry (Reserved 0) -- 0 RFC6895
, rdataMapEntry (Reserved 41) -- 41 OPT (hardwired)
---- Special-use types
, rdataMapEntry (Reserved 128) -- NXNAME
, rdataMapEntry (Reserved 249) -- TKEY
, rdataMapEntry (Reserved 250) -- TSIG
, rdataMapEntry (Reserved 251) -- IXFR
, rdataMapEntry (Reserved 252) -- AXFR
---- Query-only types
, rdataMapEntry (Reserved 253) -- MAILB
, rdataMapEntry (Reserved 254) -- MAILA
, rdataMapEntry (Reserved 255) -- ANY
----
, rdataMapEntry (Reserved 65535) -- Reserved
]
-- | Built-in default codecs for the RR-types the library decodes
-- natively. Disjoint from 'reservedCodecs'.
--
-- These act as fallbacks: a user-supplied registration for any
-- of these RR-types takes precedence over the built-in entry.
-- The merge step in 'Net.DNSBase.Resolver.makeResolvSeed' resolves overlaps in the
-- user's favour, so a caller can replace a built-in decoder with
-- their own without forking the library.
defaultCodecs :: RDataMap
defaultCodecs = IM.fromList
[ rdataMapEntry T_a -- 1
, rdataMapEntry T_ns -- 2
, rdataMapEntry T_md -- 3
, rdataMapEntry T_mf -- 4
, rdataMapEntry T_cname -- 5
, rdataMapEntry T_soa -- 6
, rdataMapEntry T_mb -- 7
, rdataMapEntry T_mg -- 8
, rdataMapEntry T_mr -- 9
, rdataMapEntry T_null -- 10
, rdataMapEntry T_wks -- 11
, rdataMapEntry T_ptr -- 12
, rdataMapEntry T_hinfo -- 13
, rdataMapEntry T_minfo -- 14
, rdataMapEntry T_mx -- 15
, rdataMapEntry T_txt -- 16
, rdataMapEntry T_rp -- 17
, rdataMapEntry T_afsdb -- 18
, rdataMapEntry T_x25 -- 19
, rdataMapEntry T_isdn -- 20
, rdataMapEntry T_rt -- 21
, rdataMapEntry T_nsap -- 22
, rdataMapEntry T_nsapptr -- 23
, rdataMapEntry T_sig -- 24
, rdataMapEntry T_key -- 25
, rdataMapEntry T_px -- 26
, rdataMapEntry T_gpos -- 27
, rdataMapEntry T_aaaa -- 28
-- 29 LOC
, rdataMapEntry T_nxt -- 30
-- 31 EID
-- 32 NIMLOC
, rdataMapEntry T_srv -- 33
-- 34 ATMA
, rdataMapEntry T_naptr -- 35
, rdataMapEntry T_kx -- 36
-- 37 CERT
, rdataMapEntry T_a6 -- 38
, rdataMapEntry T_dname -- 39
-- 40 SINK
-- 42 APL
, rdataMapEntry T_ds -- 43
, rdataMapEntry T_sshfp -- 44
, rdataMapEntry T_ipseckey -- 45 IPSECKEY
, rdataMapEntry T_rrsig -- 46
, rdataMapEntry T_nsec -- 47
, rdataMapEntry T_dnskey -- 48
-- 49 DHCID
, rdataMapEntry T_nsec3 -- 50
, rdataMapEntry T_nsec3param -- 51
, rdataMapEntry T_tlsa -- 52
, rdataMapEntry T_smimea -- 53
-- 54 Unassigned
-- 55 HIP
-- 56 NINFO
-- 57 RKEY
-- 58 TALINK
, rdataMapEntry T_cds -- 59
, rdataMapEntry T_cdnskey -- 60
, rdataMapEntry T_openpgpkey -- 61
, rdataMapEntry T_csync -- 62 CSYNC
, rdataMapEntry T_zonemd -- 63
, rdataMapEntry T_svcb -- 64
, rdataMapEntry T_https -- 65
, rdataMapEntry T_dsync -- 66
-- 67 HHIT
-- 68 BRID
-- 99 SPF
, rdataMapEntry T_nid -- 104 NID
, rdataMapEntry T_l32 -- 105 L32
, rdataMapEntry T_l64 -- 106 L64
, rdataMapEntry T_lp -- 107 LP
-- 108 EUI48 [RFC7043]
-- 109 EUI64 [RFC7043]
, rdataMapEntry T_caa -- 257
, rdataMapEntry T_amtrelay -- 260
-- 261 RESINFO
-- 262 WALLET
-- 263 CLA
-- 264 IPN
]
-- | Built-in EDNS option codecs. User registrations from
-- 'Net.DNSBase.Resolver.registerEdnsOption' /
-- 'Net.DNSBase.Resolver.extendEdnsOptionWithType' take
-- precedence over entries here after the merge step in
-- 'Net.DNSBase.Resolver.makeResolvSeed'.
baseOptions :: OptionMap
baseOptions = IM.fromList
[ optMapEntry O_nsid -- 3 NSID
, optMapEntry O_dau -- 5 DAU
, optMapEntry O_dhu -- 6 DHU
, optMapEntry O_n3u -- 7 N3U
, optMapEntry O_ecs -- 8 ECS
, optMapEntry O_ede -- 15 EDE
]
where
optMapEntry :: forall a -> KnownEdnsOption a => (Int, OptionCodec)
optMapEntry a =
( fromIntegral @Word16 . coerce $ optNum a
, OptionCodec (Proxy @a) (optionExtensionVal a)
)