【译】Rust 实现一个 DNS 客户端,我从中学到什么

2023-10-27

  • 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 给我提供了一些思路。她是我最喜欢的技术博客作者之一。她的博客总能给我一些启发,经常激励我去学习新的东西。她也很擅长把复杂的话题总结成简单的小漫画,比如这个:

Comic explaining DNS query structure, when serialized for transmission over wire

但我看完这个漫画后,我震惊了 —— DNS 查询协议比我想象的要简单得多。并且,我所在的公司在 DNS 领域算是一个大玩家。我理应更了解这个。

计划

我想构建 DNS 客户端的另一个原因是我可以使用一些优秀的 Rust crate 来快速实现(节省很多步骤)。

  1. 使用 picoargs 解析 CLI 参数

它没有 clap 那么强大,clap 可以视为 Rust 的“企业级” crate,它依赖了很多其他 crate。但我并不需要那么多高级特性,而且 picoargs 的编译速度会快_很多_。

  1. 使用 bitvec 序列化 DNS 查询,它是一个强大且通用的读写“位”值的库。

在做 Advent of Code 时,我学会了使用 Nom 解析“位”协议,我也考虑过使用 deku,但最后还是不使用它了。

  1. 使用 stdlib UdpSocket 类型与 DNS 解析器通信

我不知道怎么使用,但 Rust stdlib 有很详细的相关文档,所以我有信心能够学会它。

  1. 使用 Nom 解析响应的二进制数据

    在做 Advent of Code 的时候,我学会如何使用 Nom 解析位协议。我之前的(博客)(/nom-dns) 中更加详细的描述了解析 DNS 头部指南,使用 Nom 解析位级别的 1 个标志位和 4 个位数值。

  2. 使用 println! 将响应发送给用户

实现的怎么样了?

它用了大概 800 行代码,一个周末就能完成。规范中只有一小部分还没实现:消息的压缩(MC)。不幸的是,我又要花一个周末去完成 —— 详见下方描述。

我把它命名为 Dingo,因为它听起来像 dig,它让我想起了澳大利亚,我的家。无论如何,它可以正常工作!

Screenshot of a terminal running dingo, my DNS client, and resolving a name

你可以安装它,也可以在 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

