月度归档:2015年06月

DNS:域名系统(二)

文章《DNS:域名系统(一)》从 DNS 系统的结构和工作原理的角度描绘了 DNS 系统的基本概貌,本文将继续深入探讨 DNS 的话题,以 DNS 客户端的视角窥探 DNS 协议的细节。

在 Internet 中,DNS 客户端与服务器之间使用 TCP 或 UDP 协议传送 DNS 消息,服务器端开放的端口号均为 53。在大多数情况下,客户端通过 UDP 协议向服务端发送 DNS 查询请求,服务端也通过 UDP 协议向客户端发送响应。在客户端或服务端认为一次查询或响应需要运载的数据量超过 512 字节时,需要改用 TCP 协议来传送请求或响应,后文将对这种情形有所解释。

DNS 请求与响应的消息均由消息头部与消息实体两部分组成,请求与响应的消息头部的格式是一致的,下面是 DNS 消息头部结构的图示[1]

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      ID                       |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    QDCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  • ID 是一个 16 位的标识符,由客户端根据具体情况设置,当服务端回应这个请求时,会将同样的 ID 值填写在响应消息头部的 ID 字段中。
  • QR 是一个占 1 位的字段用于标记这个 DNS 消息是请求(Query)还是响应 (Response)。当 DNS 消息是一个请求消息时,这个字段被设置为 0,反之当 DNS 消息是一个响应消息时,这个字段被设置为 1。
  • Opcode 是一个 4 位的操作代码,它由客户端设置,服务器在响应中会填入与对应请求相同的 Opcode。目前,Opcode 有三种有效的取值,它们的值与含义如下:
    • 0 – 标准请求。
    • 1 – 反向解析请求,用于查询 PTR 记录。
    • 2 – 服务器状态查询。
  • 接下来是一个包含 4 位的 flag,从低到高依次是 AA、TC、RD 和 RA,每个 flag 的含义分别如下:
    • AA(Authoritative Answer)标记只在响应消息中有效,置位表示该响应来自请求域名对应的域中的授权服务器。
    • TC(Truncation)标记表示该消息因传输长度的限制而被截断。通常发生在客户端以 UDP 协议发起解析请求,而服务端的全部响应超过 512 字节时,服务端将响应在 512 字节处截断,并置位 TC,提示客户端收到的响应可能是不完整的。
    • RD(Recursion Desired)递归解析请求,要求 DNS 服务端以递归解析的方式处理请求。由客户端在请求中设置,服务端在响应中置位与对应的请求一致。值得注意的是,DNS 协议不强制要求所有 DNS 服务器提供递归解析的功能,因此即使客户端在请求中置位 RD,也有可能不会得到期望的响应,可以用下面提到的 RA 标记判断服务器是否支持递归解析。
    • RA(Recursion Available)递归可用。这个标记由服务端设置,在响应中有效。当 RA 置位时,表示该服务器支持递归解析,向它发起的递归解析请求都能得到期望的结果。
  • Z 所在的位置是一段 4 位的保留区域,必须填为 0。
  • RCODE(Response code)是由服务端在响应中设置的 4 位响应代码,不同的值含义分别如下:
    • 0 – 没有错误。
    • 1 – 格式错误。服务器无法解释请求。
    • 2 – 服务器失败。因为服务器的某些故障而不能完成解析请求。
    • 3 – 名字错误。这个错误代码只会出现在授权的域名服务器的响应中,含义为请求查询的域名不存在。
    • 4 – 未实现。当前服务器不支持这种查询。
    • 5 – 拒绝。服务器主动拒绝当前的查询请求。
    • 6 ~ 15 – 为其他失败保留的代码。
  • QDCOUNT 是一个 16 位的整数,表示消息实体中问题的数量。
  • ANCOUNT 是一个 16 位的整数,表示消息实体中答案的数量。
  • NSCOUNT 是一个 16 位的整数,表示消息实体中 NameServer 记录的数量。
  • ARCOUNT 是一个 16 位的整数,表示消息实体中授权记录的数量。

