注册
登录
新闻动态
其他科技
返回
新工具:搞乱 DNS!
作者:
糖果
发布时间:
2024-03-26 11:07:21 (2天前)
来源:
2021/12/15/mess-with-dns/
你好!我一直在思考如何解释DNS一堆中 的 最后 一年。 我喜欢以非常实际的方式学习。因此,当我写一本杂志时,我经常说一些类似的话来结束杂志,“......了解更多信息的最好方法是四处玩耍和实验!”。 因此,我建立了一个站点,您可以在其中进行 DNS 实验,称为Mess With DNS。它有您可以尝试的实验示例,并且非常鼓励您提出自己的实验。 在这篇文章中,我将讨论我为什么要这样做,它是如何工作的,并为您提供比您想知道的更多的细节(设计、测试、安全、编写权威名称服务器、实时流更新等) ) # 截屏 这是使用它的效果的 GIF 截屏视频: ![](/user/files/25ugUkNNhYXr_t-8WsjcluGqIZXssGcmhW1uWC09XpA.gif) # 这是一个真正的 DNS 服务器 第一:Mess With DNS 为您提供了一个真正的子域,并且它正在运行一个真正的 DNS 服务器(地址是mess-with-dns1.wizardzines.com)。DNS 的有趣之处在于,它是一个全球系统,许多不同的计算机进行交互,因此我希望人们能够实际看到该系统的运行情况。 # 尝试使用 DNS 的问题 那么,是什么让尝试使用 DNS 变得困难呢? 1. 您可能不习惯在您的域上创建测试 DNS 记录(或者您甚至可能没有域!) 2. 许多发生在幕后的DNS 查询对您来说是不可见的。这使得更难理解发生了什么。 3. 您可能不知道要进行哪些实验才能获得有趣/令人惊讶的结果 弄乱DNS: 1. 为您提供一个免费的子域,您可以使用它进行 DNS 实验(例如ocean7.messwithdns.com) 2. 向您显示进入子域记录的所有 DNS 查询的实时流(“幕后”视图) 3. 有一个要尝试的实验列表(但你可以而且应该做任何你想做的实验:)) 您可以在 Mess With DNS 中尝试三种实验:“怪异”实验、“有用”实验和“教程”实验。 # “奇怪”的实验 当我进行实验时,我喜欢打破事物。我从看到事情出错时会发生的事情中学到的东西比从正确的事情中学到的要多得多。 因此,在考虑实验时,我想到了 DNS 出错的地方,例如: 1. 负 DNS 缓存让我等待一个小时让我的网站工作,因为我偶然访问了该页面 2. 必须等待很长时间才能缓存记录过期 3. 不同的解析器具有不同的缓存记录,这意味着我得到了不同的结果 4. 指向正确的 IP 地址,但服务器无法识别Host标头 与其将这些视为令人沮丧,我想 - 我会将这些变成一个有趣的实验,没有任何后果,人们可以从中学习!因此,我们建立了一个“怪异实验”部分,您可以在其中故意引起其中一些问题,并看看它们是如何发生的。 # “有用”和“教程”实验 “奇怪”的实验是我们花费最多时间的实验,但也有: - “教程”实验将引导您设置一些基本的 DNS 记录,如果您不熟悉 DNS 或只是为了帮助您了解站点的工作原理 - “有用”的实验,让实际的 DNS 任务(如设置网站或电子邮件) 我想我稍后会添加更多“有用”实验的例子。 # 实验对不同的人有不同的结果 我们在游戏测试中注意到的一件事是“奇怪”的实验对每个人都没有相同的结果,主要是因为不同的人使用不同的 DNS 解析器。例如,有一个名为“在创建 DNS 记录之前访问域”的负面缓存实验。当我测试那个实验时,它按描述的那样工作。但是我使用 Cloudflare (1.1.1.1) 作为 DNS 解析器的朋友在尝试时得到了完全不同的结果! 起初我对此感到压力很大——如果我知道每个人都有一致的体验,那对我来说肯定会更简单!但是,仔细想想,关于 DNS 的一个基本事实是人们对它没有一致的体验。所以我认为最好对这个现实诚实。 # 给出“幕后”解释 对于一些实验,幕后发生的事情并不是很明显,我想在最后提供解释。 我经常制作漫画来展示不同的计算机如何交互。但是我不想画一堆漫画(需要很长时间,而且很难编辑)。 所以我们想出了这种格式来显示不同角色的交互方式,使用图标来识别每个角色。它看起来像这样: ![](/user/files/N75aaVW-NgGP85MNG_Bnazn4GfLC6KwGHVLxAOu8A-Y.png) 老实说,我认为这可能会更清楚,我不喜欢图标的设计,但也许我以后会改进它:) 好的,这就是我要说的关于实验的一切。 # 设计很难 接下来,我想谈谈网站的设计,这对我来说是一个挑战。 我与Marie Claire LeBlanc Flanagan一起构建了这个项目,她是我的一位游戏设计师朋友,对学习有很多想法。我们在编写所有实验、设计和 CSS 时结对编程。 我曾经认为设计是关于事物的外观——网站应该看起来不错。但每次我和比我更擅长设计的人交谈时,他们总是问的第一个问题是“好吧,这应该如何 工作?”。这一切都与功能性和清晰度有关。 当然,该网站并不完美——它可能更清晰!但我们确实在此过程中对其进行了改进。所以我想谈谈我们如何通过用户测试使网站更加清晰。 # 用户测试非常有用 我们进行用户测试的方法是观察一些使用网站的人,让他们大声讲述他们的想法,看看他们有什么困惑。 我们做了 5 个这样的测试(非常感谢 Kamal、Aaron、Ari、Anton 和 Tom!)。我们从每个测试中得出大约 50 个要点的注释和可能需要更改的内容,然后我们需要将它们提炼为要进行的实际更改。由于用户测试,我们改进了以下 3 点: ###### 1.侧边栏 我们注意到的一件事是——我们有这个侧边栏,里面有人们可以尝试的实验。看起来是这样的,本来还以为可以的。 ![](/user/files/IFCC2ubfYx9EtJnRvesZn3zx_mSzUuOehe97lguH7Ps.gif) 但是在用户测试中,我们看到人们一直在侧边栏中感到困惑和迷失。我真的不知道如何改进它,但幸运的是 Marie 在这方面比我做得更好,我们提出了一个不同的设计,可以更好地隔离每个实验的信息。这是一个gif: ![](/user/files/jRwUj46pUwEnsHzkbkknYSD592epTMK1ZpwmsiSsL2M.gif) 此外,该 gif 中的所有内容都是用纯 CSS 实现的,带有一个
我认为很酷的标签。(这是我从中学到的代码笔) ###### 2. 术语 我们在用户体验测试中学到的另一件事是,很多人对我们使用的 DNS 术语(如“A 记录”或“解析器”)感到困惑。所以我们写了一个简短的 DNS 字典来尝试帮助解决这个问题。 ###### 3.使用说明 最初,在说明中我写了类似的东西 创建一个 CNAME 记录,将visit-after.fox3.messwithdns.com 指向orange.jvns.ca,TTL 为3600 在游戏测试中,我们注意到人们花了很长时间来解析那个句子并将其翻译成他们需要填写的字段。因此我们将说明改为如下所示: ![](/user/files/zwGfnIdAW9uurJvDQuR2FfMjsNIlV-UnmXxIif4GS7Y.png) 这让我感觉清楚多了。 这就是我要说的有关设计的全部内容,让我们继续进行实施。 # 前端测试自动化很棒 尽管它很小,但这是一个比我以前做过的更大的 Javascript 项目。通常我对 Javascript 的测试策略是“编写一堆糟糕的未经测试的代码,手动测试它,希望最好”。 但这次采取这种方法时,我不断破坏网站,我有一种熟悉的感觉“朱莉娅,你需要写测试,来吧,这太荒谬了”。 我知道像 Selenium 这样的前端测试自动化框架,但我已经很久没有使用它们了。我问汤姆我应该使用什么,他建议使用Playwright,所以我使用了它并且效果很好。 所有的 Playwright 测试都是集成测试,这真的很有帮助——即使它是一个前端测试框架,集成测试也帮助我在后端找到了一堆错误。 这是我的一个测试示例,它确保当我向后端发出 DNS 请求时,它会出现在前端。(前几天当我重构后端时,这个就坏了!) test('empty dns request gets streamed', async ({ page }) => { await page.goto('http://localhost:8080') await page.click('#start-experimenting'); const subdomain = await getSubdomain(page); const fullName = 'asdf.' + subdomain + '.messwithdns.com.' await getIp(fullName); await expect(page.locator('.request-name')).toHaveText(fullName); await expect(page.locator('.request-host')).toHaveText('localhost.lan.'); await expect(page.locator('.request-response')).toContainText('Code: NXDOMAIN'); }); 我不会真正解释细节,但希望这能给你一个基本的想法。由于我不太明白的原因,这些测试仍然有点不稳定,但我认为这可能是前端自动化的正常现象?我发现它们很容易编写,很可靠,而且非常有用。 好的,现在让我们谈谈后端,它更符合我的舒适区。这很有趣,我认为有很多东西很有趣。 如果你想阅读它,我已经在 GitHub 上放了一个后端代码的快照。 # 权威 DNS 服务器 我需要编写一个权威的 DNS 服务器,当我尝试做一些我以前没有做过的事情时,我采取了我常用的方法: 1. 从一个几乎什么都不做的空程序开始(复制自How to write a DNS server in Go) 2. 阅读 0 个文档,然后开始实施 3. 看看什么破 4. 不情愿地阅读一些文档来修复损坏的东西 我使用了这个很棒的 DNS 库 https://github.com/miekg/dns,我之前将它用于https://dns-lookup.jvns.ca/的后端 。 这是我的主要ServeDNS功能的样子,删除了一些错误处理和日志记录: func (handle *handler) ServeDNS(w dns.ResponseWriter, request *dns.Msg) { // look up records in the database msg := dnsResponse(handle.db, request) w.WriteMsg(msg) // Save the request to the database and send it to any clients who have a websocket open remote_addr := w.RemoteAddr().(*net.UDPAddr).IP LogRequest(handle.db, r, msg, remote_addr, lookupHost(handle.ipRanges, remote_addr)) } # 遵循 DNS RFC?不完全是 我认为我在遵循 DNS RFC 方面做得很糟糕。基本上我的返回记录的算法是: 要返回的 DNS 响应代码: 1. 返回NXDOMAIN如果我没有一个名称的任何记录,NOERROR如果我有一个记录 2. REFUSED如果有人请求不以结尾的名称,则返回错误.messwithdns.com. 3. 返回SERVFAIL如果我碰上了某种错误如不能够连接到数据库 要返回的查询: 1. 如果我有一个CNAME名字的记录,那么无论请求什么查询类型都返回它 2. 如果我收到HTTPS查询,请返回我拥有的任何 A/AAAA 记录。(我这样做是因为 Cloudflare 似乎就是这样做的,我收到了很多 HTTPS DNS 查询,我花了几分钟时间尝试阅读 IETF 草稿,了解 HTTPS 查询是什么,但我无法真正理解它,所以我给出了向上) 3. 否则只返回与请求类型匹配的任何记录 希望这足够接近,不会造成任何重大问题。我认为有些规则我没有遵守,比如如果已经有A一个名字的记录,我就不应该让人们创造CNAME记录。但我很懒,所以我没有实现。 我确实找到了这个用于权威 DNS 服务器的测试套件,我以后可能会花更多时间浏览它。 # 使用 websockets 和 Go 通道实时流式传输请求 我希望人们能够直播进入他们域的所有请求。当我第一次考虑它是如何工作时,我开始在谷歌上搜索诸如“firebase”和“google pubsub”和“redis”以及其他发布/订阅或流媒体系统。我开始以这种方式实现它,但后来我无法让它工作,我想……等等……我不想处理单独的服务! 因此,我使用 Go 通道 ( stream.go )编写了 60 行左右的代码。基本上每次有人打开 websocket 时,我都会创建一个 Go 频道 并将其存储在地图中。然后每当 DNS 请求进来时,我就可以将它发送到所有正在等待请求的通道。 这似乎很好用。最初我使用 SSE(服务器端事件)而不是 websockets,但由于某种原因它不能在一个朋友的计算机上工作,所以我切换到 websockets,这似乎更可靠。 # 权衡:没有分布式系统,但响应时间较慢 使用 Go 通道实现我的 pub/sub 系统意味着 DNS 服务器和 HTTP 服务器都需要存在于一个进程中,因此我无法运行多个 DNS 服务器。 现在,单一进程在弗吉尼亚州,这意味着如果您在东京或其他地方,HTTP API 和 DNS 响应会变慢。我认为这是可以的,因为它是一个教育网站 - 如果它有点慢也没关系! 这意味着我不需要处理任何分布式系统,这太棒了。分布式系统很烦人。 前端的静态文件是分布式的:我把站点放在 CDN 后面,这应该有助于让一切感觉更快。 # 使用 ASN 数据库找出谁拥有 IP 地址 当 DNS 请求进来时,它来自一个 IP 地址。我想告诉拥有该 IP 地址的用户(Google?Cloudflare?他们的 ISP?)。显而易见的方法是进行反向 DNS 查找。但如果这不起作用怎么办? 我首先使用 API 来查找 IP 的所有者。这很有效,但是,类似于发布/订阅问题,我想 - 为什么要依赖外部服务?我敢打赌,我可以自己做到这一点,而无需依赖可能会出现故障的 API。 所以我在谷歌上搜索“asn 数据库下载”,我找到了这个免费的IP 到 ASN 数据库,它列出了每个 IP 地址及其属于谁。 然后我只需要实现一个二分搜索 (好吧,从技术上讲,实现二分搜索的主要是 Github Copilot,我只是修复了错误),并且我可以超级快速地查找任何 IP 的所有者。 因此,IP 地址查找代码执行反向 DNS 查找,然后回退到 ASN 数据库。 # 关于选择数据库的一些注意事项 我开始使用Planetscale是因为他们有免费套餐,而且我喜欢免费套餐。 但后来我意识到我的应用程序写入量很大(每次收到 DNS 请求时我都会写入数据库!!),而且他们的免费套餐每月只允许写入 1000 万次。最初看起来很多,但我已经收到了 100,000 个 DNS 查询,而只有我在使用该服务,而突然间 1000 万个真的感觉并没有那么多。 所以我转而使用一个带有 10GB 卷的小型 Postgres 实例。我认为这应该是一个合理的磁盘空间量,因为即使我存储了很多请求,我实际上并不需要将这些请求存储那么长时间——我可以轻松地每小时清除旧请求,但它可能不会做出改变。 数据卷附加到运行 Postgres的无状态机器上,如果需要,我可以轻松升级它以提供更多 CPU/RAM。 我也很高兴使用 Postgres,因为我的搭档 Kamal 使用 Postgres 的经验比我多,所以我可以向他提问。 # 让我们谈谈安全 与nginx playground 一样,我也有一些安全问题。当我开始构建该项目时,我进行了设置,以便任何人都可以在messwithdns.com. 这让我觉得有点可怕,尽管我一开始无法弄清楚到底是为什么。 然后我实现了 Github OAuth,但这感觉也有点糟糕——登录会增加摩擦!不是每个人都有 GitHub 帐户! 最后我和 Kamal 聊了聊,我们决定我关心 3 件事: 1. 意外冲突,其中 2 个人被分配了相同的子域并感到困惑 2. 人们试图创建网络钓鱼子域 3. 我希望人们在域中使用简短、易于输入的内容 所以我写了一个简单的登录函数: 1. 生成一个ocean8以前从未使用过的随机子域 2. 保存ocean8到数据库表,以便其他人无法使用它 3. 向客户端发送安全 cookie(使用gorilla/securecookie) 4. 然后用户(您!)可以进行任何您想要的更改ocean8.messwithdns.com。 另外,网站的域名(https://messwithdns.net)是在不同的域托管完全不是在那里用户可以设置记录的域(这是https://messwithdns.com)。所以这意味着,如果有人以某种方式创造了记录messwithdns.com,至少它不会关闭该网站。 # 一些静态 IP 地址 关于实验的一个简短说明:我想要一些人们可以在实验中使用的 IP 地址,如果他们愿意的话。所以我设置了两个静态 IPv4 地址:orange.jvns.ca 和Purple.jvns.ca。它们分别显示了一个橙子和一些葡萄的图片,所以你可以很容易地看出哪个是哪个。 每个人都有一个专用的 IP 地址很重要,因为它们将被使用一堆不同的域访问,所以我无法使用域名来决定如何路由请求。 # 就这样 我希望这个项目可以帮助你们中的一些人更好地理解 DNS,我很想听听你们遇到的任何问题。同样,该站点位于 https://messwithdns.net,您可以在 GitHub 上报告问题。
收藏
举报
1 条回复
动动手指,沙发就是你的了!
登录
后才能参与评论