标签归档:计算机网络

NAT:网络地址转换

之前的文章IP 与路由(一)中提到当前日益突显的 IPv4 地址耗尽问题,NAT 技术的广泛运用一定程度上缓解了该问题。NAT 是一种通过修改 IP 数据报头部信息来将一个 IP 地址空间映射到另一个 IP 地址空间的技术,这种映射操作通常由网络中的防火墙完成,包括硬件防火墙和软件防火墙。

NAT 的一个最广泛的应用场合是将一个局域网接入 Internet。在一个局域网中,通常每台主机都配置有私网地址,局域网中的各主机通过私网 IP 地址相互通讯。当局域网中的主机有访问 Internet 的需求时,需要先向 ISP 申请公网 IP 地址,再通过 NAT 设备将局域网中所有发往 Internet 的流量的源地址转换成这个公网 IP 地址。NAT 设备还会为局域网中所有主机与 Internet 的连接维护一个会话,保证从 Internet 返回的 IP 分组能正确地转发到局域网中对应的主机上。日常所见的家庭小型网络往往属于上述接入方式。

运用 NAT 技术可以达到使局域网中多台主机共享一个公网 IP 地址的目的,节约了 IP 地址。另一方面,NAT 向 Internet 隐藏了 NAT 背后局域网的地址空间、网络结构等细节;同时,由于 NAT 的存在,来自 Internet 的请求不能直接到达局域网,这使局域网中的主机免受来自 Internet 的探测、攻击和入侵。

基本 NAT

基本 NAT 是一种最简单的 NAT 形式。基本 NAT 配置有多个外网 IP 地址,在出站时,每个内网 IP 地址都会动态分配并映射到一个独立的外网 IP 地址。也就是说,基本 NAT 是一种只映射 IP 地址的 NAT,因此这种 NAT 又称为一对一 NAT。基本 NAT 需要维护一个会话表,将内网的 IP 地址与会话分配的外网 IP 地址一一对应起来。

在一个内网 IP 数据报穿过基本 NAT 去往外网时,NAT 会根据映射将这个 IP 数据报中的源 IP 地址字段修改为它对应的外网 IP 地址,并调整 IP 地址头部的校验和字段以及上层协议(TCP、UDP 等)中的校验和字段,然后将其交付给路由器。当基本 NAT 收到一个来自外网的 IP 数据报时,会将这个数据报头部中的目标 IP 地址字段修改为映射中对应的内网 IP 地址,同样调整校验和之后将数据报发往内网。

基本 NAT 只转换 IP 地址,不转换上层协议中的端口号等字段。

一对多 NAT

基本 NAT 只是简单地转换 IP 数据报中的 IP 地址,并不能起到复用外网 IP 地址的作用。当只从 ISP 处分配到一个外网 IP 地址,而内网中又有多台主机需要接入外网时,就需要使用一对多 NAT。一对多 NAT 通过转换上层协议的端口号来达到复用外网 IP 地址的目的,这种 NAT 方式也称为网络地址端口转换(Network Address Port Translation, NAPT)。日常所见的家庭小型网络等就是通过这种方式接入互联网的。

常见的运输层协议——例如 TCP 和 UDP 等——的数据报头部都有源端口号和目标端口号等字段,端口号用于区分同一台主机上不同的服务、进程等。NAPT 利用这一点,同时修改出站 IP 数据报头部中的源 IP 地址和上层协议的源端口号,为每一个出站会话动态分配一个未占用的端口号,并建立映射。NAPT 也需要维护一个会话表,将内网 IP 地址、上层协议号、上层协议源端口等与外网 IP 地址、上层协议号、外网上层协议端口等信息一一对应。

在一个内网 IP 数据报穿过 NAPT 运往外网时,NAPT 先在映射表中检查是否有与该 IP 数据报的源 IP 地址、上层协议号和上层协议端口号完全匹配的项目,如果有,则将它 IP 头部的 IP 地址信息修改为外网 IP 地址,将上层协议的端口号修改为映射表中记载的端口号;如果映射表中没有匹配的项目,那么先分配一个暂未使用的端口号,并写入映射表,然后再修改 IP 数据报头部的 IP 地址及上层协议的端口号。与基本 NAT 一样,在修改了这些字段之后还需要视情况调整 IP 数据报头部及上层协议头部的校验和字段。做完这些处理后,IP 数据报将被交付给路由器。

当 NAPT 收到来自外网的 IP 数据报时,通常需要根据 IP 数据报的目的 IP 地址、上层协议号和目的端口号等信息反向查询映射表,对 IP 数据报的 IP 地址等字段进行相应的转换然后发往内网。来自外网不匹配映射表的 IP 数据报将会被丢弃处理。

下图展示了一个典型的通过 NAPT 接入 Internet 的局域网。

napt图 1 一个通过 NAPT 接入 Internet 的局域网

NAPT 的映射表将内网的地址-端口与地址转换后对应的外网地址-端口建立了一种联系,并根据这种联系将外网主机返回的 IP 数据报转发给内网的主机。在实践中,有几种不同的 NAT 实现对来自外网的 IP 数据报的检查方式略有不同,这最终表现在对来自外网的 IP 数据报的处理行为有所不同,下文将详细讨论这几种情况。

 NAT 的几种过滤行为

以 UDP 数据报为例,当一个内网端点通过 NAT 建立一个出站会话时,NAT 会在内网地址-内网端口(记作 X:x)与目标的地址-端口(记作 Y:y)的映射之间指派一些过滤规则[1]。这意味着可能并不是所有来自外网的数据报都能被 NAT 映射到内网,其中有一些可能会被过滤掉。

1. 端点无关过滤

