- What I learned from making a DNS client in Rust 译文(Rust 实现一个 DNS 客户端,我从中学到什么)
- 原文链接:https://blog.adamchalmers.com/making-a-dns-client/
- 原文作者:Adam Chalmers
- 译文来自:https://github.com/suhanyujie/article-transfer-rs/
- 译者:suhanyujie
- ps:水平有限,翻译不当之处,还请指正。
- 标签:Rust, DNS, DNS client
在过去的几周里,我构建了我自己的 DNS 客户端。主要是因为我认为 dig(一个标准的 DNS 客户端)有点笨重。另一部分原因是我想了解更多关于 DNS 的知识。这就是我如何创建它,也是你如何创建 dns 客户端的过程。它是一个很棒的“周末项目”,我从完成它中学到了很多。
为什么?
Julia Evans 给我提供了一些思路。她是我最喜欢的技术博客作者之一。她的博客总能给我一些启发,经常激励我去学习新的东西。她也很擅长把复杂的话题总结成简单的小漫画,比如这个:
但我看完这个漫画后,我震惊了 —— DNS 查询协议比我想象的要简单得多。并且,我所在的公司在 DNS 领域算是一个大玩家。我理应更了解这个。
计划
我想构建 DNS 客户端的另一个原因是我可以使用一些优秀的 Rust crate 来快速实现(节省很多步骤)。
- 使用 picoargs 解析 CLI 参数
它没有 clap 那么强大,clap 可以视为 Rust 的“企业级” crate,它依赖了很多其他 crate。但我并不需要那么多高级特性,而且 picoargs 的编译速度会快_很多_。
- 使用 bitvec 序列化 DNS 查询,它是一个强大且通用的读写“位”值的库。
在做 Advent of Code 时,我学会了使用 Nom 解析“位”协议,我也考虑过使用 deku,但最后还是不使用它了。
- 使用 stdlib UdpSocket 类型与 DNS 解析器通信
我不知道怎么使用,但 Rust stdlib 有很详细的相关文档,所以我有信心能够学会它。
-
使用 Nom 解析响应的二进制数据
在做 Advent of Code 的时候,我学会如何使用 Nom 解析位协议。我之前的(博客)(/nom-dns) 中更加详细的描述了解析 DNS 头部指南,使用 Nom 解析位级别的 1 个标志位和 4 个位数值。
-
使用 println!
将响应发送给用户
实现的怎么样了?
它用了大概 800 行代码,一个周末就能完成。规范中只有一小部分还没实现:消息的压缩(MC)。不幸的是,我又要花一个周末去完成 —— 详见下方描述。
我把它命名为 Dingo,因为它听起来像 dig
,它让我想起了澳大利亚,我的家。无论如何,它可以正常工作!
你可以安装它,也可以在 github 查看源代码。
我学到了什么?
阅读 RFC 文档
我认为很多程序员会被 rfc 吓到。至少我有这种感觉,因为我也被吓到过。可能我的其他同行是喜欢 rfc 的怪物吧,他们在阅读存文本的 ASCII 时会感到兴奋不已。。。但他们从未提过。
RFC 1035 定义了 DNS 消息协议,所以我必须非常仔细地阅读它。这是我第一次真正地从上到下阅读 RFC,文档详细的程度让我感到吃惊。我不断地参考它,并将 RFC 中的关键字和一些关键句子粘贴到源代码注释中,以帮助理解代码的实现原因。也有可能是 RFC 1035 是唯一容易理解的文档,其他都是难理解的。无论如何,我喜欢它。
(作为一个历史留下来的文档,自从 1980 年以来,它发生了很大的变化,了解过去的程序员是如何设计互联网是非常有趣的)
Sockets
一直以来,我对 socket 不是很喜欢。大学的时候,我试着阅读 Beej 的 socket 编程指南,但当时没有合适的操作系统、网络和 c 技能来完成学习。我了解 TCP 和 UDP,但对统一他们的底层抽象却一无所知。
这个项目使我第一次不得不打开一个 UDP socket —— 在常规编程中,我都是依赖于一些网络库来处理底层细节。所以,我阅读了 Rust 的 UDP socket 文档,它写的很清楚。UdpSocket 上的很多方法直接对应于 Linux 系统调用。当我再回过头来重新阅读 Beej 的 socket 编程指南时,发现很容易懂了。所有的系统调用都比较熟悉 —— 它们只是 Rust 的 stdlib 中的网络方法!
事实上,如果我使用 dtruss
(一个MacOS工具,用于检查您的程序有哪些系统调用),我可以确切地看到程序在使用哪个系统调用。
$ sudo dtruss dingo -t A www.twitter.com
# Skipping lots of syscalls just for starting a process on MacOS...
getentropy(0x7FF7BE8734E0, 0x20, 0x0) = 0 0 # Used by `rand` to generate a random DNS request ID
socket(0x2, 0x2, 0x0) = 3 0 # Create the UDP socket, aka "file descriptor 3"
ioctl(0x3, 0x20006601, 0x0) = 0 0 # Not sure, something about the UDP socket
setsockopt(0x3, 0xFFFF, 0x1022) = 0 0 # Set the options on the UDP socket
bind(0x3, 0x7FF7BE87346C, 0x10) = 0 0 # Bind the UDP socket to a local address
setsockopt(0x3, 0xFFFF, 0x1006) = 0 0 # Set more options, dunno why it needs more...
connect(0x3, 0x7FF7BE873584, 0x10) = 0 0 # Connect to the remote DNS resolver
sendto(0x3, 0x7FAE060041F0, 0x21) = 33 0 # Send the request to the remote DNS resolver
recvfrom(0x3, 0x7FAE060043F0, 0x200) = 79 0 # Get the response from the remote DNS resolver
close_nocancel(0x3) = 0 0 # Close the UDP socket
# Skipping lots of syscalls just for ending a process on MacOS
系统调用 connect
,sendto
和 recvfrom
都来自于 Rust 中的方法 UdpSocket::{connect, send_to, recv_from}
—— 它们将会 1:1 转换成真正的系统调用!这很酷。
Bitvec
我真的很喜欢 Bitvec。它结合了 Vec<bool>
类型的可用性和可读性,并具备“位”的可操作性。而这正是 Rust 毫不妥协的“人体工程学、速度和正确性”的体现。
标准库公开了 BitArray,BitVec 和 BitSlice 类型。它们的工作原来基本相同,但我发现了两个小问题,它们的工作方式不同。这些很容易通过单元测试发现,所以我想这是一个不错的学习方式。作者希望在 Rust 发布 const generics后,会发布 Bitvec 2.0,使这些类型都能以类似的方式使用。
Dig 的奇怪输出
我之前提到过,我讨厌使用 dig
。它有那么多令人困惑的无关信息。我只是想看看一些主机名的解析是什么,而 dig
展示了很多我并不关心的额外信息。
$ dig adamchalmers.com
; <<>> DiG 9.10.6 <<>> adamchalmers.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51459
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;adamchalmers.com. IN A
;; ANSWER SECTION:
adamchalmers.com. 300 IN A 104.19.237.120
adamchalmers.com. 300 IN A 104.19.238.120
;; Query time: 80 msec
;; SERVER: 2600:1700:280:1f40::1#53(2600:1700:280:1f40::1)
;; WHEN: Sun Apr 10 17:48:43 CDT 2022
;; MSG SIZE rcvd: 77
但在实现了一个 DNS 客户端之后,我根进一步了解了这些相关知识。例如,“IN”并不是英文单词“in”的意思,它是“Internet”的缩写,因为从技术上讲,DNS 支持许多名称空间(只是我们基本上只使用 dig
来进行 Internet DNS 查询)。
现在看 dig
输出有点酷,因为它提醒我学到了多少东西。是的,我还了解到,dig
查询很容易就能得到你想要的信息:
$ dig +short adamchalmers.com
104.19.238.120
104.19.237.120
。。。如果我早在一月份就知道这些,我可能永远不会开始这个项目
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)