值得注意的一点是,当客户端程序通过 UDP 协议发起一次 DNS 解析请求,后又收到一个 TC 置位的服务器响应时,客户端程序一般要用 TCP 协议与服务器建立连接,重新发起一次解析请求以获得完整的解析结果。

消息实体紧随消息头部,消息实体由问题部分和资源记录部分组成,DNS 请求的消息实体只包含问题部分,DNS 响应的消息实体既有问题部分又有资源记录部分。其中资源记录又分三大块:DNS 回答资源记录、授权服务器资源记录和附加的资源记录。一个包含消息头部和完整消息实体的 DNS 消息如下所示。

    +---------------------+
    |        Header       |
    +---------------------+
    |       Question      | the question for the name server
    +---------------------+
    |        Answer       | RRs answering the question
    +---------------------+
    |      Authority      | RRs pointing toward an authority
    +---------------------+
    |      Additional     | RRs holding additional information
    +---------------------+

Question 是 DNS 解析请求的问题部分,在 DNS 响应消息中也包含这个部分,内容与对应的请求消息中的 Question 一致。Question 中有请求解析的域名、请求的资源类型和网络类型。Question 的结构如下所示。

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                     QNAME                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QTYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QCLASS                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

QNAME 是被查询的域的域名,它的长度是不定长的。在《DNS:域名系统(一)》中提到,域名系统树中任意一个结点域名是由该结点到根结点的路径上经过的所有结点的标签一起组成的,书写上用圆点分隔这些标签(label),如 work.luodichen.com 等。域名在 QNAME 中的格式与书写格式稍有不同,同样是从枝叶到树根的标签依次排列,但每个标签之前还有一个字节表示该标签的长度。注意,树根的 NULL 结点也要包含在其中,树根总是以一个值为 0 的字节表示,同时也是 QNAME 字段结束的标识。例如域名 work.luodichen.com 在 QNAME 中存储形式的 C 语言表示如下:

特别地,当解析的目标域名是根域名时,QNAME 的格式同样遵循上面所说的规则,向 QNAME 中填入根域名的长度 0,其后不再有任何字符。

QTYPE 是一个 2 字节的查询类型字段,表示希望解析域名的资源记录的类型,DNS 协议规定的资源记录类型较多,有些是已经废弃不用的,有些是为了适应互联网的发展新加的。下面的代码列出了几种常见的资源记录类型的缩写、数值和说明。

QCLASS 字段表示网络类型,一般设置为 1(Internet)。

DNS 响应消息中包含 Answer、Authority 和 Additional 等三组资源记录,每一组资源记录都有 0 个或多个资源记录,上文已经提到,每组资源记录的数目在协议头部对应的字段中。每个资源记录的格式都是一致的,它们的结构如下所示:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                                               /
    /                      NAME                     /
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     CLASS                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TTL                      |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                   RDLENGTH                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
    /                     RDATA                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

NAME 是变长字段,表示该资源记录对应的域名,它的格式可能是压缩形式的,后文中将讨论 DNS 消息中压缩名字的格式。

TYPE 与 CLASS 与上文所说的 QTYPE 和 QCLASS 类似,表示这个资源记录的类型与网络类型。

TTL 是一个 32 位的整数,表示这个资源记录的生存时间(秒),TTL 是一个资源记录能在 DNS 缓存中生存的最长时间。

RDLENGTH 是一个 16 位的整数,它表示 RDATA 字段的长度(字节)。

RDATA 是一个变长的字段,根据资源记录类型(TYPE)的不同而有不同的格式,例如在 A(IP 地址)记录中,RDATA 是一个 32 位的 IP 地址。更多资源记录类型对应的 RDATA 格式请参考文本参考文献中提到的 RFC 1035 文档和其他相关的 RFC 文档。

