moon-dns-stub

Native DNS wire format codec and async stub resolver. Supports A/AAAA/MX/SRV/TXT/CNAME/NS/PTR/SOA with name compression, UDP/TCP fallback, CNAME chain, and TTL cache.

dns
network
stub-resolver
rfc1035
codec
native
moon add jinshengmeng46/moon-dns-stub@0.1.0
Download zip
Version
0.1.0
License
Apache-2.0
Last updated
6 hours ago
Downloads
4

Dependencies

README

#moon-dns-stub

一个 Native MoonBit DNS stub resolver:包含 DNS wire codec、EDNS(0)、真实 UDP 查询与 TCP fallback、CNAME 跟踪和 TTL/LRU 缓存。

当前 0.1.0 功能范围已经冻结;后续变更以修复缺陷、兼容 MoonBit 工具链和维护文档为主。

#当前支持范围

  • DNS Header、Question、完整 Message 和 name compression 的编解码;编码端会压缩重复名称。
  • A、AAAA、CNAME、NS、PTR、MX、TXT、SOA、SRV、OPT(EDNS) 与未知 RR。
  • 严格的 RDLENGTH 边界、压缩指针环/越界检查,以及 label、域名、报文资源上限;名称 API 使用可逆的 \\DDD 十进制转义,二进制 label 中的 .\\ 和控制字节不会丢失。
  • EDNS(0) 查询(默认 UDP payload 为 1232)、DO bit、OPT option 和扩展 RCODE。
  • 可注入 DnsTransport;默认实现使用 Native UDP,收到 TC 后用 DNS-over-TCP 重发原请求。 支持 timeout、retry、IPv4、[IPv6]:port、域名服务器名和默认 53 端口。
  • resolve_a/aaaa/mx/srv/txt/ns/cname/soa/ptr;请求/响应 ID、QR、opcode、question 校验;同包或跨查询 CNAME;正、负 TTL 缓存和 LRU。

这是 Native-only 的完整根包:src/moon.pkg 将 codec、cache、resolver 和 transport 共同限制为 Native。DnsTransport 注入点用于 Native mock、受控代理和自定义 Native transport;当前包不能被 JS/Wasm 下游导入。未来如需非 Native 支持,必须先把 codec、cache 和 resolver 抽取成不依赖 async socket/TLS 的独立 portable package,再分别验证各 target。

不包含 DNSSEC、DoH/DoT、mDNS 或递归权威服务器功能。

#文档

  • 总体架构:package 结构、类型所有权、数据流与设计决策。
  • 项目申报书:项目价值、交付范围、原创性和合规边界的提交版摘要。

#安装与快速开始

需要 MoonBit 0.10.3 或更新的工具链。当前仓库可直接从源码运行:

moon check --target native --warn-list +73 --deny-warn moon run --target native src/cmd/main -- resolve example.com A --server 1.1.1.1:53 --short moon run --target native src/cmd/main -- resolve example.com MX --server 8.8.8.8 --verbose moon run --target native src/cmd/main -- resolve example.com AAAA --json moon run --target native src/examples/a_aaaa -- --server 1.1.1.1:53 moon run --target native src/examples/mx_srv -- --server 8.8.8.8:53

两个示例的 --server 可省略,默认使用 1.1.1.1:53;运行 moon run --target native src/examples/a_aaaa -- --help 可查看参数。示例和 CLI 在参数非法或任一 DNS 查询失败时都会以非零状态退出,不会把失败伪装成成功的演示记录; --help--version 正常返回零。

该库已发布到 mooncakes.io。在下游项目根目录安装 v0.1.0

moon add jinshengmeng46/moon-dns-stub@0.1.0

命令格式:

resolve <name> <A|AAAA|MX|NS|CNAME|TXT|SOA|PTR|SRV> [--server <host[:port]|[ipv6]:port>] [--timeout <milliseconds>] [--short|--verbose|--json]

--short 是默认输出。--json 会转义记录文字。单次 CLI 不提供 cache-stats:缓存仅存在于 该 resolver 进程实例中,单独启动一次 CLI 查询统计没有意义。

嵌入 Native 应用时,创建 Resolver::new(options=ResolveOptions::new(...));测试或 自定义 Native 网络环境可用 Resolver::with_transport 注入自己的传输实现。

#作为 MoonBit 库使用

CLI 只是一个入口;包名为 jinshengmeng46/moon-dns-stub,核心能力以异步 MoonBit API 提供。Native 可执行 package 需要在 moon.pkg 中同时启用 async runtime:

import {
"jinshengmeng46/moon-dns-stub" @dns,
"moonbitlang/async",
}

supported_targets = "native"

pkgtype(kind: "executable")

对应的 main.mbtsrc/examples/a_aaaa 使用相同的公开接口:

async fn main {
let resolver = @dns.Resolver::new(
options=@dns.ResolveOptions::new(
server="1.1.1.1:53",
timeout_ms=3000,
retries=1,
),
)
match resolver.resolve_a("example.com") {
Ok(addresses) => println(addresses.join(", "))
Err(error) =>
raise Failure::Failure("DNS query failed: \{to_repr(error)}")
}
}

Resolver::new 使用 Native UDP,并在响应的 TC 位被置位时以 TCP 重发同一个请求。 ResolveOptions::new 可设置 servertimeout_msretries、递归位 recurse CNAME 最大深度 max_depth;省略时使用默认值。一个 resolver 实例持有自己的缓存,应该在 应用生命周期内复用,而不是每次查询都新建。

#Resolver 查询接口

所有查询都是 async,并返回 Result[..., ResolveError]NXDomainNoData、超时、 响应不匹配和 CNAME 环均是显式错误,不会被折叠成空的成功结果。

