一个 rust 程序使用了 reqwest ,频繁地报错 error sending request for url ,调试过程记录一下。
reqwest 的 Error 使用 thiserror 接收
#[error(transparent)]
Reqwest(#[from] reqwest::Error),
在外层程序打印报错信息:
let source = e.source();
tracing::error!("{e}, source: {source:?}");
source 是:
source: Some(hyper_util::client::legacy::Error(Connect, ConnectError("dns error", Custom { kind: Uncategorized, error: "failed to lookup address information: Name has no usable address" })))
确定是 dns 查询问题,怀疑是优先查询了 ipv6 ,因为要查询的域名是禁用了 ipv6 地址解析的。reqwest 换用 hickory-dns ,得到报错信息:
source: Some(hyper_util::client::legacy::Error(Connect, ConnectError("dns error", ResolveError { kind: Proto(ProtoError { kind: NoRecordsFound { query: Query { name: Name("example.com."), query_type: AAAA, query_class: IN }, soa: Some(Record { name_labels: Name("example.com."), dns_class: IN, ttl: 10, rdata: SOA { mname: Name("ns.co.uk."), rname: Name("example.com."), serial: 1, refresh: 7200, retry: 900, expire: 1209600, minimum: 60 } }), ns: None, negative_ttl: Some(9), response_code: NoError, trusted: true, authorities: None } }) })))
于是确定了是查询 ipv6 导致的。
看 reqwest 是否提供配置项,找到代码:
/// Create a new resolver with the default configuration,
/// which reads from `/etc/resolve.conf`. If reading `/etc/resolv.conf` fails,
/// it fallbacks to hickory_resolver's default config.
/// The options are overridden to look up for both IPv4 and IPv6 addresses
/// to work with "happy eyeballs" algorithm.
fn new_resolver() -> TokioResolver {
let mut builder = TokioResolver::builder_tokio().unwrap_or_else(|err| {
log::debug!(
"hickory-dns: failed to load system DNS configuration; falling back to hickory_resolver defaults: {:?}",
err
);
TokioResolver::builder_with_config(
ResolverConfig::default(),
TokioConnectionProvider::default(),
)
});
builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
builder.build()
}
果然是双栈查询,但是这个查询策略在 reqwest 中是无法修改的,那 hickory-dns 会不会读取系统的配置,从而改变这种行为呢?
修改 /etc/gai.conf,将 precedence ::ffff:0:0/96 100 这行的注释打开,还是在报错,可能是换成了 hickory-dns ,它并不读取这个配置文件。
再根据 gemini 的说法修改 /etc/resolv.conf ,加一行 options no-aaaa 。唉,可以了,不再报错了。
看 hickory-dns ,它用了 hickory-dns/resolv-conf 这个 crate,它里面定义的选项很多,其中有两个和 ipv6 解析有关: inet6, no-aaaa 。
inet6 只是一个启用开关,并没有提供 no-inet6 这种禁用的选项,那用且只能用 no-aaaa 选项了。