这种 NAT 只检查来自外网的数据报中的目标地址和端口,将无法在映射表中找到对应的内网地址-端口的数据报过滤掉,而不管这个数据报的源地址 Z 和源端口 z(记作 Z:z)是什么。这意味着这种 NAT 会将任何来自外网且能找到映射关系的数据报进行相应的转换并送往内网。

2. 地址相关过滤

同样,这种 NAT 也会将无法在映射表中找到对应内网地址-端口的数据报过滤掉。另外,这种 NAT 还会检查来自外网的数据报的源地址 Z,如果映射表中对应的主机 X:x 之前从未向 Z 发送过任何数据报,这个数据报也会被过滤掉。也就是说,一个来自外网 Z:any 的数据报能被 NAT 转换并发往 X:x 的条件是 X:x 曾经向 Z 主机的任一端口发送过数据报。

3. 地址端口相关过滤

这种 NAT 的过滤规则更加严格。除了不能在映射表中找到对应关系的数据报会被过滤之外,NAT 还会同时检查来自外网的数据报的源地址 Z 和源端口 z(Z:z),只要映射表中对应的主机 X:x 从未向 Z:z 发送过任何数据报,这个数据报就会被过滤掉。这种 NAT 的过滤执行的是最严格的检查,只有会话建立时内网主机指定的目标地址和端口发出的响应才能被转换并送回内网。

ICMP 报文的处理

ICMP 在网络测试、网络差错消息通知等方面有重要的应用,NAT 也应当支持 ICMP 报文。TCP、UDP 等运输层协议的头部中都有端口(Port)字段,可以在 NAPT 中发挥作用。而 ICMP 作为基于 IP 的网络层协议,它的头部中并没有端口字段,因此 NAT 对 ICMP 报文的处理略有不同于 TCP 和 UDP。

1. ICMP Echo Reply 报文

ICMP Echo Reply 应用于 Ping 程序。当在内网中向外网的主机应用 Ping 程序的时候,内网发出的 ICMP Echo 报文会穿过 NAT 送往外网主机,外网主机响应的 ICMP Reply 报文需要穿过 NAT 送达对应的发送者。

尽管 ICMP 不像 TCP、UDP 等协议一样头部中有端口号字段,但 ICMP Echo Reply 的消息中有一个 Identification 字段,这个字段的作用类似于 TCP/UDP 的端口号,用于区分同一个主机上正在运行着的不同 Ping 会话。因此,在对 ICMP Echo Reply 报文进行 NAT 处理时,可以直接将报文中的 Identification 字段当作端口号处理,为每个 NAT 会话分配一个新的 Identification——就像对待 TCP/UDP 的端口号一样——然后转换 IP 地址和 Identification,将转换过的报文送向外网。当 NAT 收到来自外网的 ICMP Reply 时,根据报文中的目的 IP 地址和 Identification 在映射表中查找对应的内网主机信息,然后完成相应的地址转换送往内网。

2. ICMP 差错报文

当一个 IP 报文在网络中发生了某些差错(如目标不可达、TTL 超时、分片问题等)时,处理该报文的主机会向发送者递交一个 ICMP 差错报文以通知发送者,这个从外网传回的差错通知报文也必须能通过 NAT 并正确地送达对应的内网主机。

与 ICMP Echo Reply 不同,ICMP 差错报文是由外网主机先发起的,而且这种报文中也没有 Identification 之类的信息,因此无法像对 ICMP Echo Reply 一样处理 ICMP 差错报文。好在 ICMP 差错报文会在数据区将出差错的 IP 数据报的头部以及首 8 字节的 data 载荷传送回来,NAT 在收到这种报文时可以将出差错的原始数据报头部的协议号、源地址和源端口号取出来,并在映射表中查询,找到对应的内网主机记录后完成转换和发送。

其他话题

一方面 NAT 技术的运用对外隐藏内网的细节,隔绝了来自外网访问和连接,在 NAT 的保护下的内网主机比直接暴露在外网的主机更安全;另一方面这种特性也使内网的主机不能直接对外提供服务,并对 P2P 应用造成了很大的影响,基于 P2P 的程序无法直接在两台内网主机之间建立连接。在 NAT 技术广泛运用的背景下,发展出了针对 NAT 环境下 P2P 程序之间建立连接的 NAT 穿透技术。NAT 穿透技术通常是在一台处于外网的第三方服务器的帮助下,巧妙地使一对 NAT 背后的主机穿透 NAT 相互建立连接。有关 NAT 穿透技术的话题将在后续文章中详细讨论。

如需转载本文,请注明出处。

参考文献
[1] F. Audet Ed., Nortel Networks, et al. Network Address Translation (NAT) Behavioral Requirements for Unicast UDP, RFC 4787, Jan 2007; tools.ietf.org/html/rfc4787
[2] P. Srisuresh, Kazeon Systems, et al. NAT Behavioral Requirements for ICMP, RFC 5508, Apr 2009; tools.ietf.org/html/rfc5508
[3] P. Srisuresh, M. Holdrege, et al. IP Network Address Translator (NAT) Terminology and Considerations, RFC 2663, Aug 1999; tools.ietf.org/html/rfc2663
[4] P. Srisuresh, Jasmine Networks, et al. Traditional IP Network Address Translator (Traditional NAT), RFC 3022, Jan 2001; tools.ietf.org/html/rfc3022
[5] Wikipedia. Network address translation. https://en.wikipedia.org/wiki/Network_address_translation

IP 与路由(二)

IP 使用 IP 数据报运送上层协议的交付的数据到目标主机,IP 数据报由 IP 头部和数据载荷两部分组成。

IP 头部