方法成功结果
resolve(name, qtype)原始 DnsResult(RR answers 与 CNAME 链)
resolve_a / resolve_aaaaArray[String] 地址
resolve_ipv4第一个 IPv4 地址 String
resolve_mxArray[(Int, String)],按 preference 升序
resolve_ns / resolve_cname / resolve_txt / resolve_ptrArray[String]
resolve_soaArray[SoaResult]
resolve_srvArray[SrvResult],按 priority 升序、同 priority 按 weight 降序
cache_stats()当前 resolver 的 CacheStats(hit、miss、entry 数)

#注入 transport(Native 测试、代理或自定义适配器)

DnsTransport 的边界是完整 DNS wire message:回调接收 resolver 构造的请求字节,必须返回 对应响应字节,或返回 TransportError。因此 Native socket、Native DoH 代理和本地 mock 都可以在调用方实现;resolver 仍负责 EDNS、响应校验、CNAME 和缓存。

// request_over_doh 接收并返回完整 DNS wire message。
let transport = @dns.DnsTransport::from_request(
payload => request_over_doh(payload),
udp_payload_size=1232,
)
let resolver = @dns.Resolver::with_transport(transport~)
let answers = resolver.resolve_aaaa("example.com")

其中 request_over_doh 的类型为 async (Array[Byte]) -> Result[Array[Byte], @dns.TransportError]udp_payload_size 会成为 resolver EDNS OPT 中通告的 UDP payload 大小。使用自定义 transport 时, ResolveOptions.server 不会替调用方发起网络连接;该连接的地址、超时与重试策略由适配器负责。 测试可进一步用 Resolver::with_transport_and_clock(transport~, clock=...) 注入兼容的 32 位单调毫秒时钟。长期运行服务应使用 Resolver::with_transport_and_clock64(transport~, clock=...);默认 resolver 和缓存内部 全程使用 Int64 单调毫秒,不会在约 24.855 天处溢出。 DNS TTL 的完整 UInt32 范围不会在高位被截短;CNAME cache 只保存相对链,并在每次顶层 查询中重建 provenance,避免别名链在不同 cache key 之间串线。

#Codec 与缓存 API

wire codec 也可独立使用,不必创建 resolver:

  • encode_name_checked / decode_name:校验并编解码 DNS 名称;
  • HeaderQuestionRRRDataMessage:表示完整 DNS 报文; Message::encode_checkeddecode_message 用于安全的完整报文编解码;
  • OptRROptOptionbuild_query_with_edns_checkedOptRR::to_rr_checked opt_from_rr:构造、读取 EDNS(0);ECS/Cookie/Keepalive 提供 checked builder,并在 builder、RR conversion、编码和解码时复核长度、prefix、RDLENGTH 与 100ms 单位;
  • DnsCache::newlookupput_positiveput_negativestats:需要单独管理缓存时使用。

所有 codec 的可变长度字段均有边界检查;格式错误返回 Err,不会把截断或非法压缩指针转换为 空记录。resolver 的默认缓存使用单调时间,并区分正缓存、NXDOMAIN 与 NODATA。 传入名称时未转义的 . 分隔 label;\\DDD 表示一个 0–255 的十进制字节,\\X 表示非数字 字节 X 的字面值(数字使用无歧义的三位写法)。decode_name 对不可直接打印的字节统一返回 \\DDD,因此 decode→encode 保持 wire 语义;大小写、十进制转义/字面转义和未转义终止根点 按 DNS 名称规则归一。
  • pretty_print_rdata:人类可读的 DNS 记录表达;short_print_rdata:面向 CLI 和 dig +short 语义差分的单行表达;pretty_print_result_json:有效 JSON 结果表达。

#Target 约束

当前发布的根包整体是 Native-only,不仅是 Resolver::new。虽然部分 codec/cache 实现逻辑本身可移植,但它们与 Native resolver 位于同一个受限 package 中,因此不能把 moon check --target all 对非 Native 显示的 no work to do 当成跨 target 验证证据。 JS/Wasm portable package 拆分和独立测试属于后续版本,不是 0.1.0 的交付承诺。

#验证

moon check --target native --warn-list +73 --deny-warn moon build --target native --warn-list +73 --deny-warn moon test --target native --warn-list +73 --deny-warn -v moon fmt --check moon package --list bash scripts/cli_failure_smoke.sh DNS_DIFF_REQUIRED=1 bash scripts/dig_diff.sh DNS_DIFF_REQUIRED=1 bash scripts/dig_diff_doh.sh

单元测试完全离线;真实差分需要 dig、Python 3 和可访问的 DNS UDP/53 或 HTTPS/443。

#协议行为约定

  • UDP 响应 TC=1 时,transport 用相同请求经 TCP 重试。
  • 查询 ID 使用 Native TLS/OpenSSL 随机源;不能产生随机数时显式报错。
  • MX 按 preference 升序;SRV 按 priority 升序、同 priority 按 weight 降序,便于调用方按需选择。
  • NXDOMAIN 与 NODATA 均作为不同的显式负结果返回(不会变成空成功);负缓存 TTL 取 authority SOA 的 TTL 与 MINIMUM 的较小值。
  • authority 中只有 IN class 的 NS 且没有 IN class SOA 的 NOERROR 空答案被视为 referral, 不会伪装成 NODATA 或写入负缓存;没有 SOA 的负响应也不会被缓存。
  • TTL=0 会立即失效,替换同 key 的旧缓存项但不会为了一个不可缓存结果驱逐其他 LRU 条目; cache hit 的公开 RR TTL 是剩余寿命而非最初 TTL。

#许可证

Apache License 2.0,见 LICENSE

本项目为原创 MoonBit 实现,不移植或复制第三方 DNS 库代码。

#
CacheLookup

pub enum CacheLookup {
Miss
Positive(DnsResult)
Negative(NegativeCacheKind, DnsResult)
}

#
CacheStats

pub struct CacheStats {
hits : Int
misses : Int
entries : Int
expired : Int
}

#
CnameTracker

pub struct CnameTracker {
max_depth : Int
chain : Array[String]
}

#
CnameTracker::contains

fn CnameTracker::contains(self : CnameTracker, name : String) -> Bool

#
CnameTracker::depth

