NTP 原理与报文 — 48 字节头、四时间戳算法与时钟纪律

拆解 NTP 报文字段、offset/delay 推导、stratum 层级与选择-聚类-合并算法 / 应用层 / UDP 123 / RFC 5905

先建立直觉

NTP 干的事,本质上就是"打电话对表":你记一下自己发出请求的时刻,对方回话时也报出他收到和发出的时刻,你再记一下自己收到的时刻。四个时刻一凑,只要默认"请求去程和响应回程在路上花的时间差不多",就能算出你比对方快还是慢了多少、这条路来回要多久——然后不开玩笑地、一点点地把你的表频率拧准,而不是猛地一跳。

它解决什么问题

NTP 的核心任务是回答两个问题:我的时钟和参考时钟差多少(offset)?这条链路的往返延迟是多少(delay)? 这两个数字不解决,日志顺序、认证票据、证书有效期、分布式事务这些全都会乱。下面看它具体怎么算。

工作流程(简化版)

  1. 客户端发请求,记下发送时刻 t1,请求里带上自己的发送时间戳。
  2. 服务端收到(t2)再发出响应(t3),把 t1/t2/t3 都填进响应包。
  3. 客户端收到响应记下 t4,用四时间戳公式算出 offset 与 delay。
  4. 每隔一段时间(poll)重复采样,每个源保留最近 8 次结果,挑 delay 最小的那次。
  5. 多个源的结果先剔除"说谎源",再聚类、加权合并出"系统偏移"。
  6. 用反馈环路(PLL/FLL)调本地时钟频率缓慢逼近正确值;只有偏差极大或刚启动时才会直接跳变。

报文 / 头部长什么样

看这张表前先记住:NTP 报文头固定 48 字节(可选追加认证扩展与 MAC),是一段紧凑的二进制头,没有"一行行可读文本",所有字段都靠位宽精确摆放。

