【译】手撕 RFC 的正确姿势

原文: How to Read an RFC

在实验室时,读 RFC 就是一件令人痛苦的事情。今天搬运了一份 RFC 查阅指南,有助于大家查阅的时候保持一个良好的心情。作者是 Charles Eames ,从2000年起就活跃于 IETF,现为 HTTP and QUIC Working Groups 的联席主席。


甭管好孬,请求评论(RFCs)正是今天我们在互联网上定义繁多协议的方式。一开始这些文件会被试图解读它们的开发者奉为圭稚,很快又因为难以理解而被弃之不顾。这种事情令人沮丧,更严重的是会导致互通性和安全上的问题。

但是(敲黑板),只要了解它们是如何创建和发布的,就会更容易理解它们。以下是我从事 HTTP 和其他一些工作中总结出来的经验。

从哪里开始

获取 RFC 文件的标准地方是 RFC Editor Web Site。但是,正如下面将会看到的,这个网站上缺少一部分关键信息,所以大多数人会选择从 tools.ietf.org 获取。

因为 RFC 文件实在浩如烟海(目前数量已经超过了9000!),从中找到正确的 RFC 也变成一件困难的事。显然你可以借助 Web 搜索引擎来检索,同时 RFC Editor 网站也提供了出色的站内搜索功能。

另一个选择是 EveryRFC ,在这个网站(作者搞的)允许通过 RFC 名称和关键词一起来进行搜索,以及通过标签进行探索。

毫无疑问,纯文本形式的 RFCs 排版丑陋而难以阅读,但是事情正在往好的方向发展。RFC Editor 正在包装一种新的 RFC 格式,新的格式将会有更好的呈现,且允许进行定制。同时,如果想要获取更多有用的 RFC,可以使用针对特定范围的第三方分支。如 greebytes,维护了和 WebDAV 相关的 RFC;HTTP Working Group 维护了和 HTTP 相关的 RFC。

RFC 的类型

所有的 RFC 在顶部都有一个规范的标识,像这样

Internet Engineering Task Force (IETF)                  R. Fielding, Ed.
Request for Comments: 7230                                         Adobe
Obsoletes: 2145, 2616                                    J. Reschke, Ed.
Updates: 2817, 2818                                           greenbytes
Category: Standards Track                                      June 2014
ISSN: 2070-1721

左上角的描述 Internet Engineering Task Force (IETF) ,意味着这是 IETF 出产的文档,虽然很少人知道,但是确实有其他不需要 IETF 达成共识就发布 RFC 的方式,比如 independent stream

事实上,有很多种流程方式可供发布一个文档。但是只有 IETF 流程才表示整个 IETF 组织已经审查过,并就该协议标准达成了共识

旧的文档(大约就是 RFC5705 以前的那些)在这个位置标明的是 Network Working Group,因此你需要再多做一点工作以判断他们是否代表了 IETF 的共识。通过一开始的Status of this Memo这个段落,或者 RFC Editor 网站可以帮助你进行判断。
sample

接下来是Request for Comments号。如果这里显示的是Internet-Draft,那么它还不是一个 RFC,仅仅是一个提案,一个任何人都可以写的提案。一个文档当前为一个提案并不意味着永远不会被 IETF 采纳。

Category为这几个描述之一:Standards TrackInformationalExperimentalBest Current Practice。这几个的区别有些时候不那么清晰,但只要是 IETF 出产的,就有合理的审查量。需要注意的是,InformationalExperimental并不是标准,即使是发布的时候有 IETF 的共识。

最后,作者被列在了文档头部的右侧。和学术文章不同,这里并没有列出所有对该文档作出贡献的人,贡献者这部分内容通常位于底部的Acknowledgmnets。在 RFC 这里,“作者”就是字面上的意思,就是谁写了这个文档。你经常会看到人名后缀Ed.,这表明他们仅仅是编辑者,因为这个文档早就存在了(比如修订 RFC 这种场景)。

它是最新的吗?

RFCs 是文档的一系列存档,不能更改,哪怕改的是一个单词也不行(看看 RFC7158 和 RFC7159的区别,仅仅是改了一个年份而已)。

结果就是,你必须要知道自己手头的是正确的文档。文档头部一些元数据能够帮到你。

  • Obsoletes列出了被本文档完全替代的 RFCs,你应该看这个,而不是被替代的旧文档。注意,当新版本的协议标准出炉后,并不代表旧版本的就得废弃。比如发布了 HTTP/2 协议并不会废弃 HTTP/1.1 协议,因为实现旧的协议仍然是合法和有必要的。但是,RFC7230 就替代了 RFC2616,因为都是对 HTTP/1.1 的定义。

  • Updates列出了本文档对其内容有实质修改的 RFCs,换句话说,如果你阅读了这些 RFCs,那么最好也阅读下本文档。