IP 头部位于每个 IP 数据报的起始位置,IP 头部中包含 IP 数据报的版本、源 IP 地址、目的 IP 地址、TTL 等重要信息。IPv4 与 IPv6 的头部结构有所不同,如无特殊说明,本文中所讨论的 IP 头部均指 IPv4 的头部。IP 头部中一共有 14 个字段,其中前 13 个字段是必选的。一个 IP 头部的结构如下所示[1]

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version|  IHL  |    DSCP   |ECN|          Total Length         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Identification        |Flags|      Fragment Offset    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Time to Live |    Protocol   |         Header Checksum       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Destination Address                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Version

IP 头部最起始部分的 4 bits 是 Version 字段,这个字段与 IPv6 头部兼容,用于标识数据报的版本号。对于 IPv4 的数据报来说,这个字段的值为 4。

IHL

IHL 字段表示 IP 数据报头部的长度(Internet Header Length),是一个 4 bits 长的字段。IP 数据报是按 32 bits(4 字节)对齐的,它的位长度一定是 32 的整数倍,IHL 的值表示的是 IP 数据报长度对 32 bits 的倍数。例如,假设一个 IP 数据报的 IHL 字段值是 6,则说明这个数据报 IP 头部的长度是 6 x 32 bits = 192 bits(24 字节)。IP 数据报头部的长度不会短于 160 bits(20 字节),因此一个有效的 IHL 字段值不小于 5。

DSCP

DSCP 字段是一个 6 bits 长的差异化服务代码(Differentiated Services Code Point)。在 Internet 发展的过程中,人们发现互联网中开始同时传送着多种不同类型服务的数据,包括语音、视频、音乐流媒体、网页和电子邮件等[2]。为了保证提供尽量好的服务质量,不同互联网服务本身的一些特性决定了它们的数据报在被转发时应当采取不同的策略——例如网络语音服务对延迟比较敏感,因此它们的数据报需要优先于文件传送服务的数据报被转发。DSCP 字段标志了一个 IP 数据报所属的服务类型,网络设备可以依据这个字段的值提供差异化的转发行为。默认的 DSCP 值是 0。

ECN

ECN 是一个 2 bits 长的字段,用于为 IP 和 TCP 提供显式的拥塞通知,以取代用丢弃数据报的方式控制拥塞。关于 ECN 更详细的信息,请参考 RFC 3168 文档及维基百科 Explicit Congestion Notification 词条。

Total Length

Total Length 是一个 16 bits 长的字段,用于表示整个 IP 数据报的长度,包括头部和数据载荷,单位是字节。由于一个 IP 数据报至少应包含头部,而上文中提到最短的 IP 头部长度是 160 bits,因此 IP 数据报总长度不会小于 160 bits,一个有效的 IP 数据报中 Total Length 字段的值不会小于 20。Total Length 可以表示的最大的数是 216 – 1 = 65,535,因此理论上最大可以有长达 65,535 字节的 IP 数据报。

值得注意的是,尽管 IP 是建立在分组交换链路层上的数据报协议,下层协议也会为 IP 提供明确的边界,但 Total Length 字段的存在是有意义的。这是因为下层协议可能会对每个分组有最短长度的限制或者对对齐方式有特殊的要求,因此在传送 IP 数据报时可能会在原始的数据报末尾增加多余的填充位,此时必须依靠 IP 头部中的 Total Length 来确定数据报的真实长度。例如以太网限制了每一帧的最小长度为 64 字节,除去以太网帧头部的 18 字节之后的数据部分的最小长度是 46 字节。当以太网发送一个总长度小于 46 字节的 IP 数据报时,会在其末尾填充 0 以符合最短 46 字节的要求。

Identification

Identification 字段占 16 bits,是一个唯一标识字段。在一段时间内 IP 每发出的一个数据报都有一个唯一的 Identification,这个字段主要用于标记一组 IP 分片中的每个单独的 IP 数据报以便于接收端重新拼装。

Flags

Flags 字段占 3 bits,是一个标志字段,用于标志 IP 数据报有关分片的信息。这三个标志位的定义分别如下:

  • 第 0 位:保留位,必须置为 0;
  • 第 1 位:DF(Dont’t Fragment),禁止分片位。0 表示允许分片,1 表示不允许分片;
  • 第 2 位:MF(More Fragments),更多分片位。当一个 IP 数据报被分片为多个时,只有最后一个 IP 数据报的 MF 标志为 0,其余的 IP 数据报都将置 MF 标志为 1。

关于 IP 分片的话题,下文中还会有更详细的讨论。

Fragment Offset

Fragment Offset 是一个 13 bits 长的分片偏移字段。该字段用于在分片发生时定位一个分片所携带的数据在原始 IP 数据报的载荷中的偏移,以 8 字节(64 bits)为单位。在未分片的 IP 数据报中,这个字段的值为 0。

Time To Live

Time To Live(TTL)是一个 8 bits 的字段,标识 IP 数据报最多还能在网络中生存多长时间。当一个 IP 数据报在合理的时间内还没有被送到目的主机上时,它可能是经历了错误的路由或进入了环路,此时网络应该丢弃它。在最初的定义(RFC 791)中,TTL 字段的单位是秒,当 IP 数据报被一个模块处理时,应在处理后修改 TTL 字段的值,使其等于原来的值减去这个数据报在处理中消耗的时间(单位秒);RFC 还规定处理时间一律按向上取整计算,例如不足 1 秒的按 1 秒计。

在实践中,由于路由器对 IP 数据报处理的时间通常小于 1 秒,因此实际上路由器对其每个转发的 IP 数据报都是将 TTL 作减 1 处理,最后 TTL 字段变成了按跳数(hops)计的生存时间字段[3]。例如一个 IP 数据报的 TTL 字段值是 128,这往往意味着如果这个数据再经历 128 跳还没到达目的地就将被丢弃。当 IP 协议遇到一个 TTL 为 0 的数据报时,应当马上将其丢弃,并向数据报的源地址发送 TTL 超时的 ICMP 报文以通知发送者。