NTP 报文头固定 48 字节(可选追加认证扩展与 MAC)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN |Mode | Stratum | Poll | Precision |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Delay |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Dispersion |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reference ID (refid) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reference Timestamp (64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Origin Timestamp t1 (64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Receive Timestamp t2 (64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Transmit Timestamp t3 (64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (可选)Extension Fields / Key ID / MAC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

2.1 字段详解

字段位宽含义与取值
LI (Leap Indicator)2 bit0=无警告;1=当天最后一分钟有 61 秒(插入闰秒);2=最后一分钟有 59 秒(删除闰秒);3=时钟未同步(告警状态)
VN (Version Number)3 bit协议版本,当前为 4(NTPv4);3 为 NTPv3
Mode3 bit0=保留;1=对称主动;2=对称被动;3=客户端4=服务端5=广播;6=控制(ntpq);7=私有保留(monlist 即在此模式,反射放大源头
Stratum8 bit0=Kiss-o-Death(未指定/无效,refid 载 KoD 码);1=主服务器(直连参考钟);2~15=次级服务器;16=未同步17~255=保留
Poll8 bit有符号,最大轮询间隔的 log₂ 秒6=64 s、10=1024 s;chrony 常见 6~10
Precision8 bit有符号,本地时钟精度的 log₂ 秒。-20 ≈ 1 μs,-24 ≈ 60 ns
Root Delay32 bit到 Stratum 1 参考源的总往返延迟,16.16 定点秒
Root Dispersion32 bit到参考源的最大累积误差,16.16 定点秒。二者共同构成"根离散",用于评估可信度
Reference ID32 bitStratum 1 时为 4 字符 ASCII 源码(GPSPPSDCFATOMGOESPTP);Stratum ≥ 2 时为上游服务器 IPv4 地址(IPv6 为地址哈希前 4 字节);Stratum 0 时为 KoD 码(RATEDENYRSTR
Reference Timestamp64 bit本地时钟最近一次被校准的时刻
Origin Timestamp64 bitt1:服务端原样回填客户端的发送时间戳(用于匹配请求,也是防伪造的弱校验)
Receive Timestamp64 bitt2:服务端收到请求的时刻
Transmit Timestamp64 bitt3:服务端发出响应的时刻(在请求包中则是 t1)
Extension / MAC变长可选:NTS 扩展字段;或 Key ID(4B) + Message Digest(16/20B) 对称密钥认证

2.2 NTP 时间戳格式(64 位)

看这张表前先记住:NTP 的时间戳纪元是 1900 年,不是大家更熟悉的 Unix 1970 年;这一字之差是换算时最容易写错的地方。

部分位宽含义
Seconds32 bit1900-01-01 00:00:00 UTC 起的秒数(注意不是 Unix 纪元 1970
Fraction32 bit秒的小数部分,分辨率 = 2⁻³² s ≈ 233 皮秒

换算关系:

1
Unix 时间戳 = NTP 秒数 - 2208988800 # 1900→1970 相差 70 年 = 2208988800 秒

Era 问题:32 位秒数在 2036-02-07 06:28:16 UTC 溢出(约 136 年一个 Era)。NTPv4 通过 Era Number 概念处理,实现上要求节点初始时间误差在 68 年内即可正确归属 Era。

2.3 Kiss-o-Death(KoD)码

看这张表前先记住:当对端回给你的包 Stratum = 0,它不是"第 0 层",而是在用 refid 字段向你发"别再打了"的拒绝信号(Kiss-o-Death)。

Stratum = 0 时,refid 携带 4 字符 ASCII 告知客户端"别再打了":

KoD 码含义客户端应对
RATE轮询过于频繁,超出限速立即增大 poll 间隔
DENY服务器拒绝服务停止访问该服务器
RSTR访问被策略限制停止访问
ACST任播服务器(信息性)
AUTH认证失败检查密钥配置
INIT / STEP服务端正在初始化 / 步进调整中稍后重试

交互时序

一句话看懂这张图:从 GPS 经 Stratum 1 → 2 逐级下发,客户端发请求、收响应算 offset/delay,再多次采样滤波、多源统计合并,最后用反馈环路调频率(小偏差只 slew、大偏差才 step),被限速时收到 KoD 就调大 poll。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
sequenceDiagram
    autonumber
    participant C as 客户端 (chronyd)
    participant S2 as NTP 服务器 Stratum 2
    participant S1 as 上游 Stratum 1
    participant GPS as Stratum 0 (GPS/原子钟)

    Note over GPS,S1: 上游同步链
    GPS-->>S1: PPS 秒脉冲 + 串口时码
    S1->>S2: NTP 响应 (Stratum=1, refid="GPS")
    Note right of S2: S2 自身变为 Stratum 2

    Note over C,S2: ① 客户端同步(模式 3 → 4)
    Note right of C: 记录 t1(本地发送时刻)
    C->>S2: NTP Request<br/>LI=0 VN=4 Mode=3 Stratum=0<br/>Transmit Timestamp = t1
    Note left of S2: 记录 t2(收到时刻)
    Note left of S2: 记录 t3(发出时刻)
    S2->>C: NTP Response<br/>LI=0 VN=4 Mode=4 Stratum=2<br/>refid=上游IP, RootDelay, RootDispersion<br/>Origin=t1, Receive=t2, Transmit=t3
    Note right of C: 记录 t4(收到时刻)<br/>校验 Origin==t1(防伪)<br/>offset = ((t2-t1)+(t3-t4))/2<br/>delay = (t4-t1)-(t3-t2)

    Note over C,S2: ② 多次采样与滤波
    loop 每 poll 间隔(64~1024 s,动态)
        C->>S2: Request (t1')
        S2->>C: Response (t2', t3')
        Note right of C: clock filter:保留 8 次采样<br/>按 delay 最小优先选出最佳 offset
    end

    Note over C: ③ 多源统计(配置了多个 server 时)
    Note right of C: Selection:用交集算法剔除 falseticker<br/>Clustering:按离散度聚类<br/>Combining:加权合并出系统 offset

    Note over C: ④ 时钟纪律
    alt |offset| < 128 ms
        Note right of C: slew 渐进调整:改变时钟频率<br/>(默认最大 500 ppm,不发生时间跳变)
    else |offset| >= 128 ms 且持续 900 s
        Note right of C: step 步进:直接跳变系统时间<br/>(chrony makestep / ntpd -g 首次允许)
    else |offset| > 1000 s
        Note right of C: panic:ntpd 默认拒绝并退出<br/>(需 -g 或 tinker panic 0)
    end
    Note right of C: 频率误差写入 driftfile,重启后直接补偿

    Note over C,S2: ⑤ 异常:限速
    S2->>C: Stratum=0, refid="RATE" (Kiss-o-Death)
    Note right of C: 立即增大 poll 间隔

关键机制 / 变体

4.1 四时间戳模型与推导

这是干什么的:用一次往返的四个时刻,在"默认来回路径对称"的前提下,把"钟差"和"网络延迟"两个未知量同时解出来。

一次客户端-服务端交换产生 4 个时间戳:

时间戳记录方时机报文字段
t1客户端发出请求的瞬间Transmit Timestamp(请求包)
t2服务端收到请求的瞬间Receive Timestamp(响应包)
t3服务端发出响应的瞬间Transmit Timestamp(响应包)
t4客户端收到响应的瞬间本地记录,不在报文中

推导(关键假设:网络往返路径对称):

1
2
3
4
5
去程耗时 d1 = t2 - t1 - θ (θ 为客户端相对服务端的钟差)
回程耗时 d2 = t4 - t3 + θ

往返时延 delay = d1 + d2 = (t4 - t1) - (t3 - t2)
时钟偏移 offset = θ = ((t2 - t1) + (t3 - t4)) / 2

直觉解释(t2-t1) 是"去程观测差"(含钟差 + 去程延迟),(t3-t4) 是"回程观测差"(含钟差 - 回程延迟),两者取平均恰好抵消掉延迟,剩下钟差。

注意(t3 - t2) 是服务端处理耗时,从往返总时间中扣除它才是真正的网络时延。

4.2 路径不对称是最大误差源

这是干什么的:提醒你 NTP 的精度天花板在哪——它默认来回对称,而现实链路往往不对称。

若去程 10 ms、回程 30 ms,NTP 会误认为各 20 ms,产生 10 ms 的系统性偏差。这就是:

  • 广域网 NTP 精度通常只有几毫秒到几十毫秒;
  • 非对称链路(ADSL 上下行不同、跨运营商绕行)会引入固定偏差;
  • PTP 通过硬件时戳 + 路径测量把这一误差降到亚微秒。

4.3 从"测量"到"调时"

这是干什么的:单次测量含噪声,NTP 不会照单全收,而是"多采样 + 多源统计 + 反馈调频"三步把误差磨平。

单次测量含噪声,NTP 不会立即照搬 offset,而是:

  1. 多次采样(poll 间隔 64~1024 秒,动态调整),维护每个源的 8 次采样滤波器(clock filter,按 delay 最小优先选取);
  2. 多源统计:Selection → Clustering → Combining(见 §4.2);
  3. 时钟纪律(Clock Discipline):用 PLL/FLL 混合环路调整本地时钟频率,使其缓慢逼近正确时间,同时把频率误差写入 drift 文件,下次启动直接补偿。

4.4 Stratum 层级与"根"度量

这是干什么的:解释"Stratum 这个层级数字到底代表什么",以及为什么不能只看它大小判断准不准。

Stratum含义示例
0参考时钟本体(不直接联网)铯原子钟、GPS/北斗接收机、DCF77 长波、PPS 秒脉冲
1直连 Stratum 0 的主服务器time.nist.govntp.aliyun.com、国家授时中心
2同步自 Stratum 1企业内网时间服务器
3~15逐级下降业务服务器、交换机、终端
16未同步(不可用)刚启动或失联的节点

每经过一级,Root DelayRoot Dispersion 累加,可信度下降。Stratum 数字小 ≠ 一定更准——如果 Stratum 1 在地球另一端、RTT 300 ms,还不如同机房的 Stratum 3 靠谱。选源应综合 stratum、delay、jitter、dispersion。

4.5 选择-聚类-合并三步算法(NTP 精髓)

这是干什么的:告诉你 NTP 靠什么识别出"说谎的时间源"并综合出可信时间——也是"为什么至少要配 4 个源"。

步骤目的做法
Selection(选择)剔除falseticker(说谎的时间源)Marzullo/交集算法:每个源给出置信区间 [offset-δ, offset+δ],找被最多区间覆盖的交集;不在交集内的源被判为 falseticker 淘汰
Clustering(聚类)从幸存者中筛出最一致的一组反复剔除离散度(jitter/dispersion)最大的外围源,直到剩余源足够一致
Combining(合并)得出单一系统偏移按各源权重(与 root distance 成反比)加权平均

推论配置的服务器数量很关键

  • 1 个源:无法检测其是否说谎;
  • 2 个源:分歧时无法判断谁对;
  • 3 个源:可识别 1 个 falseticker(但幸存者只剩 2 个,勉强);
  • ≥4 个源:能可靠剔除 1 个错误源,是生产推荐最小配置

4.6 时钟纪律:slew vs step

这是干什么的:解释"调时间"的两种手法,以及为什么默认宁可慢调也不直接跳——因为跳变会把依赖时间的程序搞崩。

| 方式 | 触发条件 | 行为 | 风险 | |——|———|——| | slew(渐进) | 偏差较小(chrony 默认 <1 s 用 slew;ntpd <128 ms) | 通过 adjtimex() 修改时钟频率(最大 500 ppm,即每秒最多追 0.5 ms),时间单调不倒退 | 追大偏差很慢:128 ms 需约 256 秒 | | step(步进) | 偏差大或启动时 | 直接 settimeofday() 跳变 | 时间可能倒退,破坏依赖单调时间的程序(数据库、定时器、日志顺序) | | panic | ntpd 默认偏差 > 1000 s | 拒绝调整并退出,防止误配置造成灾难 | 需 ntpd -gtinker panic 0 放行 |

工程实践:启动时允许一次 step,运行中只 slew。chrony 的 makestep 1.0 3 即"前 3 次更新中若偏差 >1 s 则 step,之后只 slew"。

4.7 频率补偿(drift)

这是干什么的:说明 NTP 为什么断网也不崩——它把"我的表天生走快/走慢多少"记进文件,下次直接补偿。

晶振偏差是系统性的(如恒定快 12 ppm)。NTP 测出该频率误差后写入 drift 文件(/var/lib/ntp/ntp.drift/var/lib/chrony/drift),下次启动直接加载,即使断网也能维持较好精度——这叫自由运行(free-running)保持能力

4.8 工作模式

这是干什么的:列清 NTP 那些 Mode 编号分别干什么,尤其要记住 mode 7 是安全隐患。

Mode名称场景
1 / 2对称主动 / 被动同级服务器互为对等体(peer),互相校验,常用于 Stratum 1 之间
3 / 4客户端 / 服务端最常见的单播模式
5 / 6广播 / 组播服务端周期性广播(224.0.1.1 / ff05::101),客户端被动接收;无法测量延迟,精度差且易被伪造,不推荐
6控制ntpq 查询(NTPv2 控制报文)
7私有ntpdcmonlist 等,必须禁用

4.9 闰秒(Leap Second)

这是干什么的:解释那个会让程序宕机的"23:59:60"是怎么回事,以及现代为什么改用"抹平"。

  • UTC 为跟上地球自转不均匀,会在 6/30 或 12/31 末尾插入一秒(23:59:60)。
  • NTP 通过 LI 字段在闰秒当天提前告知客户端。
  • 历史上闰秒引发过大规模故障(2012 年 Linux 内核 hrtimer 死锁导致 Reddit、LinkedIn 等宕机)。
  • 现代主流方案:闰秒抹平(Leap Smear)——Google/AWS/阿里云的 NTP 服务把这 1 秒分散到前后 12~24 小时内微调(每秒慢约 11.6 μs),客户端全程无感、无 23:59:60。
  • 注意:抹平源与非抹平源不能混用,否则两组源会互判为 falseticker。
  • 国际计量大会(2022 CGPM)已决议最晚 2035 年前取消闰秒

4.10 安全问题与防护

这是干什么的:点明 NTP 历史上的两大安全坑——反射放大 DDoS 和时间欺骗,以及对应的现代防护(NTS、对称密钥)。

威胁说明防护
monlist 反射放大 DDoSmode 7 的 monlist 请求 234 字节可返回最多 600 条记录(约 48 KB),放大约 556 倍(CVE-2013-5211)升级 ntpd ≥ 4.2.7p26;disable monitorrestrict ... noquery;改用 chrony(不实现 mode 7)
时间欺骗(MITM)伪造 NTP 响应把时间调远,可使过期证书重新"有效"、令 TOTP 失效、绕过 Kerberos 票据有效期多源配置、maxdistance 限制、NTS 或对称密钥认证
抢答/伪造攻击者猜测 Origin Timestamp 伪造响应t1 是 64 位高精度随机化时间戳,猜中概率极低;NTS 提供密码学保证
NTS(RFC 8915)基于 TLS 1.3TCP 4460 完成密钥交换(NTS-KE),再用 AEAD 保护 UDP 123 上的 NTP 报文;含 Unique Identifier 与 cookie 机制防重放与追踪推荐:server time.cloudflare.com iburst nts
对称密钥认证共享密钥 + MD5/AES-CMAC,密钥分发困难,仅适合可控内网keys / trustedkey 配置

常见误区

  1. “时区差 8 小时就是时间不对。” 错。时区只是显示口径;NTP 管的是 UTC 本身准不准。显示差 8 小时用 timedatectl set-timezone,与 NTP 无关。
  2. “Stratum 越小一定越准。” 错。一个 RTT 300 ms 的远端 Stratum 1,精度可能远不如同机房 RTT 0.2 ms 的 Stratum 3。选源要综合 stratum、delay、jitter、dispersion。
  3. “NTP 时间戳和 Unix 一样从 1970 年算。” 错。NTP 纪元是 1900-01-01,换算要减 2208988800;这点写错会导致时间整体偏移 70 年。
  4. “NTP 调时间都是一下子跳过去。” 错。默认小偏差只 slew(改频率缓慢逼近),避免时间倒退;只有偏差极大或刚启动才 step。
  5. “配 1~2 个时间源够了。” 错。少于 4 个源无法可靠识别"说谎源"(falseticker),生产建议 ≥4 个不同机构的源。
  6. “容器里跑个 NTP 客户端同步就行。” 错。容器与宿主共享内核时钟,容器内改时间要么无效要么拖累宿主;正确做法是在宿主机同步、容器只挂时区。

速记口诀

  1. 四时刻算两差t1 发、t2 收、t3 发回、t4 收;offset=((t2-t1)+(t3-t4))/2delay=(t4-t1)-(t3-t2)
  2. 对称假设是地基:公式成立前提"去回一样快";链路不对称就引入约一半偏差的误差。
  3. 四源起步防说谎:1 源无校验、2 源难裁决、3 源勉强、≥4 才能稳剔 1 个 falseticker。
  4. 小偏差缓调大偏差跳:默认 slew 改频率(不倒退),step 才跳变;断网靠 driftfile 续命。

知识框架

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
mindmap
  root((NTP 网络时间协议))
    核心算法
      四时间戳 t1 t2 t3 t4
      offset 公式
      delay 公式
      路径对称假设
      clock filter 八采样
    报文格式 48字节
      LI 闰秒指示
      VN 版本4
      Mode 3客户端 4服务端
      Stratum 0-16
      Poll 对数间隔
      Precision
      Root Delay
      Root Dispersion
      Reference ID
      Reference/Origin/Receive/Transmit 时间戳
      可选 MAC 与 NTS 扩展
    时间戳格式
      1900 纪元
      32位秒 + 32位小数
      233皮秒分辨率
      2036 Era 溢出
      Unix换算 -2208988800
    层级体系
      Stratum0 参考钟 GPS 原子钟
      Stratum1 主服务器
      Stratum2-15 次级
      Stratum16 未同步
      Root Delay/Dispersion 累加
    统计算法
      Selection 交集剔除 falseticker
      Clustering 聚类
      Combining 加权合并
      至少4个源
    时钟纪律
      slew 渐进调频
      step 步进跳变
      panic 阈值
      PLL FLL 混合环路
      driftfile 频率补偿
    工作模式
      客户端服务端 3/4
      对称主动被动 1/2
      广播组播 5/6
      控制 6 私有 7
    闰秒
      LI 预告
      23:59:60
      Leap Smear 抹平
      2035前取消
    安全
      monlist 反射放大
      时间欺骗 MITM
      对称密钥认证
      Autokey 已弃用
      NTS RFC8915 TLS1.3