谷歌云支持工程师解决了一个棘手的DNS案例

2020-05-19 23:48:43

编者按:有没有想过Google Cloud技术解决方案工程师(TSE)是如何处理您的支持案例的?TSE是负责故障排除和确定客户报告的问题的技术根本原因的支持工程师。有些相当简单,但每隔一段时间,就会收到一张支持票证,需要几名专门的工程师进行故障排除。在这篇博客文章中,我们从一位Google Cloud技术解决方案工程师那里听到了他们最近解决的一个特别棘手的支持案例-DNS数据包丢失的案例。在此过程中,他们将向您展示他们在故障排除过程中收集的信息,以及他们是如何推理得出解决方案的。除了揭开一个深层次的漏洞之外,我们还希望这篇文章能让你对下次向Google Cloud Support提交工单时会有什么期待有一些了解。

故障排除既是一门科学,也是一门艺术。第一步是假设为什么有些东西会以意想不到的方式运行,然后证明这个假设是否正确。但是在你提出一个假设之前,你首先需要清楚地找出问题,并准确地表达出来。如果问题太模糊,那么你需要集思广益,以缩小问题的范围--这就是过程中“艺术”部分的用武之地。

这在Google Cloud环境中面临双重挑战。Google Cloud努力帮助确保客户隐私,因此我们作为您的技术解决方案工程师对您的系统没有写入权限。我们对系统的可见性也不像你们一样,我们绝对不能修改系统来快速测试我们的假设是否正确。一些客户相信他们可以给我们发送一个虚拟机ID,相信我们会像修理厂的汽车修理工一样修理它。但事实上,GCP支持案例更像是一次对话:与您沟通是我们收集信息、做出假设并证明(或反驳)它们的主要方式,一直到最终解决案例。

这是一个大团圆结局的支持案例的故事。它成功的原因之一是案例描述非常出色:非常详细和准确。以下是第一个支持票证的成绩单,为保护客户隐私而匿名:

此案的立案名称为“P1:严重影响--生产中无法使用的服务”。这意味着案例将在默认情况下“跟随太阳”,提供全天候支持(单击此处阅读有关支持案例优先顺序的更多信息),并且在每个区域班次结束时将分配给下一个区域团队。事实上,当我们在苏黎世的团队到达它的时候,案例已经被退回到世界各地的其他区域支持团队几次了。在此期间,客户已经实施了缓解措施。但由于他们没有找到根本原因,他们担心这个问题可能会再次出现在生产系统中。

有了所有这些信息,我们就可以开始进行部分故障排除了。

我们要做的第一件事是检查元数据服务器的日志和状态,以确保其正常工作。它是。元数据服务器响应169.254.169.254 IP地址,并负责解析域名等。我们还会仔细检查应用于VM的防火墙规则是否正确,并且没有阻止数据包。

这个问题很奇怪。到目前为止,我们唯一的假设是UDP数据包正在被丢弃,但nmap的输出现在证明它们没有被丢弃。我们在脑海中想出一些更多的假设和方法来验证它们:

libdns是否正常工作?=>;运行strace以检查它是否正在实际发送和接收数据包。

案例的当前所有者征求建议,因此我们决定立即与客户通话,进行一些实时故障排除。

我们发现digg+tcp google.com(TCP)可以工作,但diggoogle.com(UDP)不能。

我们在运行“digg”的同时运行tcpdump,我们看到UDP数据包正在返回。

我们运行strace digg google.com,我们看到dig正确地调用了sendmsg()和recvmsg(),但后者超时。

我们的班次快结束了,所以不幸的是,我们不得不把这个案子放到不同的时区去处理。这个案例在我们团队中已经很有名了,一位同事建议使用python scape y模块来创建一个原始的DNS包:

客户运行代码,DNS回复返回,应用程序收到它!这证实问题不可能出在网络层。

在又一次环游世界之后,案子又回到了我们的团队手中。我认为,如果案例停止在世界各地旋转,对客户更好,所以我将从现在开始保留案例。

同时,客户同意提供该映像的快照。这是令人难以置信的好消息:能够自己测试映像使故障排除变得非常迅速-不需要要求客户运行命令,将输出发送给我并对其进行分析,我可以实时执行所有操作!

我的同事们开始嫉妒了。我们在午餐时谈论这件事,现在还没有人知道发生了什么。幸运的是,客户并不太着急,因为他们有缓解措施,所以我们有更多的时间进行调查。因为我们有图像,所以我们可以做我们想做的所有测试。这太好玩了!

问系统工程师的一个非常著名的面试问题是“当你运行ping www.google.com时会发生什么?”这是一个很好的问题,因为它要求受访者描述从shell到用户领域、到内核再到网络的路径。我笑了:有时候面试问题在现实生活中其实很有用……。

