跳到主要内容

8. 线上协议

NTP 线上协议 (on-wire protocol) 的核心, 是在服务器 (server), 对等方 (peer), 以及客户端 (client) 之间交换时间值的机制. 它天然能够抵抗数据包丢失或重复. 数据完整性由 IP 和 UDP 校验和 (checksum) 提供. 该协议不提供流量控制 (flow control) 或重传 (retransmission) 机制, 也不需要这些机制. 协议使用时间戳, 这些时间戳要么从数据包头部中提取, 要么在数据包到达或离开时从系统时钟采样得到. 时间戳是精密数据, 在发生链路层重传 (link-level retransmission) 时应重新采样, 并且在发送时应针对计算 MAC 所需的时间进行校正.

NTP 消息使用两种不同的通信模式: one-to-one 和 one-to-many, 通常称为单播 (unicast) 和广播 (broadcast). 就本文而言, broadcast 一词解释为任何可用的 one-to-many 机制. 对于 IPv4, 这等同于 IPv4 broadcast 或 IPv4 multicast. 对于 IPv6, 这等同于 IPv6 multicast. 为此, IANA 已分配 IPv4 multicast address 224.0.1.1, 以及以 :101 结尾的 IPv6 multicast address, 其 prefix 由 scoping rule 决定. 除这些已分配的 multicast address 外, 也可以使用其他未分配的 multicast address.

如图 15 所示, 线上协议使用编号为 t1 到 t4 的四个时间戳, 以及 org, rec, xmt 三个状态变量. 该图展示了最一般的情况, 即两个对等方 A 和 B 各自独立测量相对于对方的偏移量 (offset) 和延迟 (delay). 为便于说明, 数据包时间戳用小写表示, 状态变量用大写表示. 数据包到达或离开时, 状态变量从数据包时间戳复制而来.

          t2            t3           t6            t7
+---------+ +---------+ +---------+ +---------+
| 0 | | t1 | | t3 | | t5 |
+---------+ +---------+ +---------+ +---------+
| 0 | | t2 | | t4 | | t6 | Packet
+---------+ +---------+ +---------+ +---------+ Timestamps
| t1 | |t3=clock | | t5 | |t7=clock |
+---------+ +---------+ +---------+ +---------+
|t2=clock | |t6=clock |
+---------+ +---------+
Peer B
+---------+ +---------+ +---------+ +---------+
org | T1 | | T1 | | t5<>T1? | | T5 |
+---------+ +---------+ +---------+ +---------+ State
rec | T2 | | T2 | | T6 | | T6 | Variables
+---------+ +---------+ +---------+ +---------+
xmt | 0 | | T3 | | t3=T3? | | T7 |
+---------+ +---------+ +---------+ +---------+

t2 t3 t6 t7
---------------------------------------------------------
/\ \ /\ \
/ \ / \
/ \ / \
/ \/ / \/
---------------------------------------------------------
t1 t4 t5 t8

t1 t4 t5 t8
+---------+ +---------+ +---------+ +---------+
| 0 | | t1 | | t3 | | t5 |
+---------+ +---------+ +---------+ +---------+
| 0 | | t2 | | t4 | | t6 | Packet
+---------+ +---------+ +---------+ +---------+ Timestamps
|t1=clock | | t3 | |t5=clock | | t7 |
+---------+ +---------+ +---------+ +---------+
|t4=clock | |t8=clock |
+---------+ +---------+
Peer A
+---------+ +---------+ +---------+ +---------+
org | 0 | | t3<>0? | | T3 | | t7<>T3? |
+---------+ +---------+ +---------+ +---------+ State
rec | 0 | | T4 | | T4 | | T8 | Variables
+---------+ +---------+ +---------+ +---------+
xmt | T1 | | t1=T1? | | T5 | | t5=T5? |
+---------+ +---------+ +---------+ +---------+

Figure 15: On-Wire Protocol

在图中, A 发送的第一个数据包只包含起源时间戳 (origin timestamp) t1, 随后该值被复制到 T1. B 在 t2 收到该数据包, 并将 t1 复制到 T1, 将接收时间戳 (receive timestamp) t2 复制到 T2. 此时或稍后的 t3, B 向 A 发送一个数据包, 其中包含 t1, t2, 以及发送时间戳 (transmit timestamp) t3. 这三个时间戳都被复制到相应的状态变量. A 在 t4 收到该数据包, 数据包中包含三个时间戳 t1, t2, t3, 以及目的时间戳 (destination timestamp) t4. 如下文所述, 这四个时间戳用于计算 B 相对于 A 的偏移量和延迟.

在更新 xmt 和 org 状态变量之前, 会执行两项健全性检查 (sanity check), 以防护重复 (duplicate), 伪造 (bogus), 或重放 (replayed) 的数据包. 在上述交换中, 如果数据包中的发送时间戳 t3 与 org 状态变量 T3 匹配, 则该数据包是重复或重放数据包. 如果数据包中的起源时间戳 t1 与 xmt 状态变量 T1 不匹配, 则该数据包是伪造数据包. 在任一情况下, 都先更新状态变量, 然后丢弃该数据包. 为了防止最后一次发送的数据包被重放, 在伪造检查成功后立即将 xmt 状态变量设置为零.

最近的四个时间戳, 即 T1 到 T4, 用于计算 B 相对于 A 的偏移量:

theta = T(B) - T(A) = 1/2 * [(T2-T1) + (T3-T4)]

以及往返延迟:

delta = T(ABA) = (T4-T1) - (T3-T2).

注意, 括号内的量由 64-bit 无符号时间戳计算得到, 结果是带有 63 个有效位加符号位的有符号值. 这些值可以表示从过去 68 年到未来 68 年的日期. 然而, 偏移量和延迟是这些值的和与差, 其中包含 62 个有效位和两个符号位, 因此它们能够无歧义表示的值范围是从过去 34 年到未来 34 年. 换言之, 在服务启动前, 客户端的时间必须被设置在服务器时间的 34 年以内. 这是 64-bit integer arithmetic 的基本限制.

在可用 floating double arithmetic 的实现中, 可以将一阶差值转换为 floating double, 并用该算术计算二阶和与差. 由于相对于时间戳的量级, 二阶项通常非常小, 因此不会损失有效位, 同时无歧义范围可从 34 年恢复到 68 年.

在某些场景中, 如果客户端的初始频率偏移量 (frequency offset) 较大, 而实际传播时间 (propagation time) 较小, 延迟计算可能变为负值. 例如, 如果频率差 (frequency difference) 为 100 ppm, 且间隔 T4-T1 为 64 s, 则表观延迟 (apparent delay) 为 -6.4 ms. 由于负值会在后续计算中产生误导, delta 的值应被钳制为不小于 s.rho, 其中 s.rho 是第 11.1 节描述的系统精度 (system precision), 以秒表示.

以上讨论假定的是最一般的情况, 即两个对称对等方 (symmetric peer) 独立测量彼此之间的偏移量和延迟. 对于无状态服务器 (stateless server), 协议可以简化. 无状态服务器将客户端数据包中的 T3 和 T4 复制到服务器数据包的 T1 和 T2, 并在发送给客户端之前附加发送时间戳 T3. 第 9 节及后续章节和附录给出了填充其余协议字段的更多细节.

注意, 如上所述的线上协议能够抵抗服务器响应数据包的重放. 但是, 它不能抵抗客户端请求数据包的重放, 后者会导致服务器应答数据包带有新的 T2 和 T3 值, 从而产生不正确的偏移量和延迟. 该漏洞可以通过在计算偏移量和延迟后将 xmt 状态变量设置为零来避免.