fn CnameTracker::depth(self : CnameTracker) -> Int

#
CnameTracker::follow

fn CnameTracker::follow(self : CnameTracker, target : String) -> Result[Unit, String]

#
CnameTracker::get_chain

fn CnameTracker::get_chain(self : CnameTracker) -> Array[String]

#
CnameTracker::is_at_max

fn CnameTracker::is_at_max(self : CnameTracker) -> Bool

#
CnameTracker::new

fn CnameTracker::new(origin : String, max_depth? : Int) -> CnameTracker

#
DnsCache

pub struct DnsCache {
hash :
HashMap
[String, DnsResult]
expires :
HashMap
[String, Int64]
negative :
HashMap
[String, NegativeCacheKind]
access_order :
HashMap
[String, Int64]
next_counter :
Ref
[Int64]
max_size : Int
hit_count :
Ref
[Int64]
miss_count :
Ref
[Int64]
expired_count :
Ref
[Int64]
}

#
DnsCache::contains

fn DnsCache::contains(self : DnsCache, name : String, qtype : UInt16, now_ms : Int) -> Bool

#
DnsCache::get

fn DnsCache::get(self : DnsCache, name : String, qtype : UInt16, now_ms : Int) -> DnsResult?

#
DnsCache::is_empty

fn DnsCache::is_empty(self : DnsCache) -> Bool

#
DnsCache::len

fn DnsCache::len(self : DnsCache) -> Int

#
DnsCache::lookup

fn DnsCache::lookup(self : DnsCache, name : String, qtype : UInt16, now_ms : Int) -> CacheLookup

#
DnsCache::new

fn DnsCache::new(max_size? : Int) -> DnsCache

#
DnsCache::prune_expired

fn DnsCache::prune_expired(self : DnsCache, now_ms : Int) -> Unit

#
DnsCache::put

fn DnsCache::put(self : DnsCache, name : String, qtype : UInt16, result : DnsResult, ttl : Int, is_negative : Bool, now_ms : Int) -> Unit

#
DnsCache::put_negative

fn DnsCache::put_negative(self : DnsCache, name : String, qtype : UInt16, kind : NegativeCacheKind, ttl_seconds : Int, now_ms : Int) -> Unit

#
DnsCache::put_positive

fn DnsCache::put_positive(self : DnsCache, name : String, qtype : UInt16, result : DnsResult, ttl_seconds : Int, now_ms : Int) -> Unit

#
DnsCache::remove_entry

fn DnsCache::remove_entry(self : DnsCache, name : String, qtype : UInt16) -> Unit

#
DnsCache::stats

fn DnsCache::stats(self : DnsCache) -> CacheStats

#
DnsResult

pub(all) struct DnsResult {
answers : Array[RR]
cname_chain : Array[String]
}

#
DnsTransport

pub(all) struct DnsTransport {
request : async (Array[Byte]) -> Result[Array[Byte], TransportError]
udp_payload_size : UInt16
}

An injectable asynchronous DNS transport.

The callback boundary keeps the resolver independent from socket backends and makes deterministic resolver tests possible without a network connection.

#
DnsTransport::from_request

fn DnsTransport::from_request(request : async (Array[Byte]) -> Result[Array[Byte], TransportError], udp_payload_size? : UInt16) -> DnsTransport

#
DnsTransport::max_payload

fn DnsTransport::max_payload(self : DnsTransport) -> UInt16

#
DnsTransport::send

async fn DnsTransport::send(self : DnsTransport, payload : Array[Byte]) -> Result[Array[Byte], TransportError]

#
FallbackStats

pub struct FallbackStats {
udp_successes :
Ref
[Int]
udp_truncated :
Ref
[Int]
tcp_successes :
Ref
[Int]
tcp_failures :
Ref
[Int]
}

#
FallbackStats::new

#
FallbackStats::record_tcp_failure

fn FallbackStats::record_tcp_failure(self : FallbackStats) -> Unit

#
FallbackStats::record_tcp_success

fn FallbackStats::record_tcp_success(self : FallbackStats) -> Unit

#
FallbackStats::record_truncation

fn FallbackStats::record_truncation(self : FallbackStats) -> Unit

#
FallbackStats::record_udp_success

fn FallbackStats::record_udp_success(self : FallbackStats) -> Unit

#
FallbackStats::truncation_rate

fn FallbackStats::truncation_rate(self : FallbackStats) -> Int

#
FallbackTransport

pub struct FallbackTransport {
udp : UdpTransport
tcp : TcpTransport
}

#
FallbackTransport::as_transport

#
FallbackTransport::max_payload

fn FallbackTransport::max_payload(self : FallbackTransport) -> UInt16

#
FallbackTransport::new

fn FallbackTransport::new(server : String) -> FallbackTransport

#
FallbackTransport::send

async fn FallbackTransport::send(self : FallbackTransport, payload : Array[Byte]) -> Result[Array[Byte], TransportError]

#
FallbackTransport::with_options

fn FallbackTransport::with_options(server : String, timeout_ms : Int, retries : Int, udp_payload_size? : UInt16) -> FallbackTransport

pub struct Header {
id : UInt16
flags : UInt16
qdcount : UInt16
ancount : UInt16
nscount : UInt16
arcount : UInt16
}

#
Header::encode

fn Header::encode(self : Header) -> Array[Byte]

#
Message

pub struct Message {
header : Header
questions : Array[Question]
answers : Array[RR]
authorities : Array[RR]
additionals : Array[RR]
}

#
Message::encode

fn Message::encode(self : Message) -> Array[Byte]

#
Message::encode_checked

fn Message::encode_checked(self : Message) -> Result[Array[Byte], String]

#
NegativeCacheKind

pub enum NegativeCacheKind {
NXDomain
NoData
}

#
OptOption

pub struct OptOption {
opt_code : UInt16
opt_data : Array[Byte]
}

#
OptRR

pub struct OptRR {
udp_payload_size : UInt16
ext_rcode : UInt16
edns_version : UInt16
flags : UInt16
options : Array[OptOption]
}