一个充分利用了 TTL 特性实现的网络工具是 traceroute。traceroute 程序利用逐次构造并发送 TTL 从 1 开始依次递增的 IP 数据报,并接收中途路由器返回的 ICMP TTL Timeout 报文来探测从当前主机到任一目标 IP 地址所经历的所有路由器的地址。

Protocol

Protocol 是一个 8 bits 的字段,它用于标识 IP 数据报所运送的载荷属于何种上层协议,最常见的 Protocol 字段值有 1 – ICMP、6 – TCP、17 – UDP 等。这个列表中列出了所有 Protocol 字段取值对应的上层协议及它们的详细信息。

Header Checksum

Header Checksum 字段长 16 bits,是 IP 头部的校验和字段。该用于对 IP 头部进行校验,以帮助 IP 协议检查出 IP 数据报头部在链路层转发过程中可能出现的误码情况。校验和是一种简单的差错检测方法,校验和值是一个无符号整数。以要求的校验和的字长为单位对原始数据分组,然后分别将它们相加,忽略掉在求和中溢出的部分,将得到的结果按位取反即为这段数据的校验和。如果原始数据的长度不是校验和字长的整数倍,那么对原始数据的末尾进行补 0 处理,直到其长度等于校验和字长的整数倍。下面是一个用 C 语言实现 16 位校验和计算的例子:

IP 头部 Checksum 字段的计算方法是,先将 IP 头部其他字段填充好,再将 Checksum 字段置为 0,对整个头部执行一次校验和计算,最后将计算结果填充到 Checksum 字段中。考察校验和的计算方法后不难发现,如果此时再对整个 IP 头部执行一次校验和计算,计算结果必为 0。当 IP 网络中一个设备收到 IP 数据报时,它首先会检验数据报头部是否在传输过程中发生了差错;检测方法就是利用了上述校验和的性质,只需对 IP 数据报头部部分求一次检验和,判断计算结果是否为 0,如果不为 0,则说明头部中出现了差错,必须丢弃这个数据报。

需要注意的是,IP 只对它的头部进行校验,计算校验和并不包括 IP 携带的载荷数据部分,IP 不保证它携带的载荷在传输过程中不发生差错。也就是说,如果一个 IP 数据报在传输过程中发生了误码,但误码只发生在载荷部分,IP 并不能检测这个差错,这个包含差错的数据报同样将被送到目的主机。对数据载荷的差错检验应该由上层协议来完成。

Source Address

32 bits 长,数据报发送者的 IP 地址。

Destination Address

32 bits 长,数据报目的主机的 IP 地址。

Options

Options 是 IP 头部中最后一个字段,它是不定长字段,也是一个可选的选项,一个 IP 头部中可以包含零个或多个 Options,用于在 IP 头部中添加附加的选项信息。IP 头部中的选项一共有如下两种情形:

  • 仅由一字节的选项类型(option-type)组成,没有附加数据;
  • 由一字节选项类型、一字节选项数据长度和不定长的选项数据组成。

其中选项类型依次由以下三部分构成:

  • 1 bit 拷贝标志(copied flag),这个标志位决定 IP 数据报发生分片时是否在分片的数据报中拷贝本选项,取值 0 表示不拷贝,取值 1 表示拷贝。
  • 2 bits 选项类别(option class)
  • 5 bits 选项号(option number)

RFC 791 中定义了如下几种选项:

类别 选项号 数据长度 描述
0 0 标志选项字段的结束,只占一个字节
0 1 没有选项,在选项与选项之间起填充作用,用于对齐各个选项的起始位置,占一个字节
0 2 11 安全选项。
0 3 变长 松散源路由选项。
0 9 变长 严格源路由选项。
0 7 变长 路由追踪。用于追踪数据报在网络中经历的路由。
0 8 4 Stream ID。用于运载卫星网络(SATNET)数据流的 id。
2 4 变长 Internet 时间戳。

有关 options 字段更详细的信息,请参阅 RFC 791 文档。

IP 数据报分片与重组

上文中提到,理论上最大的 IP 数据报可以长达 65,535 字节。IP 是建立在数据链路层之上的网络层协议,一个 IP 数据报从源主机出发到达目的主机的过程中,往往会经历多个不同物理网络,其链路层实现可能各不相同。在分组交换网链路层中,IP 数据报与链路层的帧往往是一一对应的,一个链路层帧携带一个 IP 数据报。而链路层对一帧的最大长度是有限制的,这个最大长度通常远小于 65,535 字节,例如 IEEE802.3 以太网的最大帧长是 1500 字节。在软件中,会根据链路层最大帧长的实际情况为链路层接口配置一个 MTU(最大传输单元) 值,表示这个接口一次最大能传输的字节数。

不同链路层的最大帧长度可能不尽相同,因此在互联网中很可能会出现下面的情形:某主机从 A 接口收到一个数据报,在查路由表之后决定将其从 B 接口转发给下一站,而 B 接口所在的链路层的 MTU 小于数据报的长度,不足以直接运载这个数据报。当这种情形发生时,IP 将根据该数据报头部中 flag 字段的情况作如下两种处理:

  1. 如果 DF 位是 1,说明该数据报不允许分片,丢弃这个数据报并向它的源地址发送“目标不可达:要求分片”的 ICMP 报文。
  2. 如果 DF 位是 0,则根据具体情况将数据报拆分成两片或多片,依次发往下一站。