为了节省 DNS 查询和响应的网络流量,DNS 协议使用一种压缩的策略来消除 DNS 消息中域名相关字段可能产生的重复内容,从而减小 DNS 消息的长度,这对于一种主要使用 UDP 来传送消息的协议来说是很重要的。前文中提到,一个域名可以表示为多个标签连续排列,并在每个标签前用一个字节表示标签长度的方式来表示,为了下文描述方便,将这个字节称为该标签的“标签头”。此外,DNS 协议还允许使用一种指针的方式引用消息实体中当前标签所在的位置之前任意位置出现过的域名或域名的一部分。DNS 规定,当一个“标签头”的高 2 位都为 1 时,这个标签头余下的 6 位以及标签头后一个字节的 8 位共 14 位构成一个指针。指针的整数数值是一个偏移量,偏移的起始位置是消息实体的第一个字节,指针指向的是另一个标签头,因为指针的目标可能是一个普通的标签头也可能是另一个带指针的标签头,因此需要递归地处理新的标签头,直到遇到根结点标签。下面是一个在 Answer 资源记录中 NAME 字段以指针表示的实例,可以看到 Answer 资源记录的 Name 字段是 0xc00c,指针偏移值是 12,指向 Queries 中 Name 字段的一个域名 luodichen.com。

dns-compress图 1 解析域名 luodichen.com 的响应消息

在编写文本的同时,还用 C++ 开发了一个同时支持 Windows 和 Linux 平台的 DNS 客户端库,源码托管在 GitHub,查看