#
OptRR::default

fn OptRR::default() -> OptRR

#
OptRR::encode

fn OptRR::encode(self : OptRR) -> Array[Byte]

#
OptRR::encode_checked

fn OptRR::encode_checked(self : OptRR) -> Result[Array[Byte], String]

#
OptRR::to_rr

fn OptRR::to_rr(self : OptRR) -> RR

Compatibility wrapper for the original API. Invalid EDNS fields abort explicitly instead of constructing an RR that bypasses checked validation.

#
OptRR::to_rr_checked

fn OptRR::to_rr_checked(self : OptRR) -> Result[RR, String]

#
OptRR::with_dnssec

fn OptRR::with_dnssec(payload_size : UInt16) -> OptRR

#
Question

pub struct Question {
name : String
qtype : UInt16
qclass : UInt16
}

#
Question::encode

fn Question::encode(self : Question) -> Array[Byte]

#
Question::encode_checked

fn Question::encode_checked(self : Question) -> Result[Array[Byte], String]

#
RData

pub enum RData {
A(Int)
AAAA(Int, Int, Int, Int)
CNAME(String)
NS(String)
PTR(String)
MX(Int, String)
TXT(Array[String])
SOA(String, String, UInt, UInt, UInt, UInt, UInt)
SRV(UInt, UInt, UInt, String)
OPT(Array[OptOption])
Unknown(Array[Byte])
}

#
RData::encode

fn RData::encode(self : RData) -> Array[Byte]

#
RData::encode_checked

fn RData::encode_checked(self : RData) -> Result[Array[Byte], String]

#
RData::rtype

fn RData::rtype(self : RData) -> UInt16

pub struct RR {
name : String
rtype : UInt16
rclass : UInt16
ttl : UInt
rdlength : UInt16
rdata : RData
}

#
RR::encode

fn RR::encode(self : RR) -> Array[Byte]

#
RR::encode_checked

fn RR::encode_checked(self : RR) -> Result[Array[Byte], String]

#
ResolveError

pub enum ResolveError {
FormatError(String)
ResponseMismatch(String)
RandomnessUnavailable
FormErr
NXDomain
NoData
Referral
ServFail
Refused
NotImplemented
ExtendedRcode(Int)
Timeout
TruncatedAndTcpFailed
ConnectionFailed(String)
CnameLoop(String)
} derive(Eq,
Debug
)

#
ResolveOptions

pub(all) struct ResolveOptions {
server : String
timeout_ms : Int
retries : Int
recurse : Bool
max_cname_depth : Int
}

#
ResolveOptions::default

#
ResolveOptions::new

fn ResolveOptions::new(server~ : String, timeout_ms? : Int, retries? : Int, recurse? : Bool, max_depth? : Int) -> ResolveOptions

Construct resolver options for callers outside this package. The fields of public structs are read-only across MoonBit package boundaries, so exposing this constructor makes server, timeout, and retry configuration available to the CLI and embedding applications.

#
Resolver

pub struct Resolver {
transport : DnsTransport
cache : DnsCache
recurse : Bool
max_cname_depth : Int
clock : () -> Int64
}

#
Resolver::cache_stats

fn Resolver::cache_stats(self : Resolver) -> CacheStats

#
Resolver::new

fn Resolver::new(options? : ResolveOptions) -> Resolver

#
Resolver::resolve

async fn Resolver::resolve(self : Resolver, name : String, qtype : UInt16) -> Result[DnsResult, ResolveError]

#
Resolver::resolve_a

async fn Resolver::resolve_a(self : Resolver, name : String) -> Result[Array[String], ResolveError]

#
Resolver::resolve_aaaa

async fn Resolver::resolve_aaaa(self : Resolver, name : String) -> Result[Array[String], ResolveError]

#
Resolver::resolve_cname

async fn Resolver::resolve_cname(self : Resolver, name : String) -> Result[Array[String], ResolveError]

#
Resolver::resolve_ipv4

async fn Resolver::resolve_ipv4(self : Resolver, host : String) -> Result[String, ResolveError]

#
Resolver::resolve_mx

async fn Resolver::resolve_mx(self : Resolver, name : String) -> Result[Array[(Int, String)], ResolveError]

#
Resolver::resolve_ns

async fn Resolver::resolve_ns(self : Resolver, name : String) -> Result[Array[String], ResolveError]

#
Resolver::resolve_ptr

async fn Resolver::resolve_ptr(self : Resolver, name : String) -> Result[Array[String], ResolveError]

#
Resolver::resolve_soa

async fn Resolver::resolve_soa(self : Resolver, name : String) -> Result[Array[SoaResult], ResolveError]

#
Resolver::resolve_srv

async fn Resolver::resolve_srv(self : Resolver, name : String) -> Result[Array[SrvResult], ResolveError]

#
Resolver::resolve_txt

async fn Resolver::resolve_txt(self : Resolver, name : String) -> Result[Array[String], ResolveError]

#
Resolver::with_transport

fn Resolver::with_transport(transport~ : DnsTransport, options? : ResolveOptions) -> Resolver

#
Resolver::with_transport_and_clock

fn Resolver::with_transport_and_clock(transport~ : DnsTransport, clock~ : () -> Int, options? : ResolveOptions) -> Resolver

#
Resolver::with_transport_and_clock64

fn Resolver::with_transport_and_clock64(transport~ : DnsTransport, clock~ : () -> Int64, options? : ResolveOptions) -> Resolver

Construct a resolver with a caller-controlled Int64 monotonic millisecond source. Use this API for long-running processes and tests that cross the 32-bit millisecond boundary. The older Int clock constructor remains as a compatibility wrapper.

#
RrTypeInfo

pub struct RrTypeInfo {
rtype : UInt16
name : String
description : String
is_domain_based : Bool
}

#
ServerAddress

pub(all) struct ServerAddress {
host : String
port : Int
} derive(Eq,
Debug
)