当一个数据报需要分片时,首先根据具体的 MTU 值将原始数据报的载荷数据拆分成两份或多份,除最后一份之外,每一份的长度应该是 8 字节的整数倍以保证对齐。然后创建相同数量的新 IP 数据报,将原始数据报的头部复制到这些新数据报的头部中,将拆分后的数据依次填入到新的数据报的载荷中。再依次修改新数据报头部中的 flag、total length、fragment offset 等字段,除最后一个数据报外的所有数据报 flag 字段的 MF 标志都修改为 1,所有数据报的 total length 字段都修改为数据报实际的总长度,所有数据报的 fragment offset 字段修改为分片载荷在原数据报载荷中的偏移。最后一个数据报做必要的补 0 对齐处理。

将分片后的数据报依次发送给下一站。

当 IP 收到分片的 IP 数据报时,在必要时(例如分片已到达目的主机)将它们收集起来并把各自的载荷连接为整体。IP 把头部中 identification、source address、destination address 和 protocol 相同的 IP 数据报归为一组,并根据 fragment offset 字段的值决定各个分片的先后顺序,在所有分片都到达时,原始数据报的载荷就拼装好了。

后续博文还将继续讨论与 IP 路由相关的话题。

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

参考文献
[1] Information Sciences Institute of USC, INTERNET PROTOCOL DARPA INTERNET PROGRAM PROTOCOL SPECIFICATION, RFC 791, Sep 1981; tools.ietf.org/html/rfc791
[2] Wikipedia. Differentiated services[EB/OL]. https://en.wikipedia.org/wiki/Differentiated_services
[3] Wikipedia. IPv4[EB/OL]. https://en.wikipedia.org/wiki/IPv4
[4] W. Richard Stevens, TCP/IP 详解 卷1:协议[M]. 机械工业出版社, 2000.

IP 与路由(一)

IP(Internet Protocol网际协议) 是 TCP/IP 协议族中最核心的部分。IP 是一个网络层协议,为处于不同网络中的主机提供端到端通信的服务。IP 协议采用分组发送的方式工作,只需要下层协议提供分组交换服务,不需要在通信前建立专用的连接。IP 协议只提供“尽力而为”的分组传送服务,不保证传送的分组不丢失、不重复、不出差错,也不保证分组能按顺序到达。因此,IP 的上层协议还应当视情况实现差错控制、重传等功能。

目前 IP 有两个在互联网中应用的版本,分别是 Internet Protocol version 4(IPv4)Internet Protocol version 6(IPv6),本文主要讨论的是 IPv4,如无特殊说明,下文中的“IP”均指 IPv4。

IP 地址

所有使用 IP 协议互联的主机都配置有 IP 地址,IP 地址是 TCP/IP 网中主机的标识,IP 路由的实现依赖于 IP 地址。IP 地址是一个非常重要的概念,也是 TCP/IP 在日常交流中被提及最频繁的概念,以至于在日常语境中“IP”一词往往特指“IP 地址”。

IP 地址是一个 32 位无符号整数,由 4 个字节组成,因此理论上最多可以有 232 约 42.9 亿个 IP 地址。为了便于表达和记忆,IP 地址通常不直接用整数表示,而用点分十进制的形式表示——从高位到低位依次用 4 个范围为 0~255 的十进制数表示 IP 地址的 4 个字节,中间用点号分隔。例如,本站目前所在服务器的 IP 地址是 107.182.176.178,用十进制数表示这个 IP 地址是 1807134898,用十六进制数表示是 0x6bb6b0b2。

IP 地址是 Internet 上的公共资源,目前由互联网名称与数字地址分配机构(Internet Corporation for Assigned Names and Numbers, ICANN)负责管理和分配。IP 地址的分配是根据地理区域分层次进行的,全球共有如下五个分布在不同地区的区域互联网注册管理机构(Regional Internet Registries, RIRs):

AfriNIC 非洲互联网络信息中心
APNIC 亚太互联网络信息中心
ARIN 美国互联网数字地址注册局
LACNIC 拉丁美洲和加勒比地区互联网络信息中心
RIPE NCC 欧洲互联网络协调中心

下面的地图展示了上述几个区域互联网注册管理机构管辖的范围,图片来源于维基百科 Regional Internet registry 词条。

Regional_Internet_Registries_world_map图 1 全球五个区域互联网注册管理机构管辖的范围

ICANN 将整个 IP 地址空间划分为若干块分配给上述几个区域互联网注册管理机构,区域互联网注册管理机构再将他们所有的 IP 块分成更小的块,分配给所管辖的互联网服务提供商或其他网络机构;互联网服务提供商最终将 IP 地址分配给每一台接入互联网的设备[1]

并不是所有的 IP 地址都应用在 Internet 中,IP 地址空间中还有相当可观数量的 IP 地址被保留用于组建私网、广播、多播及其他用途,以这些地址为目的地址的 IP 报文不会被 Internet 中的路由器转发。下面是这些 IP 地址的列表:

起始地址 终止地址 数目 用途
0.0.0.0 0.255.255.255 16,777,216 当前网络
10.0.0.0 10.255.255.255 16,777,216 私有地址
100.64.0.0 100.127.255.255 4,194,304 共享地址空间
127.0.0.0 127.255.255.255 16,777,216 环回
169.254.0.0 169.254.255.255 65,536 本地链路
172.16.0.0 172.31.255.255 1,048,576 私有地址
192.0.0.0 192.0.0.255 256 IETF 协议分配
192.0.2.0 192.0.2.255 256 文档与示例用
192.88.99.0 192.88.99.255 256 IPv6-IPv4 中继
192.168.0.0 192.168.255.255 65,536 私有地址
198.18.0.0 198.19.255.255 131,072 网络基准测试用
198.51.100.0 198.51.100.255 256 文档与示例用
203.0.113.0 203.0.113.255 256 文档与示例用
224.0.0.0 239.255.255.255 268,435,456 IP 多播
240.0.0.0 255.255.255.254 268,435,455 保留地址
255.255.255.255 1 广播地址