(允许转载本文,转载请注明出处 http://luodichen.com/blog/?p=367 )

参考文献
[1] P. Mockapetris, DOMAIN NAMES – IMPLEMENTATION AND SPECIFICATION, RFC 1035, November 1987; www.ietf.org/rfc/rfc1035.txt

HTTP 协议概貌

HTTP 协议诞生于 1990 年,最初被设计用于传输 WWW 的超媒体数据。在二十多年的互联网发展过程中,web 应用始终都是互联网重要角色,HTTP 协议也得到了广泛的应用。HTTP 协议发展时间较长,在开发上基于 HTTP 的服务端与客户端组件非常丰富,用 HTTP 协议搭建不论是 B/S 模式的应用程序还是 C/S 模式的应用程序都十分简便。再加上它具有很好的灵活性,在互联网技术高度发达的今天,除了常规的 web 应用之外,HTTP 协议还广泛被运用于各种多媒体流服务、下载服务、桌面和移动客户端应用等。

HTTP 是一个基于请求/响应模型的协议,用户代理程序(user agent,对于 web 应用来说,是浏览器,以下简称“用户”)向原始服务器(origin server,以下简称“服务器”)发送 HTTP 请求(request);服务器收到请求后,解析请求并向用户发送相应的 HTTP 响应(response)*

HTTP 请求与响应消息格式都由以下几个部分按顺序组成:

  • 起始行;
  • 消息头部;
  • 标识头部结束的空行;
  • 可选的消息主体(message body)。

除了消息主体可能存在二进制格式的信息之外,HTTP 协议的其他部分都由 ASCII 字符流组成。消息各部分按“行”分割,每一行以 CRLF 结束。像上面提到的,HTTP 的请求和响应消息的第一行都称作起始行,起始行中包含请求资源的类型、资源位置、HTTP 版本号、响应的状态等信息,以下将具体讨论 HTTP 协议的起始行。

请求起始行

HTTP 请求的起始行由方式(method)、资源ID(URI)和协议版本号组成,三个部分以空格分隔。下面是一个 HTTP 请求行的示例:

GET http://luodichen.com/ HTTP/1.1

这个请求起始行的含义是以 GET 方式向服务端请求URI http://luodichen.com/ 的资源,HTTP 协议版本号为 1.1。

GET 是最常见的请求方式,表示希望服务端返回请求 URI 所在的资源数据。另外一个最常见的请求方式是 POST,POST 常用于像提交表单之类需要向服务端传送少量信息的场合,服务端收到 POST 请求之后,也可以视情况响应相应的消息。当发送一个 POST 请求时,需要将要传送的数据填写在消息主体中。除了 GET 和 POST 方式之外,HTTP 协议还定义了其他多种请求方式,要了解每种请求方式的详细情况请查阅参考文献中引用的 RFC 2616 文档。HTTP 1.1 协议中定义的所有请求方式如下:

OPTIONS
GET
HEAD
POST
PUT
DELETE
TRACE
CONNECT

请求 URI 可以是一个完整的 HTTP URL,如 http://luodichen.com/blog/,也可以是一个相对路径,如 /blog/。相对路径表示相对于服务器 HTTP 根目录的路径,当 URI 本身就是根目录时,应当将 URI 设置为 ‘/’。当 URI 是一个相对路径时,必须在请求的 HTTP 头部中包含 Host 字段,如 Host: luodichen.com。有关 HTTP 头部的详细情况将在下文中讨论。

URI 中还可以携带一些发送给服务端的参数,参数以符号 ‘?’ 开头,由 key=value 形式组成,每一组参数用符号 ‘&’ 分隔。当参数的 key 或 value 中含有一些特殊字符时,应当对它们进行 urlencode 编码。下面是一个典型的带有参数的 URI:

/login/?user=username&pass=pass%26%7bword%7d

响应起始行

响应的起始行在 RFC 中被称为状态行(status-line)。状态行依次由 HTTP 协议版本号、三位数数字状态码和原因语句组成,三者由空格分隔,中间无换行或回车符。

接收到请求之后,服务端根据具体情况会产生 5 类响应,这 5 类响应对应状态码的头一位数字 1-5。这些响应状态码的具体描述如下:

  • 1xx – 消息。请求已收到,将继续处理;
  • 2xx – 成功。请求已经成功收到,并且是合法的请求,且已经执行;
  • 3xx – 重定向。要求执行另一个动作;
  • 4xx – 客户端错误。请求包含错误的格式,或请求不能被通过;
  • 5xx – 服务端错误。请求格式应该是正确的,但服务端执行请求时发生了错误。

200 是最常见的响应代码,表示请求成功,请求所要求的信息已经包含在响应中。301 和 303 也是比较常见的响应代码,通常用于服务端要求跳转。其中 301 表示永久转移(Moved Permanently),303 表示临时跳转到另一个资源。404 是最常见的错误代码,它的含义是未找到(Not Found),当向服务端发送一个 GET 请求,而服务端没有找到请求 URI 指示的资源时,会响应 404 错误代码,提示用户端发送了不存在的 URI 请求。

除此之外还有很多 HTTP 状态码,下面是在 HTTP 1.1 中定义的所有状态码和他们对应的原因语句。

100 Continue
101 Switching Protocols

200 OK
201 Created
202 Accepted
203 Non-Authoritative Information
204 No Content
205 Reset Content
206 Partial Content

300 Multiple Choices
301 Moved Permanently
302 Found
303 See Other
304 Not Modified
305 Use Proxy
307 Temporary Redirect

400 Bad Request
401 Unauthorized
402 Payment Required
403 Forbidden
404 Not Found
405 Method Not Allowed
406 Not Acceptable
407 Proxy Authentication Required
408 Request Time-out
409 Conflict
410 Gone
411 Length Required
412 Precondition Failed
413 Request Entity Too Large
414 Request-URI Too Large
415 Unsupported Media Type
416 Requested range not satisfiable
417 Expectation Failed

500 Internal Server Error
501 Not Implemented
502 Bad Gateway
503 Service Unavailable
504 Gateway Time-out
505 HTTP Version not supported

关于这些状态更详细的说明,请参阅文末参考文献中引用的 RFC 2616 文档中的第 10 节。

HTTP 协议还允许服务端程序根据具体情况定义扩充的状态码,并且也有一些服务端程序这样做了。例如 nginx 定义了状态码 495、496、497、499 等[2]

头部字段

HTTP 的头部字段均为 key-value 形式,每个字段各占一行,行末由 CRLF 结束。其中有一些字段是请求或响应特有的字段,还有一些是既可能出现在请求中又可能出现在响应中的字段,称为通用字段。HTTP 1.1 协议定义全部通用头部字段列出如下:

Cache-Control
Connection
Date
Pragma
Trailer
Transfer-Encoding
Upgrade
Via
Warning

HTTP 请求特有的字段列出如下:

Accept
Accept-Charset
Accept-Encoding
Accept-Language
Authorization
Expect
From
Host
If-Match
If-Modified-Since
If-None-Match
If-Range
If-Unmodified-Since
Max-Forwards
Proxy-Authorization
Range
Referer
TE
User-Agent

HTTP 响应特有的字段列出如下:

Accept-Ranges
Age
ETag
Location
Proxy-Authenticate
Retry-After
Server
Vary
WWW-Authenticate

与数据实体有关的字段列出如下:

Allow
Content-Encoding
Content-Language
Content-Length
Content-Location
Content-MD5
Content-Range
Content-Type
Expires
Last-Modified

HTTP 协议同样允许用户端和服务端自行定义新的扩充字段。下文中会提及几个重要的头部字段的含义和协议细节,关于完整的 HTTP 协议头部字段的定义请参阅文末参考文献中引用的 RFC 2616 文档中的第 14 节。

定义用户端接受的内容格式

HTTP 协议用于传送多种格式的资源,用户端程序可能不能接受全部类型的格式,因此,在向服务端提交请求时,用户端程序可以在 HTTP 头部中设置 Accept 字段以告知服务端自己支持的资源格式。Accept 字段由一个或多个媒体范围组成,多个媒体范围之间用逗号分隔,媒体范围还可以接一个可选的 q 参数。下面是一个 Accept 字段的示例:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

媒体范围的格式是 主类型/子类型,* 表示匹配所有的类型。q 参数是一个范围在 0-1 的浮点数,用于标识它所附加的媒体范围的优先级,当服务端同时支持 Accept 中列的多个媒体范围时,优先匹配 q  值较大的那个。当 q 值缺省时,它的默认值是 1。

q 参数在多个头部字段中都可能出现,含义均是在多个选项中选择时的优先级,关于这一点,下文不再赘述。

定义用户端接受的字符集

当 HTTP 用于传送文本内容时,由于不同的用户端程序可能接受不同的字符集,因此在向服务端提交请求时,用户端可以在 HTTP 头部设置 Accept-Charset 字段,以通知服务端自己支持的字符集。当支持多个字符集时,字符集之间用逗号分隔,并可以加上一个可选的 q 参数。下面是一个 Accept-Charset 字段的示例:

Accept-Charset: iso-8859-5, unicode-1-1;q=0.8

其他 Accept-XXX 字段

此外,还有用于压缩格式的 Accept-Encoding 字段、用于指示支持的语言的 Accept-Language 字段等。

消息内容的格式

当请求或响应中包含消息实体(内容)时,需要设置 Content-Type 指定消息的类型。这个字段是一个 MIME Type 值。下面是一个 Content-Type 的示例:

Content-Type: application/json

最初的 HTTP 协议在设计上并没有考虑处理状态和会话等问题,HTTP 是一个无状态的协议,直到 1994 年 Netscape 浏览器支持了一种叫 cookie 的技术。目前,cookie 技术在 web 应用中的地位已经十分重要,关于 HTTP cookie 的话题留到后续博文中讨论。


* 这里的 user agent / origin server 术语与 TCP/IP 中的 client / server 术语有微小的差别。术语 client / server 着重表示发起连接的程序(客户端)与接受连接的程序(服务端)之间具有一条可通信的链路,而 user agent / origin server 着重表示请求的发送者(user agent)与请求的接收和响应者(origin server)。事实上,在存在代理的情况下,user agent 与 origin server 之间不存在可以直接通信的链路,反之它们之间存在可直接通信的链路。

(允许转载文本,转载请注明出处:http://luodichen.com/blog/?p=344 )

参考文献
[1] R. Fielding, J. Gettys, J. Mogul, et al, Hypertext Transfer Protocol — HTTP/1.1, RFC 2616, June 1999; www.ietf.org/rfc/rfc2616.txt
[2] Wikipedia. List of HTTP status codes[EB/OL]. http://en.wikipedia.org/wiki/List_of_HTTP_status_codes