#
SoaResult

pub(all) struct SoaResult {
mname : String
rname : String
serial : UInt
refresh : UInt
retry : UInt
expire : UInt
minimum : UInt
}

#
SrvResult

pub(all) struct SrvResult {
priority : UInt
weight : UInt
port : UInt
target : String
}

#
TcpTransport

pub struct TcpTransport {
server : String
timeout_ms : Int
max_retries : Int
}

#
TcpTransport::as_transport

fn TcpTransport::as_transport(self : TcpTransport) -> DnsTransport

#
TcpTransport::new

fn TcpTransport::new(server : String) -> TcpTransport

#
TcpTransport::send

async fn TcpTransport::send(self : TcpTransport, payload : Array[Byte]) -> Result[Array[Byte], TransportError]

Send a DNS request over a new TCP connection and read exactly one framed response. Partial reads are handled by Reader::read_exactly.

#
TcpTransport::with_options

fn TcpTransport::with_options(server : String, timeout_ms : Int, max_retries : Int) -> TcpTransport

#
TcpTransport::with_timeout

fn TcpTransport::with_timeout(server : String, timeout_ms : Int) -> TcpTransport

#
TransportError

pub enum TransportError {
Timeout(Int)
Truncated
ConnectionFailed(String)
SendFailed(String)
RecvFailed(String)
InvalidServer(String)
InvalidMessage(String)
} derive(Eq,
Debug
)

#
TransportStats

pub struct TransportStats {
bytes_sent :
Ref
[Int]
bytes_received :
Ref
[Int]
packets_sent :
Ref
[Int]
packets_received :
Ref
[Int]
errors :
Ref
[Int]
timeouts :
Ref
[Int]
truncations :
Ref
[Int]
}

#
TransportStats::average_send_size

fn TransportStats::average_send_size(self : TransportStats) -> Int

#
TransportStats::new

#
TransportStats::record_error

fn TransportStats::record_error(self : TransportStats, err : TransportError) -> Unit

#
TransportStats::record_recv

fn TransportStats::record_recv(self : TransportStats, bytes : Int) -> Unit

#
TransportStats::record_send

fn TransportStats::record_send(self : TransportStats, bytes : Int) -> Unit

#
UdpTransport

pub struct UdpTransport {
server : String
timeout_ms : Int
max_retries : Int
udp_payload_size : UInt16
}

#
UdpTransport::as_transport

fn UdpTransport::as_transport(self : UdpTransport) -> DnsTransport

#
UdpTransport::max_payload

fn UdpTransport::max_payload(self : UdpTransport) -> UInt16

#
UdpTransport::new

fn UdpTransport::new(server : String) -> UdpTransport

#
UdpTransport::send

async fn UdpTransport::send(self : UdpTransport, payload : Array[Byte]) -> Result[Array[Byte], TransportError]

Send one DNS datagram. A connected UDP socket only accepts responses from the configured server; the OS therefore enforces the source-address check.

#
UdpTransport::with_options

fn UdpTransport::with_options(server : String, timeout_ms : Int, max_retries : Int, udp_payload_size? : UInt16) -> UdpTransport

#
UdpTransport::with_retries

fn UdpTransport::with_retries(server : String, timeout_ms : Int, max_retries : Int) -> UdpTransport

#
UdpTransport::with_timeout

fn UdpTransport::with_timeout(server : String, timeout_ms : Int) -> UdpTransport

#
build_client_subnet_option

fn build_client_subnet_option(family : UInt16, source_prefix : UInt16, scope_prefix : UInt16, address : Array[Byte]) -> OptOption

Compatibility constructor for the original API. Invalid input aborts immediately instead of returning an option that could become illegal wire. New code should use build_client_subnet_option_checked.

#
build_client_subnet_option_checked

fn build_client_subnet_option_checked(family : UInt16, source_prefix : UInt16, scope_prefix : UInt16, address : Array[Byte]) -> Result[OptOption, String]

fn build_cookie_option(client_cookie : Array[Byte], server_cookie : Array[Byte]) -> OptOption

Compatibility constructor for the original API. Invalid cookie lengths abort immediately; new code should use build_cookie_option_checked.
fn build_cookie_option_checked(client_cookie : Array[Byte], server_cookie : Array[Byte]) -> Result[OptOption, String]

#
build_keepalive_option

fn build_keepalive_option(timeout_ms : UInt16) -> OptOption

Compatibility constructor accepting the original UInt16 millisecond argument. The encoded field is converted to RFC 7828's 100ms units.

#
build_keepalive_option_checked

fn build_keepalive_option_checked(timeout_ms : Int) -> Result[OptOption, String]

#
build_keepalive_request_option

fn build_keepalive_request_option() -> OptOption

Build the empty TCP Keepalive option sent in a DNS request.

#
build_padding_option

fn build_padding_option(target_size : UInt16) -> OptOption

#
build_query

fn build_query(id : UInt16, name : String, qtype : UInt16, recurse : Bool) -> Message

#
build_query_with_edns

fn build_query_with_edns(id : UInt16, name : String, qtype : UInt16, recurse : Bool, opt : OptRR) -> Message

Compatibility wrapper for the original API. Resolver internals use the checked variant so malformed input is returned as a typed error.

#
build_query_with_edns_checked

fn build_query_with_edns_checked(id : UInt16, name : String, qtype : UInt16, recurse : Bool, opt : OptRR) -> Result[Message, String]

#
decode_header

fn decode_header(bytes : Array[Byte], offset? : Int) -> Result[(Header, Int), String]

#
decode_message

fn decode_message(bytes : Array[Byte]) -> Result[Message, String]

#
decode_name

fn decode_name(bytes : Array[Byte], offset : Int, msg_start : Int) -> Result[(String, Int), String]

#
decode_opt_rr

fn decode_opt_rr(bytes : Array[Byte], offset : Int, rdlength : UInt16, _msg_start : Int) -> Result[(OptRR, Int), String]

#
decode_question