我决定把这个问题应用到手头的问题上。粗略地说,当您尝试解析DNS名称时,会发生以下情况:

libdns检查系统配置以了解要询问哪个DNS服务器(在此图中:169.254.169.254,元数据服务器)。

libdns使用系统调用创建UDP套接字(SOCKET_DGRAM),并发送和接收包含DNS请求的UDP数据包。

内核与硬件交互以使用网络接口在网络上发送分组。

当联系元数据服务器时,数据包实际上是由管理程序捕获的,管理程序将其发送到元数据服务器。

元数据服务器魔术般地实际解析名称,然后以相同的方式发回回复。

测试1:在受影响的系统中运行strace,检查digi是否调用了正确的系统调用。

测试2:使用SPASYY检查是否可以绕过操作系统系统库解析名称。

测试3:对libdns包运行rpm-V,对库文件运行md5sum。

结果:库代码与正在运行的操作系统中的代码完全相同。

测试4:在没有该行为的虚拟机上挂载客户映像的根文件系统,运行chroot,查看DNS解析是否有效。

测试1:检查tcpdump,查看运行“digg”后收发的DNS数据包是否正确。

测试2:捕获流量以检查DNS请求是否正确发送和回复是否已收到

要配置内核运行时,可以使用命令行选项(GRUB)或sysctl界面。我在/etc/sysctl.conf中查看,您会发现有几个自定义配置。我觉得我说对了点什么。我忽略与网络无关或仅限于TCP的所有选项。剩下的是一堆net.core配置。然后,我选择主机解析有效的虚拟机,并逐个应用损坏的虚拟机中的所有设置。最终,我找到了罪魁祸首:

这是最终破坏DNS解析的设置!我找到了确凿的证据。但是为什么呢?我需要动机。

net.core.rmem_default是为UDP数据包设置默认接收缓冲区大小的方式。常用值约为200KiB,但如果您的服务器接收到大量UDP数据包,则可能需要增加缓冲区大小。如果新数据包到达时缓冲区已满,因为应用程序速度不足以使用它们,那么您将丢失数据包。客户正在运行一个应用程序来收集作为UDP数据包发送的指标,因此他们正确地增加了缓冲区,以确保不会丢失任何数据点。并且他们已经将此值设置为可能的最高值:2^31-1(如果您尝试将其设置为2^31,内核将返回“无效参数”)。

突然,我意识到为什么nmap和scape可以正常工作:它们使用原始套接字!原始套接字与普通套接字不同:它们绕过iptables,并且没有缓冲!

但是为什么“缓冲太大”会引发问题呢?它显然没有像预期的那样工作。

在这一点上,我可以在多个内核和多个发行版上重现该问题。该问题在内核3.x中已经存在,现在在内核5.x中也存在。事实上,如果你参选。

我开始寻找有效的值。使用简单的二进制搜索算法,我发现2147481343似乎可以做到这一点。这个数字对我来说没有任何意义。我建议客户试一下这个号码。客户回复说:它可以在google.com上使用,但不能在其他域名上使用。我继续我的调查。

我安装了dropwatch,这是一个向您显示数据包在内核中被丢弃的位置的工具。我应该早点用的。有问题的函数是UDP_QUEUE_RCV_SKB。因此,我下载了内核源代码并添加了几个printk函数,以准确跟踪数据包被丢弃的位置。我很快就找到了失败的特定IF条件。我盯着它看了一会儿,所有的一切都集中在一起:2^31-1,没有任何意义的数字,不起作用的域。这是__udp_enqueue_Schedule_SKB中的一小段代码:

大小是类型u16(无符号整数16位),并且存储分组的大小。

sk->;sk_rcvbuf的类型为int,存储的缓冲区大小默认情况下等于net.core.rmem_default中的值。

当sk_rcvbuf接近2^31时,添加数据包的大小可能会导致整数溢出。因为它是一个整数,它变成了一个负数,所以条件是真的,而它应该是假的(有关更多信息,也请查看关于带符号幅度表示的讨论)。

修复是微不足道的:强制转换为unsign int。我应用修复程序,然后重新启动。DNS解析再次起作用。

我将我的发现发送给客户,并将内核补丁发送给LKML。我感到高兴:拼图的每一块都终于拼凑在一起了。我可以确切地解释为什么我们观察到了我们所观察到的东西。但更重要的是,通过合作,我们能够解决问题!

诚然,这是一个非常罕见的案例。谢天谢地,我们处理的支持案例很少有这么复杂,所以我们可以更快地结案。不过,通过阅读此支持案例,希望您能够了解我们是如何开展工作的,以及您如何帮助我们尽快解决您的支持案例。如果您喜欢这篇博文,请给我们发电子邮件至[email protected],我们将在我们最热门的文档中挖掘其他棘手的支持案例进行讨论。