系统调用 connectsendtorecvfrom 都来自于 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(使用前将#替换为@)

【译】Rust 实现一个 DNS 客户端,我从中学到什么 的相关文章

随机推荐

  • YaRN: Efficient Context Window Extension of Large Language Models

    本文是LLM系列文章 针对 YaRN Efficient Context Window Extension of Large Language Models 的翻译 YaRN 大型语言模型的有效上下文窗口扩展 摘要 1 引言 2 背景和相关
  • zookeeper版本选择与配置参数调优

    一 zookeeper 发布策略 Apache ZooKeeper 社区一次支持两个发布分支 stable和current ZooKeeper的稳定版本是 3 7 x 当前版本是 3 8 x 一旦发布新的次要版本 稳定版本预计将很快退役 大
  • 使用开散列实现对字典的查找插入删除(C++实现)

    一 需求分析 问题描述 实现对字典的查找 基本要求 在分块查找 AVL树 哈希查找 B树或者B 树查找中选择一种你认为最高效的动态查找方法对字典 单词 词性加释义 在内存中的动态查找结构或者在外存的字典文件的构造 查找 插入 删除 逻辑操作
  • JVM常见命令之JPS

    1 JPS Java Virtual Machine Process Status JDK1 5提供的显示当前所有进程pid的命令 2 jps q 只输出pid 3 jps l 输出应用程序main class 的完整package名或者应
  • Spring底层组件xxxAware家族

    搞懂xxxAware家族对理解Spring源码和提高代码能力也有帮助 Spring中常见xxxAware接口列举如下 ApplicationContextAware BeanNameAware EmbeddedValueResolverAw
  • 实时数仓实践以及架构

    前言 数据智能 Data Intelligence 有一个必须且基础的环节 就是数据仓库的建设 同时 数据仓库也是公司数据发展到一定规模后必然会提供的一种基础服务 从智能商业的角度来讲 数据的结果代表了用户的反馈 获取结果的及时性就显得尤为
  • IDEA在Debug模式下 对象转JSON

    如果文章对你有帮助欢迎 关注 点赞 收藏 一键三连 一起努力 IDEA在debug模式下面是不能直接复制被调试的Object为Json字符串的 但是在工作中经常会用到json入参 这里用两种方式实现这个操作 方便进行其他操作和工作交流 一
  • eds能谱图分析实例_EDS那些事儿

    什么是EDS 我们通常所说的EDS全称为能量色散X射线谱仪 简称能谱仪 可同时记录所有X射线谱 用以测量X射线强度与X射线能量的函数关系 是一种不损坏试样的快速微区成分分析方法 通过测量材料被激发的特征X射线能量进行元素的定性分析 测量特征
  • fake-useragent,python爬虫伪装请求头

    在编写爬虫进行网页数据的时候 大多数情况下 需要在请求是增加请求头 下面介绍一个python下非常好用的伪装请求头的库 fake useragent 具体使用说明如下 安装fake useragent库 pip install fake u
  • 复杂美区块链溯源系统架构

    从功能架构上 复杂美将区块链存证溯源系统按照功能划分为区块链核心层 接口层 运维管理层 溯源平台层和用户端层 1 区块链基础层 面向整个存证溯源平台提供基础信息服务 主要是为上层架构组件提供基础设施 保证上层服务可靠运行 源数据从IOT设备
  • 数据结构-链式存储

    数据结构 一 数据结构的定义 一组用来保存一种或者多种特定关系的数据集合 二 数据与数据之间的关系 lt 1 gt 数据的逻辑结构 数据元素与元素之间的关系 集合 关系平等 线性结构 元素之间一对一的关系 表 队列 栈 树形结构 元素之间一
  • 模式分类识别

    模式分类识别 DBN深度置信网络数据多特征分类预测 Matlab完整程序 目录 模式分类识别 DBN深度置信网络数据多特征分类预测 Matlab完整程序 分类结果 基本介绍 程序设计 参考资料 分类结果
  • Host文件

    linux中 etc目录 配置文件 etc目录包含了系统特有的配置文件 所谓配置文件 就是用于控制程序运行的本地文件 它绝大多情况下都说 只读 的私有文件 而且是可编辑的 这里的可编辑是指能直接看懂的 所以那些二进制可执行文件是不能作为配置
  • springboot多数据源---2事务

    一 多数据源事务控制 在多数据源下 由于涉及到数据库的多个读写 一旦发生异常就可能会导致数据不一致的情况 在这种情况希望使用事务 进行回退 但是Spring的声明式事务在一次请求线程中只能使用一个数据源进行控制 但是对于多源数据库 1 单一
  • 在webstorm 中直接运行ts文件

    原文链接 在webstorm 中直接运行ts文件 上一篇 ubuntu 使用 Apache Bench 进行并发测试 下一篇 使用js解数独难题 安装插件后重启IDE Run Configuration for TypeScript
  • Notice: Use of undefined constant submit - assumed 'submit'

    Notice Use of undefined constant submit assumed submit in D wamp www ECMS insert monitors php on line 66 Notice Undefine
  • vue期望值与实际值比较:折线图

    效果图 点击上方对应按钮 下方相应的数据图可隐藏 显示 代码 一 下载echarts包 终端运行 npm install echarts 二 components HelloWorld vue
  • Python3,一行代码解析地址信息,原来物流单的地址是这样拆分。

    1行代码解析地址信息 1 引言 2 代码示例 2 1 简介 2 2 安装 2 3 实战 2 3 1 提取省市区信息 2 3 2 提取街镇乡 村或居委会信息 2 3 3 自动补全省市信息 3 总结 1 引言 小屌丝 鱼哥 你说咱们发快递时填写
  • 页式存储,段式存储,段页式存储,引入快表等访存次数

    王道的说法 页式存储 2次 第一次 访问内存中的页表 利用逻辑地址中的页号查找到页帧号 与逻辑地址中的页内偏移拼接形成物理地址 第二次 得到物理地址后 再一次访问内存 存取指令或者数据 段式存储 2次 同上 段页式存储 3次 第一次 访问内
  • 【译】Rust 实现一个 DNS 客户端,我从中学到什么

    What I learned from making a DNS client in Rust 译文 Rust 实现一个 DNS 客户端 我从中学到什么 原文链接 https blog adamchalmers com making a d