fn decode_question(bytes : Array[Byte], offset : Int, msg_start : Int) -> Result[(Question, Int), String]

#
decode_questions

fn decode_questions(bytes : Array[Byte], offset : Int, count : UInt16, msg_start : Int) -> Result[(Array[Question], Int), String]

#
decode_rdata

fn decode_rdata(rtype : UInt16, bytes : Array[Byte], offset : Int, rdlength : UInt16, msg_start : Int) -> Result[(RData, Int), String]

#
decode_rr

fn decode_rr(bytes : Array[Byte], offset : Int, msg_start : Int) -> Result[(RR, Int), String]

#
decode_rrs

fn decode_rrs(bytes : Array[Byte], offset : Int, count : UInt16, msg_start : Int) -> Result[(Array[RR], Int), String]

#
default_retries

let default_retries : Int

#
default_timeout_ms

let default_timeout_ms : Int

#
default_udp_size

let default_udp_size : Int

#
dns_cloudflare_primary

let dns_cloudflare_primary : String

#
dns_cloudflare_secondary

let dns_cloudflare_secondary : String

#
dns_google_primary

let dns_google_primary : String

#
dns_google_secondary

let dns_google_secondary : String

#
dns_header_size

let dns_header_size : Int

#
dns_port

let dns_port : UInt16

#
dns_quad9_primary

let dns_quad9_primary : String

#
ede_blocked

let ede_blocked : UInt16

#
ede_cached_error

let ede_cached_error : UInt16

#
ede_censored

let ede_censored : UInt16

#
ede_dnskey_missing

let ede_dnskey_missing : UInt16

#
ede_dnssec_bogus

let ede_dnssec_bogus : UInt16

#
ede_dnssec_indeterminate

let ede_dnssec_indeterminate : UInt16

#
ede_filtered

let ede_filtered : UInt16

#
ede_forged_answer

let ede_forged_answer : UInt16

#
ede_invalid_data

let ede_invalid_data : UInt16

#
ede_network_error

let ede_network_error : UInt16

#
ede_no_reachable_authority

let ede_no_reachable_authority : UInt16

#
ede_no_zone_key

let ede_no_zone_key : UInt16

#
ede_not_authoritative

let ede_not_authoritative : UInt16

#
ede_not_ready

let ede_not_ready : UInt16

#
ede_not_supported

let ede_not_supported : UInt16

#
ede_nsec_missing

let ede_nsec_missing : UInt16

#
ede_other

let ede_other : UInt16

#
ede_prohibited

let ede_prohibited : UInt16

#
ede_rrsigs_missing

let ede_rrsigs_missing : UInt16

#
ede_signature_expired

let ede_signature_expired : UInt16

#
ede_signature_expired_before

let ede_signature_expired_before : UInt16

#
ede_signature_not_yet_valid

let ede_signature_not_yet_valid : UInt16

#
ede_stale_answer

let ede_stale_answer : UInt16

#
ede_stale_nxdomain

let ede_stale_nxdomain : UInt16

#
ede_too_early

let ede_too_early : UInt16

#
ede_unable_to_conform

let ede_unable_to_conform : UInt16

#
ede_unsupported_dnskey_algorithm

let ede_unsupported_dnskey_algorithm : UInt16

#
ede_unsupported_ds_digest

let ede_unsupported_ds_digest : UInt16

#
ede_unsupported_nsec3_iterations

let ede_unsupported_nsec3_iterations : UInt16

#
edns_default_udp_size

let edns_default_udp_size : UInt16

#
edns_flag_do

let edns_flag_do : UInt16

#
edns_opt_chain

let edns_opt_chain : UInt16

#
edns_opt_client_subnet

let edns_opt_client_subnet : UInt16

#
edns_opt_client_tag

let edns_opt_client_tag : UInt16

let edns_opt_cookie : UInt16

#
edns_opt_dau

let edns_opt_dau : UInt16

#
edns_opt_dhu

let edns_opt_dhu : UInt16

#
edns_opt_expire

let edns_opt_expire : UInt16

#
edns_opt_extended_error

let edns_opt_extended_error : UInt16

#
edns_opt_key_tag

let edns_opt_key_tag : UInt16

#
edns_opt_n3u

let edns_opt_n3u : UInt16

#
edns_opt_nsid

let edns_opt_nsid : UInt16

#
edns_opt_padding

let edns_opt_padding : UInt16

#
edns_opt_server_tag

let edns_opt_server_tag : UInt16

#
edns_opt_tcp_keepalive

let edns_opt_tcp_keepalive : UInt16

#
edns_option_name

fn edns_option_name(code : UInt16) -> String

#
encode_name_checked

fn encode_name_checked(name : String) -> Result[Array[Byte], String]

#
encode_name_uncompressed

fn encode_name_uncompressed(name : String) -> Array[Byte]

#
encode_questions

fn encode_questions(questions : Array[Question]) -> Array[Byte]

#
encode_tcp_message

fn encode_tcp_message(payload : Array[Byte]) -> Result[Array[Byte], TransportError]

Prefix a DNS payload with its two-octet RFC 7766 TCP length field.

#
extended_error_name

fn extended_error_name(code : UInt16) -> String

#
extract_cname_targets

fn extract_cname_targets(answers : Array[RR]) -> Array[String]

#
filter_terminal_records

fn filter_terminal_records(answers : Array[RR]) -> Array[RR]

#
find_cname_final_target

fn find_cname_final_target(answers : Array[RR], original_name : String) -> String

#
flag_aa

let flag_aa : UInt16

#
flag_qr

let flag_qr : UInt16

#
flag_ra

let flag_ra : UInt16

#
flag_rd

let flag_rd : UInt16

#
flag_tc

let flag_tc : UInt16

#
is_known_edns_option

fn is_known_edns_option(code : UInt16) -> Bool

#
is_retryable

fn is_retryable(err : TransportError) -> Bool

#
is_timeout

fn is_timeout(err : TransportError) -> Bool

#
lookup_rr_type

