最近用 Rust 写了个叫 capture-dns 的小程序,实时显示 DNS 查询结果的。配合 ipmarkup 的效果是这样的:
>>> sudo capture-dns lo | ipmarkup [sudo] lilydjwg 的密码: github.com -> 52.74.223.119(新加坡Amazon数据中心) github.com -> 13.229.188.59(新加坡Amazon数据中心) github.com -> 13.250.177.223(新加坡Amazon数据中心) live.github.com -> 192.30.253.125(美国弗吉尼亚州阿什本GitHub) live.github.com -> 192.30.253.124(美国弗吉尼亚州阿什本GitHub) collector.githubapp.com -> 34.193.248.191(美国弗吉尼亚州阿什本Amazon数据中心) collector.githubapp.com -> 52.20.29.9(美国弗吉尼亚州阿什本Amazon数据中心) collector.githubapp.com -> 34.197.57.23(美国弗吉尼亚州阿什本Amazon数据中心) api.github.com -> 13.250.94.254(美国Amazon数据中心) api.github.com -> 13.250.168.23(美国Amazon数据中心) api.github.com -> 54.169.195.247(新加坡Amazon数据中心) ocsp.digicert.com -> 117.18.237.29(澳大利亚美国MCI通信服务有限公司(韦里孙商业Verizon Business)EdgeCast亚太网络CDN节点)
可以看到本地的软件们都在查询哪些域名,得到的 IP 又是什么。抓取的是应答,所以没得到 IP 结果的不会显示。我抓取的是 lo 网络接口,因为我本地有用 dnsmasq 做缓存。
其实这个程序一开始不是这样子的。群里有人想抓取系统上进行的 DNS 查询的域名。一开始是用 tshark 抓取的,然而它太占用内存了。我粗略看了一下 Python 的 scapy 工具,也用掉了大几十M内存。那么,用 Rust 写一个好了,也顺便练习一下 Rust。
这个程序运行时只有几M的内存占用,CPU 占用也是非常低的。不过它并没有做完全的协议分析,而是假设抓得的包是以太网帧封装的 IPv4 报文封装的 UDP 数据包里包着 DNS 应答报文。所以如果你是在 eth0 上跑 PPPoE 的话,抓 eth0 上的包就不行了,得抓 ppp0 这种了。当然你要是 IPv6 啊 DoH、DoT 啥的就更抓不到了。
后来我用 bcc 的 tcpretrans 脚本查看我这里到哪些地方的 TCP 连接不太通畅,然而经常会看到一些我猜不到是干嘛的 IP。所以就把这个程序改了一下,把域名对应的解析结果显示出来了。
Rust 不仅节省资源,而且开发的体验真的很棒呢,编译成功之后就能按我预期的运行了。也不用担心什么时候遇到个有问题的报文导致程序崩掉,因为写的时候就已经处理好了出错的情况。不像 Python 写的脚本,刚写好,一跑就抛个异常出来,提示我哪里不小心写错了。好不容易调试好了,跑着跑着,遇到意外情况就挂掉了……