除去上述保留的地址之后,真正用于 Internet 的 IP 地址约有 37.0 亿个。Internet 已经以难以置信的高速度发展了二十多年,过去的十年中,互联网用户数每年都有数以亿计的增长,接入 Internet 的设备越来越多,IP 地址资源变得越来越稀缺,Internet 必须面对 IP 地址即将用尽的现实。相比于 IPv4,IPv6 拥有多得多的地址数,可以解决地址不够用的问题,目前 Internet 已经开始向 IPv6 过渡了。此外,NAT(Network address translation) 技术的广泛运用也一定程度上缓解了 IP 地址数紧张的问题,关于 NAT 的话题将在后续博文中详细讨论。

IP 子网

IP 的设计目标是连接多个本地网络,在多个网络之间建立一个互联系统。网络中的每一台主机都至少拥有一个唯一的 IP 地址,一个 IP 地址由网络号和主机号两部分组成。网络号用于区分整个网络中不同的子网,主机号用于区分同一子网下具体的主机。

将一个 IP 地址截成两段,较高位的一段是网络号,较低位的一段是主机号。划分子网时可以根据具体的情况在已分配的所有 IP 地址范围内任意选取网络号和主机号的长度。值得注意的是,IP 网络是具有层次结构的,一台子网内主机所在的整个网络可能只是另一个更大的网络中子网的一部分。网络的层次结构也表现在 IP 地址上,IP 地址中的主机号可以再次划分为一个子网号和一个子网内的主机号。下图是一个例子。

ip-subnet图 2 IP 地址 125.221.228.23 所对应网络号、子网号和主机号的情况

网络中所有的主机都配置有 IP 地址,但因为 IP 地址是由网络号与主机号两个部分组成的,还需要配置子网掩码使程序能区分 IP 地址中的网络号和主机号。子网掩码的形式与 IP 地址一样也是一个 32 位整数,用点分十进制表示;在一个子网掩码中,网络号部分所在的位全为 1,主机号部分所在的位全为 0。例如,上图中的 IP 地址所在子网的子网俺码是 255.255.255.0。IP 地址和它所对应的子网掩码求按位与运算后得到的结果正好是该地址的网络号;IP 地址和它所对应的子网掩码的反码求按位与运算后得到的结果正好是该地址的主机号。

在需要同时表示 IP 地址和它的子网划分时,还有一种更简便的形式,表示方法是在一个 IP 地址之后加上一个斜杠(/)再写上子网掩码中“1”的个数。如上图中的 IP 地址与子网划分可以这样表示:125.221.228.23/24。

子网号为全 0 的 IP 地址被保留用于表示网络号,不能配置给网络中的主机;另外,子网号为全 1 的 IP 地址也是保留的,它用于表示子网的广播地址。例如上图中的 IP 地址所在的网络号是 125.221.228.0,广播地址是 125.221.228.255。

后续博文将继续讨论 IP 报文的细节及 IP 路由的工作方式。

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

参考文献
[1] ICANN. Beginner’s Guide to INTERNET PROTOCOL (IP) ADDRESSES, 2011.
[2] Wikipedia. IPv4[EB/OL]. https://en.wikipedia.org/wiki/IPv4

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

DNS:域名系统(一)

两年前写了《DNS协议及DNS客户端的实现》,最近一段时间计划重新写一个 DNS 客户端库,翻看当初写的文章,觉得太浅、太粗,甚至有错漏。于是决定在写 DNS 库的同时,写一篇博客,算是对以前文章的补充和修正。

将域名系统理解成一个“将 IP 地址和域名相互映射的分布式数据库”是片面的,域名系统面向的是域名和与域名相关联的资源,包括但不仅限于 IP 地址。域名系统的主要设计目标是(在网络中)实现一个统一的名字空间(name space)用于关联名字与其对应的资源(resources)。为了避免尤其是编码方式导致的一些问题,在域名中不应该包含网络 id、地址、路由或类似的其他信息[1]

DNS 是一个分布式的系统,由 Internet 上遍布世界各地的域名服务器组成;分布式域名系统的另一个含义是,在整个系统中,每一个节点的服务器都只存有部分域名的记录。当想要解析一个域名时,可能需要直接或间接地访问多个域名服务器才能得到结果。

DNS 的名字空间是一个树状结构,树中的每一个结点都拥有一个标签(label),规定标签的长度在 0-63 字符之间,其中 0 字符长的标签(NULL)由根结点保留。另外,兄弟结点之间不能有相同的标签,但非兄弟结点(即使是父子结点)之间可以拥有相同的标签。标签由英文字母(a-z, A-Z)、数字(0-9)和连字符(-)组成,不区分大小写,连字符不能连续出现,也不能出现在标签的第一个字符处。例如,label0、label-0 是合法的,而 -label、label–0 是非法的结点标签;label0 与 LABEL0 是等效的标签。每一个结点都对应一个域名,结点的域名由结点与根之间的路径上所有的结点(包括结点和根本身)的标签组成,这些标签从左到右依次排列,用点号(.)连接。下图展示了互联网中真实 DNS 名字空间的一部分,大部分结点还有其他子结点没有画出。

dns图1 互联网中 DNS 域名系统的一部分

根结点的直接子结点所在的域称为顶级域,对应的域名称为顶级域名,顶级域名目前由 ICANN(互联网名称与数字地址分配机构)负责分配和管理,关于域的分配和授权将在下文中讨论。从名称来看,顶级域可以分为以下几种:

  • 普通组织域名,通常为三个字符,是最常见的域名,如 com(商业机构)、net(网络服务)、org(非盈利性组织)等;
  • 国家域名,通常为两个字符,分配给世界各国使用,如 cn(中国)、tw(台湾)、us(美国)、jp(日本)等;
  • 用于地址到名字转换的特殊域 arpa。