不幸的是,ASCII 格式的文本文档(比如在 RFC Editor 上的)没有告诉你,自己的内容被哪些文档更新,或者被哪些文档所废弃。这使得绝大多数人选择使用 tools.ietf.org 上的 RFC 分支,该分支恰好对这部分内容有很好的展示

[Docs] [txt|pdf] [draft-ietf-http...] [Tracker] [Diff1] [Diff2] [Errata]

Obsoleted by: 7230, 7231, 7232, 7233, 7234, 7235          DRAFT STANDARD
Updated by: 2817, 5785, 6266, 6585                          Errata Exist

页面上每一个号码都是一个链接,你可以方便地找到最新的那份。

但即便最新的文档通常也还存在问题。在页面的顶部,右侧可以看到一个警告信息Errata Exist,你可以到最顶部的Errata去访问这些信息。
Errata

Errata是对文档的修正和说明,只是并不足以形成一个新的 RFC。有些时候,它们对于协议的实现有重要的影响(比如,标准中的一个bug导致了重大的误解),所以值得读一下。

例如 RFC7230 的 errata。在读 errata 的时候,也要留心它们的状态,许多 errata 并没有被接受,因为它们只是对标准的误读。

理解上下文

一个比你预想的更普遍的现象是:开发者阅读了 RFC 的描述,根据他们看到的着手实现,最终完成的实物却和 RFC 作者想表达的意思相悖。

这是因为在阅读者会截取文档部分来阅读的情况下,想要找到一种保证不会出现误解的写作方式是极端困难的(比如对某些神圣经典的误读)。

结果就是,读 RFC 不仅要阅读直接相关的部分,还要阅读间接相关的一小部分,不管是在同一份标准文档,或在其他文档中。紧要关头,如果你无法通读文档,那么阅读所有可能相关的部分也足够了。

例如,规定 HTTP 头部由 CRLF 分隔,但是如果你跳过了这部分,将看到接收者将单个的 LF 识别为行终止符,并忽略所有前面的 CR。对不?

同样重要,需要提请大家注意的是许多协议在 IANA registries上建立了条目,以管理它们的扩展点。这些扩展点,而非标准文档,才是事实的源泉。例如 HTTP 方法的典范列表就在这里,而不在任何的 HTTP 标准文档中。

解读要求

几乎所有的 RFCs 在顶部都有类似下面这样的一段文字:

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
"OPTIONAL" in this document are to be interpreted as described in
BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all
capitals, as shown here.

RFC2119的这些关键词帮助定义了互通性,但有时也会让开发者糊涂。像这样的描述在标准中是常见的:

The Foo message MUST NOT contain a Bar header.

这个要求放置在协议单元(Foo message)前面。如果你正在发送一个 Foo message,显然不能包含 Bar header;如果包含了 Bar header,那就是一个不符合规范的消息。

但是这条要求对于消息的接收者来说就不那么清晰了,如果你收到一个带有 Bar header 的消息,咋整?

部分人会直接拒绝该消息,即使标准并没有规定要这么做。其他人仍会处理该消息,但是会剔除 Bar header,或者忽略它,即使标准明确规定所有的消息头都需要被处理。

这些差异,在无意中导致了互通性问题。正确的做法是遵循正常的头部处理过程,除非标准中要求不要那么做。

这是因为通常意义上,标准只是公开地定义了一些行为,换句话说,法无禁止即可为。因此,过度解读协议标准会在无意中造成破坏,因为你选择这样做的同时还有其他人选择那样做。

理想情况下,标准应该从处理消息方的行为的角度来定义,就像这样:

Senders of the Foo message MUST NOT include a Bar header. Recipients
of a Foo message that includes a Bar header MUST ignore the Bar header,
but MUST NOT remove it.

除了这些,最好在标准中找找有关错误处理的通用建议(比如 HTTP 的 Conformance and Error Handling 部分)

同样地,请记住这些要求的目标。大部分的标准文档有一套高度发展的术语,用于区分标准文档中的不同角色。

比如 HTTP 有代理,这是一个中介,有客户端有服务器端(但不是 User-Agent 或原始服务器)。需要注意所有这些角色的要求。

同样地,依赖于标准所针对的场景,在 HTTP 的一些要求中,区分“生成”消息和仅仅“发送”消息两个行为。留意这种类型的术语能够节省你瞎猜的功夫。

SHOULD

是的,SHOULD 有自己的段落。这个扭扭捏捏的术语传染了很多 RFC,除非下功夫去根除。RFC2119 是这样描述它的:

SHOULD  This word, or the adjective "RECOMMENDED", mean that there
        may exist valid reasons in particular circumstances to ignore a
        particular item, but the full implications must be understood and
        carefully weighed before choosing a different course.

实际上,作者通常用 SHOULD 和 SHOULD NOT 表达:“我们乐意你这样做,但是我们不能总是要求你这样做”。

例如,在 HTTP 方法的概览部分,我们看到这样的描述:

When a request method is received that is unrecognized or not
implemented by an origin server, the origin server SHOULD respond
with the 501 (Not Implemented) status code. When a request method
is received that is known by an origin server but not allowed for
the target resource, the origin server SHOULD respond with the 405
(Method Not Allowed) status code.

这些 SHOULDs 不是 MUSTs,因为服务器可以合理地决定采用另一种行为。如果服务器认为一个请求来自于一个攻击者,就可以直接断开连接,或者被请求的资源要求 HTTP 认证,就可以在返回405之前返回一个401错误(没有经过认证)。

SHOULD 并不意味着服务器就可以忽略所有的要求,就因为 SHOULD 看起来无需被尊重。

有些时候,我们看到 SHOULD 是这样的形式:

A sender that generates a message containing a payload body SHOULD
generate a Content-Type header field in that message unless the
intended media type of the enclosed representation is unknown to
the sender.

注意“unless”这个词,它规定了 SHOULD 允许的特殊情形。有争议的是,这里应该被定义为 MUST,因为 unless 条款仍然会生效,但这种写法风格在标准中有些普遍。

阅读样例

另一个普遍的陷阱是草草浏览标准,找到其中的样例,然后照着实现。

不幸的是,样例是最少得到作者关注的部分,因为只要协议有一点变动就需要更新这些样例。

结果就是,样例是整个标准文档中最不靠谱的部分。诚然,作者应该在发布之前再次彻底地检查这些样例,但是总会有错误成为漏网之鱼。

而且,一个完美的案例也许并不是为了阐释你正在查找的标准中的那部分内容。它们会出于简洁的原因被截断,也可能需要额外的一步解码才可读。

就算要花去更多的时间,更好的做法也应该是去读真正的文本内容,毕竟样例不能完全代表标准文档。

On ABNF

Augmented BNF 通常被用于定义协议单元。如:

FooHeader = 1#foo
foo       = 1*9DIGIT [ ";" "bar" ]

一旦你习惯了,会发现 ABNF 提供了一个易于理解的框架去描述协议的一些元素是什么样的。

但是,ABNF 是“负有野心的”。它建立了一种理想的消息格式,并要求你生成的消息必须要匹配它。它并没有规定如果接收到不匹配它格式的消息应该怎么处理。实际上,许多规范都没有说明 ABNF 和处理要求之间的关系。

如果你尝试严格遵守它们的 ABNF 格式,大多数协议会发生严重的问题,小部分正常。在上面的例子中,空格不允许出现在冒号的周围,但是你完全可以赌有些人会这样做,然后一些实现也同样会接受它。

所以,确认你读完了 ABNF 附近的附加要求和上下文,并意识到如果没有直接要求,就需要调整解析,使得接收的范围比 ABNF 要求的更大。

一些规范开始认识到 ABNF 的雄心壮志,明确定义了包含错误处理的解析算法。当定义明确了,就务必严格准守,以保障互操作性。

安全考量

RFC3552 开始,RFC 的模板中就包括了Security Considerations部分。

结果就是,鲜少有 RFC 发布的时候会没有一个关于安全的关键章节。审查过程不允许一个草案里写“本协议没有安全考量部分”。

所以,不管你是在实现或者部署一个协议,花点精力去阅读和理解安全考量部分是值得的。否则,很有可能栽到坑里。

遵循规范的参考(如果有的话)是一个好主意。如果没有,尝试查找一些术语,以增进对当前正在讨论问题的理解。

更多

如果一个 RFC 不能回答你的问题,或者你不能确认它内容里的描述,最好的办法就是找到最相关的 Working Group,在他们的邮件列表中提问。如果没有活跃的工作组能够解决问题中涉及的主题,试试合适的区域中的邮件列表。

提交一个 errata 通常不是你第一步应该采取的措施,先去找个人谈谈吧。

许多工作组现在采用 Github 来管理他们的规范,如果你对某个活跃的规范存疑,那么到 Github 上去提交问题。如果这个规范已经是一个 RFC,通常最好的做法是使用邮件列表,除非你找到一个相反的方向。

我确认还有很多关于如何阅读 RFC 的内容可以写,一些我已经写在这儿的内容也存在争议,但这就是我的思考。希望能帮到你。

猜你喜欢

转载自blog.csdn.net/alading2009/article/details/84187898
rfc