欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

关于Cloudflare DNS的闰秒故障深度探讨分析

来源:本站整理 作者:佚名 时间:2017-01-05 TAG: 我要投稿

2016年12月31日增加了一闰秒,31日11点59分59秒之后不是2017年,而是59分60秒。就在这个时候,在Cloudflare系统深处的一个自定义RRDNS软件出现了一个问题:一个数字变为了负数,但是按理说,它的最低值应该是零。稍后,这个负值就引起了RRDNS出现问题。该问题是通过Go语言的恢复功能捕获的。最后的结果就是,导致托管在Cloudflare上的某些网络资源无法正常进行DNS解析。
该问题只对使用的CNAME DNS记录的Cloudflare客户产生了影响,并且只影响到了Cloudflare 102个数据中心中的少数机器。在针对Cloudflare的DNS查询峰值时刻,大约有0.2%的查询受到了影响,同时,对于Cloudflare的所有HTTP请求中,只有不到1%的请求遇到了错误。
这个问题很快就被发现了。并且,大部分受影响的机器在90分钟内就得到了修复,到了UTC 06:45,修复程序已向全球用户发布。我们对客户受到了影响深表歉意,同时,为了让人们弄明白这个问题的来龙去脉,我们认为有必要把具体的原因和原理讲清楚。
关于Cloudflare DNS的背景知识
Cloudflare的客户是通过我们的DNS服务来为其域名提供DNS查询的权威答案的。他们需要告诉我们其原始Web服务器的IT地址,以便我们联系该服务器来处理非缓存请求。要想完成此项工作,可以借助两种方式:要么输入与名称有关的IP地址(比如example.com的IP地址是192.0.2.123,并作为一条A记录来输入),要么输入CNAME(比如example.com是origin-server.example-hosting.biz)。
下图显示的是一个测试网站,它不仅提供了theburritobot.com的A记录,还提供了www.theburritobot.com的CNAME,它直接指向Heroku。

当客户选用CNAME这种方式时,Cloudflare有时需要使用DNS查询原始服务器的实际IP地址。它是使用标准的递归DNS自动执行这项操作的。导致本次故障的软件bug,就位于执行这个CNAME查询的代码中。
在系统内部,执行CNAME查询时,Cloudflare运行DNS解析器,查询来自互联网的DNS记录,然后,RRDNS会跟这些解析器进行交互,以便获得IP地址。RRDNS会跟踪记录内部解析器的性能情况,并对可能的解析器(我们每个数据中心都会运行多个解析器,以确保冗余性)进行加权选择,选择性能最好的那个解析器。其中一些解析最后在数据结构中记录下了闰秒期间的一个负值。
稍后,这个负数被传递给了进行加权选择的代码,从而引发了问题。实际上,负数是由于闰秒和平滑处理(smoothing)这两个因素共同作用的结果。
程序员过于盲目相信时间值了
之所以出现影响我们DNS服务的那个错误,根本原因在于程序员坚信表示时间的值不会倒退。以我们为例,一些代码想当然地以为:在最糟糕的情况下,两个时间之间的时差总是为零。
RRDNS软件是用Go编写的,并且使用Go的time.Now()函数来获取时间。遗憾的是,这个函数无法保证单调性。Go目前没有提供单调的时间源(详情请访问https://github.com/golang/go/issues/12914)。
在评估用于CNAME查询的上游DNS解析器的性能时,RRDNS使用了下列代码:
// Update upstream sRTT on UDP queries, penalize it if it fails
if !start.IsZero() {
    rtt := time.Now().Sub(start)
    if success && rcode != dns.RcodeServerFailure {
        s.updateRTT(rtt)
    } else {
        // The penalty should be a multiple of actual timeout
        // as we don't know when the good message was supposed to arrive,
        // but it should not put server to backoff instantly
        s.updateRTT(TimeoutPenalty * s.timeout)
    }
}
在上面的代码中,如果time.Now()返回的时间早于start中的时间(这个值是早先调用time.Now()时设定的),rtt可能是负数。
如果时间往前进,该代码会一切正常。不幸的是,我们将我们的解析器调整得非常快,这意味着它们在几毫秒内进行应答是很正常的事。但是,如果恰好进行解析时,时间后退了一秒,那么解析时间就会成为负值。
实际上,RRDNS并非通过单个测量值来衡量每个解析器的性能,而是通过多个测量值,然后对它们进行平滑处理。所以,单个测量值不会引起RRDNS认为解析器在负时间内工作,但是对多个测量值进行平滑处理后,这个值最终就可能变成负值。
当RRDNS选择上游解析CNAME时,它使用了一种加权选择算法。相应代码取得上游时间值之后,会并将它们提供给Go的rand.Int63n()函数。如果rand.Int63n的参数为负数, 就会立即出现问题。这就是造成RRDNS问题的根本原因。
(除此之外,程序员在时间方面还有许多错误认识)。
一个字符搞定漏洞
当我们使用非单调时钟源的时候,一个需要注意的问题是,始终检查两个时间戳之间的差值是否为负数。如果出现负数,除非时钟停止倒回,否则就不可能准确地确定时间差。
在这个补丁中,我们让RRDNS忘记当前的上游性能,并且如果时间向后跳过的话,就让它再次正常化。这样就可以防止将负数传递给服务器的选择代码,从而避免了在联系上游服务器之前就抛出错误信息了。
我们使用的修复方法是防止在服务器选择代码中记录负值。之后,重新启动所有RRDNS服务器,就能解决这个问题了。
时间表
下面是闰秒错误相关事件的完整时间表:
2017-01-01 00:00 UTC 出现影响
2017-01-01 00:10 UTC 上报给工程师
2017-01-01 00:34 UTC 确认问题
2017-01-01 00:55 UTC 缓解措施部署到一个canary节点上,并加以核实
2017-01-01 01:03 UTC 缓解措施部署到canary数据中心,并加以核实

[1] [2]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载