最近 ICANN 还分配了一些由汉字组成的顶级域名,如“中国”等。

域名系统的正常运作离不开域名服务器(Server)和域名解析器(Resolver)。域名服务器中存储有它所管辖的域的域名和资源记录数据,并运行着一个域名服务程序,接受网络上的域名查询请求,并查询相应的记录,向查询者返回结果。域名解析器是一个客户端程序,它由应用程序调用,并通过网络接口访问一台或多台域名服务器,最终返回给调用者域名解析结果。

域名系统的中心是根结点,管理根结点所在域的服务器是根域名服务器,是最根本、最重要的域名服务器。全球共有 13 台根域名服务器,它们都有自己的域名,分别是 a.root-servers.net、b.root-servers.net,…,一直到 m.root-servers.net。这里的“一台根域名服务器”指的是逻辑上的数量,并不是指服务器的主机数量,事实上“一台”根域名服务器都由多台主机组成,它们共有同一个域名和 IP 地址。所有的根域名服务器都只存有全部顶级域名的记录,并只能提供对顶级域名的解析服务。

前面讲道,想要解析一个域名可能需要向多个域名服务器发起解析请求,这是由域名系统的层次结构以及授权机制决定的。ICANN 负责管理和分配全部顶级域名,并将顶级域名分别授权给所有这些域名的机构。例如,顶级域名 cn 由 ICANN 授权给 CNNIC(中国互联网络信息中心)管理。这些拥有顶级域名的二级域名机构可以布署域名服务器,并再授权给下一级的其他机构来管理他们的域名或子域名。这样,一个由根结点出发,逐级授权的域名系统就能建立起来了。

所有根域名服务器都是权威的(authoritative)域名服务器,权威域名服务器意味着它的提供的域名记录是“授权的”。同样,由 ICANN 授权的顶级域名管理机构以及它们授权的机构所属的服务器也是权威的域名服务器。权威域名服务器上的授权记录是由管理员通过配置文件设置的,另外,授权域名服务器之间还会定期通过“区域传递”来交换这些记录以保持持续的更新。

一次典型的域名解析是解析器首先向根域名服务器发起请求,请求查询被解析域名所在的顶级域名的信息。根域名服务器通常会返回所有该顶级域名的域名服务器的名称,解析器再向顶级域名所有者服务器发起解析,此时可能收到解析的结果,也可能收到下一个域名服务器的名称。如果是后者,则解析器再次向下一级域名服务器发起请求,直到得到它想要的结果。下面是用 dig 命令跟踪解析域名 work.luodichen.com 的 IP 地址的结果。

MacBook-Air-luodichen:~ luodichen$ dig +trace work.luodichen.com

; <<>> DiG 9.8.3-P1 <<>> +trace work.luodichen.com
;; global options: +cmd
.                       408915  IN      NS      k.root-servers.net.
.                       408915  IN      NS      c.root-servers.net.
.                       408915  IN      NS      l.root-servers.net.
.                       408915  IN      NS      g.root-servers.net.
.                       408915  IN      NS      d.root-servers.net.
.                       408915  IN      NS      f.root-servers.net.
.                       408915  IN      NS      b.root-servers.net.
.                       408915  IN      NS      h.root-servers.net.
.                       408915  IN      NS      i.root-servers.net.
.                       408915  IN      NS      a.root-servers.net.
.                       408915  IN      NS      m.root-servers.net.
.                       408915  IN      NS      e.root-servers.net.
.                       408915  IN      NS      j.root-servers.net.
;; Received 228 bytes from 202.96.128.86#53(202.96.128.86) in 392 ms

com.                    172800  IN      NS      l.gtld-servers.net.
com.                    172800  IN      NS      c.gtld-servers.net.
com.                    172800  IN      NS      e.gtld-servers.net.
com.                    172800  IN      NS      b.gtld-servers.net.
com.                    172800  IN      NS      i.gtld-servers.net.
com.                    172800  IN      NS      j.gtld-servers.net.
com.                    172800  IN      NS      g.gtld-servers.net.
com.                    172800  IN      NS      h.gtld-servers.net.
com.                    172800  IN      NS      k.gtld-servers.net.
com.                    172800  IN      NS      a.gtld-servers.net.
com.                    172800  IN      NS      d.gtld-servers.net.
com.                    172800  IN      NS      m.gtld-servers.net.
com.                    172800  IN      NS      f.gtld-servers.net.
;; Received 508 bytes from 192.36.148.17#53(192.36.148.17) in 413 ms

luodichen.com.          172800  IN      NS      f1g1ns1.dnspod.net.
luodichen.com.          172800  IN      NS      f1g1ns2.dnspod.net.
;; Received 250 bytes from 192.41.162.30#53(192.41.162.30) in 390 ms

work.luodichen.com.     600     IN      NS      ns1.oray.net.
work.luodichen.com.     600     IN      NS      ns2.oray.net.
;; Received 88 bytes from 112.90.82.194#53(112.90.82.194) in 87 ms

work.luodichen.com.     60      IN      A       113.76.146.88
;; Received 52 bytes from 61.174.40.200#53(61.174.40.200) in 81 ms

可以看到在解析 work.luodichen.com 的过程中,经历了如下几个阶段

  1. 向本地配置的 DNS 服务器 202.96.128.86 查询根域名 . 的域名服务器,得到 13 个根域名服务器的域名;
  2. 向其中某个根域名服务器 192.36.148.17 查询顶级域名 com 的域名服务器,得到一些服务器列表;
  3. 向其中某个 com 域名服务器查询 luodichen.com 的域名服务器,得到 f1g1ns1.dnspod.net 和 f1g1ns2.dnspod.net 两个域名服务器的域名;
  4. 向其中某个 luodichen.com 的域名服务器查询 work.luodichen.com 的域名服务器,得到 ns1.oray.net 和 ns2.oray.net 两个域名服务器的域名;
  5. 向其中某个 work.luodichen.com 的域名服务器查询 work.luodichen.com 的 IP 地址,得到最终结果是 113.76.146.88。