fn lookup_rr_type(rtype : UInt16) -> RrTypeInfo?

#
mask_aa

let mask_aa : UInt16

#
mask_opcode

let mask_opcode : UInt16

#
mask_qr

let mask_qr : UInt16

#
mask_ra

let mask_ra : UInt16

#
mask_rcode

let mask_rcode : UInt16

#
mask_rd

let mask_rd : UInt16

#
mask_tc

let mask_tc : UInt16

#
mask_z

let mask_z : UInt16

#
max_cname_depth

let max_cname_depth : Int

#
max_dns_labels

let max_dns_labels : Int

#
max_dns_message_len

let max_dns_message_len : Int

#
max_edns_options

let max_edns_options : Int

#
max_label_len

let max_label_len : Int

#
max_lifetime_ms

let max_lifetime_ms : Int

#
max_name_len

let max_name_len : Int

#
max_pointer_hops

let max_pointer_hops : Int

#
max_section_records

let max_section_records : Int

#
max_udp_payload

let max_udp_payload : UInt16

#
negative_ttl

let negative_ttl : Int

#
opcode_iquery

let opcode_iquery : UInt16

#
opcode_notify

let opcode_notify : UInt16

#
opcode_query

let opcode_query : UInt16

#
opcode_status

let opcode_status : UInt16

#
opcode_update

let opcode_update : UInt16

#
opt_from_rr

fn opt_from_rr(rr : RR) -> Result[OptRR, String]

#
parse_server_addr

fn parse_server_addr(source : String) -> Result[ServerAddress, TransportError]

Parse an IPv4, hostname, or bracketed IPv6 DNS server address. host, [ipv6], and host:port use port 53 when omitted.

#
pretty_print_message

fn pretty_print_message(msg : Message) -> String

#
pretty_print_rdata

fn pretty_print_rdata(rdata : RData) -> String

#
pretty_print_result

fn pretty_print_result(result : DnsResult) -> String

#
pretty_print_result_json

fn pretty_print_result_json(result : DnsResult, name : String, qtype : UInt16, server : String) -> String

#
pretty_print_rr

fn pretty_print_rr(rr : RR) -> String

#
ptr_name_from_ipv4

fn ptr_name_from_ipv4(ip : Int) -> String

#
qclass_in

let qclass_in : UInt16

#
qtype_a

let qtype_a : UInt16

#
qtype_aaaa

let qtype_aaaa : UInt16

#
qtype_afsdb

let qtype_afsdb : UInt16

#
qtype_any

let qtype_any : UInt16

#
qtype_axfr

let qtype_axfr : UInt16

#
qtype_caa

let qtype_caa : UInt16

#
qtype_cert

let qtype_cert : UInt16

#
qtype_cname

let qtype_cname : UInt16

#
qtype_dname

let qtype_dname : UInt16

#
qtype_dnskey

let qtype_dnskey : UInt16

#
qtype_ds

let qtype_ds : UInt16

#
qtype_hinfo

let qtype_hinfo : UInt16

#
qtype_https

let qtype_https : UInt16

#
qtype_isdn

let qtype_isdn : UInt16

#
qtype_ixfr

let qtype_ixfr : UInt16

#
qtype_key

let qtype_key : UInt16

#
qtype_kx

let qtype_kx : UInt16

#
qtype_loc

let qtype_loc : UInt16

#
qtype_mb

let qtype_mb : UInt16

#
qtype_md

let qtype_md : UInt16

#
qtype_mf

let qtype_mf : UInt16

#
qtype_mg

let qtype_mg : UInt16

#
qtype_minfo

let qtype_minfo : UInt16

#
qtype_mr

let qtype_mr : UInt16

#
qtype_mx

let qtype_mx : UInt16

#
qtype_naptr

let qtype_naptr : UInt16

#
qtype_ns

let qtype_ns : UInt16

#
qtype_nsec

let qtype_nsec : UInt16

#
qtype_null

let qtype_null : UInt16

#
qtype_opt

let qtype_opt : UInt16

#
qtype_ptr

let qtype_ptr : UInt16

#
qtype_px

let qtype_px : UInt16

#
qtype_rp

let qtype_rp : UInt16

#
qtype_rrsig

let qtype_rrsig : UInt16

#
qtype_rt

let qtype_rt : UInt16

#
qtype_sig

let qtype_sig : UInt16

#
qtype_soa

let qtype_soa : UInt16

#
qtype_spf

let qtype_spf : UInt16

#
qtype_srv

let qtype_srv : UInt16

#
qtype_sshfp

let qtype_sshfp : UInt16

#
qtype_svcb

let qtype_svcb : UInt16

#
qtype_tlsa

let qtype_tlsa : UInt16

#
qtype_to_string

fn qtype_to_string(qtype : UInt16) -> String

#
qtype_txt

let qtype_txt : UInt16

#
qtype_wks

let qtype_wks : UInt16

#
qtype_x25

let qtype_x25 : UInt16

#
question_a

fn question_a(name : String) -> Question

#
question_aaaa

fn question_aaaa(name : String) -> Question

#
question_any

fn question_any(name : String) -> Question

#
question_clone

fn question_clone(q : Question) -> Question

#
question_cname

fn question_cname(name : String) -> Question

#
question_custom

fn question_custom(name : String, qtype : UInt16, qclass? : UInt16) -> Question

#
question_description

fn question_description(q : Question) -> String

#
question_is_internet

fn question_is_internet(q : Question) -> Bool

#
question_is_type

fn question_is_type(q : Question, qtype : UInt16) -> Bool

#
question_mx

fn question_mx(name : String) -> Question

#
question_ns

fn question_ns(name : String) -> Question

#
question_ptr

fn question_ptr(name : String) -> Question

#
question_soa

fn question_soa(name : String) -> Question

#
question_srv

fn question_srv(name : String) -> Question

#
question_txt

fn question_txt(name : String) -> Question

#
question_wire_size

fn question_wire_size(q : Question) -> Int

