如何使用Perl的Net::DNS模块读取DNS域所有A记录
Perl 结合 Net::DNS 获取域下全量A记录实现方案
单条结果返回的核心原因
- 代码逻辑缺陷:发起查询后仅提取了返回结果集中的第一条记录,未遍历整个应答段
- 协议认知误区:普通DNS递归查询仅返回请求域名对应的匹配记录,不会主动返回域下所有子主机的条目,不存在"传入根域名就自动拉取全域记录"的默认能力,全量获取A记录只有两种可落地的实现路径。
场景1:目标域授权NS允许区域传输(AXFR)
这是唯一能完整、准确获取域下所有记录的合法方式,一般仅对域管理员信任的IP开放权限,适合管理自有域名的场景使用,无需做枚举扫描。
示例代码:
use strict; use warnings; use Net::DNS; # 配置目标域和对应授权NS地址 my $target_domain = "example.com"; my $res = Net::DNS::Resolver->new( nameservers => ['ns1.example.com'], recurse => 0, debug => 0, ); # 发起AXFR区域传输请求 my @all_rrs = $res->axfr($target_domain); die "AXFR请求失败:" . $res->errorstring unless @all_rrs; # 过滤提取所有A记录 my @a_records; for my $rr (@all_rrs) { next unless $rr->type eq 'A'; push @a_records, { hostname => $rr->name, ip => $rr->address }; } # 后续可直接使用@a_records中的IP集合生成SPF TXT记录
注意:绝大多数对外开放的公共NS都会默认关闭AXFR权限,避免域名拓扑泄露,如果请求返回REFUSED错误,直接使用下面的枚举方案即可。
场景2:目标域不开放AXFR,使用字典枚举A记录
该场景下没有办法通过标准DNS协议被动拿到全量A记录,只能通过预置常见子域名字典,循环拼接域名发起A查询来收集有效记录,注意要正确遍历所有返回结果,避免漏记同主机多IP的情况。
示例代码:
use strict; use warnings; use Net::DNS; my $target_domain = "example.com"; my $res = Net::DNS::Resolver->new( nameservers => ['8.8.8.8', '223.5.5.5'], # 可替换为可用的递归DNS recurse => 1, timeout => 3, retrans => 2, ); # 预置常见主机名前缀,可根据业务场景自行扩充 my @prefix_list = qw(www mail smtp pop imap ftp vpn api app web admin shop test dev); my @a_records; for my $prefix (@prefix_list) { my $query_host = "$prefix.$target_domain"; my $reply = $res->query($query_host, 'A'); next unless $reply; # 列表上下文接收所有应答记录,避免只取第一条 for my $rr ($reply->answer) { next unless $rr->type eq 'A'; push @a_records, { hostname => $rr->name, ip => $rr->address }; } } # 对@a_records按IP去重后即可生成SPF记录
常见踩坑说明
- 注意Perl上下文特性:调用
$reply->answer时如果用标量变量接收返回值,只会拿到应答段的第一条资源记录,必须放在列表上下文中接收全量结果再遍历 - 枚举查询时注意控制请求频率,短时间发起大量请求很容易被公共DNS服务器限流拦截
- 收集完A记录后一定要做IP去重,同一IP对应多个主机名时无需在SPF中重复添加
- 不要试图通过根域名的ANY类型查询拿全量记录,绝大多数公共DNS都已经屏蔽了ANY类型查询,且返回结果也不会包含所有子域记录
内容的提问来源于stack exchange,提问作者Marisa Giancarla
相关产品推荐
相关产品推荐