在实际的终端应用程序中,可能并不需要像以上的实验那样从根服务器开始一级一级地发起查询请求。如果所有的终端应用程序都这样做,一方面根域名服务器将承担巨大的网络压力;另一方面一次域名解析可能耗费非常长的时间,影响效率。解决这个问题的是缓存(Cache)技术。

除根域名之外的一些域名服务器,它们除了运行着域名服务程序之外,还运行着域名解析器。当这些服务器收到一个不属于它管辖的区域的域名解析请求时,它会尝试以客户端的身份向别的域名服务器发起解析请求,并将最终的解析结果返回给它的客户端,这种服务器称作递归域名服务器,它的客户端发起的解析请求称为递归解析请求。这种服务器同时还可能存在着一个高速缓存(Cache),用来缓存上面提到的这些域名解析的结果,当下次又有相同的域名的请求时,服务器会直接将缓存中的记录返回给客户端,并标记这个记录是“非权威的(non-authoritative)”查询结果。为了确保缓存中的信息能及时更新,每条 DNS 记录都有对应的存活时间(TTL, time to live),当缓存中的信息存在的时间超过它的 TTL 时,这条缓存记录就会过期失效,下次请求这条记录时,服务器又会以递归查询的方式解析这个域名,并再次存入缓存中。

为了解决 DNS 查询效率的问题,通常我们在终端系统中配置的是从 ISP 处获取的 DNS 服务器地址或者一些商业 DNS 服务器地址,如 114.114.114.114、8.8.8.8 等。这些服务器都属于带有缓存的递归服务器,终端解析器只要向这些服务器发送一次递归查询请求就能很快地得到查询结果。

解析器通过 DNS 协议向域名服务器发起查询请求,关于 DNS 协议的细节,以及域名系统多个资源记录作用与格式将在下一篇博文中讨论。

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

参考文献
[1] P. Mockapetris, DOMAIN NAMES – CONCEPTS AND FACILITIES, RFC 1034, November 1987; www.ietf.org/rfc/rfc1034.txt
[2] P. Mockapetris, DOMAIN NAMES – IMPLEMENTATION AND SPECIFICATION, RFC 1035, November 1987; www.ietf.org/rfc/rfc1035.txt
[3] W. Richard Stevens, TCP/IP 详解 卷1:协议[M]. 机械工业出版社, 2000.

DNS协议及DNS客户端的实现

DNS(Domain Name System)是一个提供域名解析服务的分布式系统,主要功能是完成域名与其对应的IP地址之间的相互转换。网络中提供DNS服务的主机叫做DNS服务器,而向DNS服务器发起查询请求的主机叫做DNS客户端,客户端与服务器之间通过DNS协议这一种应用层协议来相互通信并交换数据。

DNS协议建立在UDP或TCP协议之上,DNS服务器开放UDP:53端口和TCP:53端口监听客户端发来的请求。一般情况下客户端通过UDP协议封装请求报文,服务器也用UDP协议封装回应报文;由于广域网中不适合传输过大的UDP数据包,因此规定当封装了DNS回应的UDP数据包长度可能超过512字节时,客户端应该使用TCP协议连接DNS服务器并传输请求和回应,具体包括以下两种情况:(1)客户端认为UDP回应包长度可能超过512字节,主动使用TCP协议;(2)客户端第一次使用UDP协议发送DNS请求,服务端发现UDP回应包会超过512字节,截断UDP包中的回应报文,并在回应报文中为TC字段置1以通知客户端该报文已经被截断,客户端收到之后再发起一次TCP请求。

DNS数据报由头部和记录部分组成,其中请求报文只有问题部分,而回应报文可以有问题部分、回答部分、授权部分和附加部分。
net

DNS头部包含了标识、标志以及各个记录部分的记录个数。net_dns_header

其中标识字段用于确定请求会话,DNS服务保证请求和其对应的回应报文中标识字段的值是相等的。标志字段被分成若干子字段,用于标识数据包的各个属性。DNS头部的C语言结构可以表示如下:

问题部分由问题记录组成,资源部分、授权部分和附加部分由资源记录组成,问题记录和资源记录的结构是不一样的。
问题记录的格式如下图所示
net_a_record
查询名字是将要查询的域名,域名是一个由圆点分为若干节的字符串,这里的名字也由若干节组成,每一节第一个字节是这一个节中包含字符的长度。如下图
net_query_name

查询类型一般是A(地址)记录,表示把域名转换为32位的IPv4地址。全部的查询类型如下所示

查询类别一般都为1(Internet)。

资源记录的格式如下图所示
net_res_record
其中前三个字段与问题记录一致。生存时间表示该DNS回应记录可以在主机缓存中生存的时间,以秒为单位。资源数据长度是紧随其后的资源数据的长度,资源数据的具体内容与域类型字段有关,在只考虑A类型的时候这里的记录是一个32位的IP地址。

为了使DNS报文尽可能短,DNS协议对于可能重复出现的域名节采取压缩策略,因此通常情况下在一个报文中不会存有完全相同的域名节。当出现重复的域名节时,域名节的第一个字节将不是这个节的字符长度,而是以11开头的一个指针,指向该节在报文中第一次出现的位置。
net_pointer

下面是一个用UDP协议实现的DNS客户端类(没有实现TCP方式),测试Demo运行结果如图所示

dns_resolver


最新的相关文章

DNS:域名系统(一)
DNS:域名系统(二)