#
questions_for_types

fn questions_for_types(name : String, qtypes : Array[UInt16]) -> Array[Question]

#
rcode_badalg

let rcode_badalg : Int

#
rcode_badcookie

let rcode_badcookie : Int

#
rcode_badkey

let rcode_badkey : Int

#
rcode_badmode

let rcode_badmode : Int

#
rcode_badname

let rcode_badname : Int

#
rcode_badtime

let rcode_badtime : Int

#
rcode_badtrunc

let rcode_badtrunc : Int

#
rcode_badvers

let rcode_badvers : Int

#
rcode_formerr

let rcode_formerr : Int

#
rcode_noerror

let rcode_noerror : Int

#
rcode_notimp

let rcode_notimp : Int

#
rcode_nxdomain

let rcode_nxdomain : Int

#
rcode_refused

let rcode_refused : Int

#
rcode_servfail

let rcode_servfail : Int

#
rcode_to_string

fn rcode_to_string(rcode : Int) -> String

#
read_tcp_length

fn read_tcp_length(buf : Array[Byte], offset? : Int) -> Result[Int, TransportError]

#
response_has_cname

fn response_has_cname(msg : Message) -> Bool

#
rr_a

fn rr_a(name : String, ip : Int, ttl? : UInt) -> RR

#
rr_aaaa

fn rr_aaaa(name : String, w1 : Int, w2 : Int, w3 : Int, w4 : Int, ttl? : UInt) -> RR

#
rr_cname

fn rr_cname(name : String, target : String, ttl? : UInt) -> RR

#
rr_compare_ttl

fn rr_compare_ttl(a : RR, b : RR) -> Int

Compare two DNS TTLs using their RFC 1035 unsigned 32-bit ordering. The result is -1, 0, or 1 and never depends on subtracting values that may overflow a signed Int.

#
rr_count_by_type

fn rr_count_by_type(rrs : Array[RR], rtype : UInt16) -> Int

#
rr_filter_by_min_ttl

fn rr_filter_by_min_ttl(rrs : Array[RR], min_ttl : UInt) -> Array[RR]

#
rr_is_expired

fn rr_is_expired(rr : RR, current_time : Int) -> Bool

Return whether an RR's unsigned TTL has elapsed after current_time seconds. Equality is expired. Negative elapsed values are treated as being before the record was inserted.

#
rr_is_type

fn rr_is_type(rr : RR, rtype : UInt16) -> Bool

#
rr_mx

fn rr_mx(name : String, pref : Int, exchange : String, ttl? : UInt) -> RR

#
rr_new

fn rr_new(name : String, rtype : UInt16, rclass : UInt16, ttl : UInt, rdata : RData) -> RR

#
rr_ns

fn rr_ns(name : String, ns : String, ttl? : UInt) -> RR

#
rr_ptr

fn rr_ptr(name : String, ptr : String, ttl? : UInt) -> RR

#
rr_short_description

fn rr_short_description(rr : RR) -> String

#
rr_soa

fn rr_soa(name : String, mname : String, rname : String, serial : UInt, refresh : UInt, retry : UInt, expire : UInt, minimum : UInt, ttl? : UInt) -> RR

#
rr_srv

fn rr_srv(name : String, priority : UInt, weight : UInt, port : UInt, target : String, ttl? : UInt) -> RR

#
rr_txt

fn rr_txt(name : String, strings : Array[String], ttl? : UInt) -> RR

#
rr_type_needs_decompression

fn rr_type_needs_decompression(rtype : UInt16) -> Bool

#
rr_type_registry

fn rr_type_registry() -> Array[RrTypeInfo]

#
short_print_rdata

fn short_print_rdata(rdata : RData) -> String

Format RDATA for one-answer-per-line command output.

This deliberately keeps DNS names without a presentation trailing dot and leaves SOA fields space separated so scripts can compare the value with dig +short after their type-specific normalization. It is a semantic formatter, not a zone-file serializer.

#
soa_default_expire

let soa_default_expire : UInt

#
soa_default_minimum

let soa_default_minimum : UInt

#
soa_default_refresh

let soa_default_refresh : UInt

#
soa_default_retry

let soa_default_retry : UInt

#
tcp_dns_max_len

let tcp_dns_max_len : Int

#
tcp_dns_max_message_size

fn tcp_dns_max_message_size() -> Int

#
transport_error_message

fn transport_error_message(err : TransportError) -> String

#
validate_tcp_message

fn validate_tcp_message(buf : Array[Byte]) -> Bool

#
wire_check_range

fn wire_check_range(bytes : Array[Byte], offset : Int, length : Int) -> Result[Unit, String]

#
wire_concat

fn wire_concat(segments : Array[Array[Byte]]) -> Array[Byte]

#
wire_copy_range

fn wire_copy_range(bytes : Array[Byte], offset : Int, length : Int) -> Result[Array[Byte], String]

#
wire_get_pointer

fn wire_get_pointer(bytes : Array[Byte], offset : Int) -> Result[Int, String]

#
wire_get_u16

fn wire_get_u16(bytes : Array[Byte], offset : Int) -> Result[(UInt16, Int), String]

#
wire_get_u32

fn wire_get_u32(bytes : Array[Byte], offset : Int) -> Result[(UInt, Int), String]

#
wire_hex_dump

fn wire_hex_dump(bytes : Array[Byte], max_len? : Int) -> String

#
wire_is_pointer

fn wire_is_pointer(b : Byte) -> Bool

#
wire_message_size

fn wire_message_size(header_bytes : Array[Byte], sections : Array[Array[Byte]]) -> Int

#
wire_put_pointer

fn wire_put_pointer(buf : Array[Byte], offset : Int, target : Int) -> Result[Int, String]

#
wire_put_u16

fn wire_put_u16(buf : Array[Byte], offset : Int, val : UInt16) -> Result[Int, String]

#
wire_put_u32

fn wire_put_u32(buf : Array[Byte], offset : Int, val : UInt) -> Result[Int, String]