3. Network Time Protocol (网络时间协议)
本节包含 Network Time Protocol (网络时间协议, NTP) 的正式定义, 包括其数据格式, 实体, 状态变量, 事件和事件处理过程. 规范基于图1所示的实现模型, 但并不意味着该模型是规范可以基于的唯一模型. 特别是, 该规范旨在说明和阐明 NTP 的内在操作, 并作为更严格, 全面和可验证规范的基础.
3.1. Data Formats (数据格式)
本文中所有明确或隐含的数学运算均采用二进制补码定点算术. 数据被指定为整数或定点量, 位从零开始以大端方式从左 (高位) 开始编号. 由于各种实现可能会缩放外部派生的量以供内部使用, 因此不指定定点量的精度或小数点位置. 除非另有说明, 所有量均为无符号量, 可以占据完整字段宽度, 位零前面有一个隐含的零. 设计用于处理有符号量的硬件和软件包在最高有效 (符号) 位被设置时会产生令人惊讶的结果. 建议将外部派生的无符号定点量 (如时间戳) 在内部使用时右移一位, 因为完整字段宽度所代表的精度很少是合理的.
由于 NTP 时间戳是珍贵的数据, 实际上代表了协议的主要产品, 因此建立了一种特殊的时间戳格式. NTP 时间戳表示为64位无符号定点数, 以秒为单位, 相对于1900年1月1日0时. 整数部分在前32位, 小数部分在后32位. 这种格式允许方便的多精度算术和转换为 Time Protocol (时间协议) 表示 (秒), 但确实使转换为 ICMP Timestamp message (ICMP 时间戳消息) 表示 (毫秒) 变得复杂. 这种表示的精度约为200皮秒, 对于即使是最特殊的要求也应该足够.
时间戳通过在某些重要事件 (如消息到达) 发生时将本地时钟的当前值复制到时间戳来确定. 为了保持最高精度, 重要的是尽可能靠近与事件相关的硬件或软件驱动程序来完成此操作. 特别是, 应为每次链路级重传重新确定出发时间戳. 在某些情况下, 特定时间戳可能不可用, 例如当主机重新启动或协议首次启动时. 在这些情况下, 64位字段设置为零, 表示该值无效或未定义.
请注意, 自1968年某个时间以来, 最高有效位 (整数部分的第0位) 已被设置, 并且64位字段将在2036年某个时间溢出. 如果 NTP 在2036年仍在使用, 将需要某种外部手段来限定相对于1900年的时间和相对于2036年的时间 (以及136年的其他倍数). 需要此类限定的带时间戳数据将非常珍贵, 以至于适当的手段应该很容易获得. 每136年将存在一个200皮秒的间隔 (此后忽略), 在此期间64位字段将为零, 因此被认为无效.
3.2. State Variables and Parameters (状态变量和参数)
以下是协议使用的各种状态变量和参数的摘要. 它们被分为几类: system variables (系统变量), 与操作系统环境和本地时钟机制相关; peer variables (对等节点变量), 表示特定于每个对等节点的协议机器状态; packet variables (数据包变量), 表示 NTP 消息的内容; 以及 parameters (参数), 表示当前版本所有实现的固定配置常量. 对于每个类别, 变量的描述后跟其名称以及控制它的过程或值. 请注意, 变量使用小写字母, 而参数使用大写字母. 格式和使用的其他详细信息在后面的章节和附录中介绍.
3.2.1. Common Variables (公共变量)
以下变量对系统, 对等节点和数据包类别中的两个或多个是公共的. 其他变量特定于附录 C 中描述的可选认证机制. 当需要区分同名的公共变量时, 将使用变量标识符.
Peer Address (peer.peeraddr, pkt.peeraddr), Peer Port (peer.peerport, pkt.peerport): 这些是对等节点的32位互联网地址和16位端口号.
Host Address (peer.hostaddr, pkt.hostaddr), Host Port (peer.hostport, pkt.hostport): 这些是主机的32位互联网地址和16位端口号. 它们包含在状态变量中以支持多宿主.
Leap Indicator (sys.leap, peer.leap, pkt.leap): 这是一个两位代码, 警告即将在 NTP 时间尺度中插入闰秒. 这些位在插入日的23:59之前设置, 并在次日00:00之后重置. 这导致插入日的秒数 (滚动间隔) 增加或减少一秒. 对于主服务器, 这些位由操作员干预设置, 而对于辅助服务器, 这些位由协议设置. 两个位, 分别为第0位和第1位, 编码如下:
| 值 | 含义 |
|---|---|
| 00 | 无警告 |
| 01 | 最后一分钟有61秒 |
| 10 | 最后一分钟有59秒 |
| 11 | 告警条件 (时钟未同步) |
在除告警条件 (11₂) 之外的所有情况下, NTP 本身对这些位不做任何处理, 只是将它们传递给不属于 NTP 的时间转换例程. 告警条件发生在本地时钟由于任何原因未同步时, 例如首次启动或在没有主要参考源可用的较长时间之后.
Mode (peer.mode, pkt.mode): 这是一个整数, 指示关联模式, 值编码如下:
| 值 | 模式 |
|---|---|
| 0 | 未指定 |
| 1 | 对称主动 |
| 2 | 对称被动 |
| 3 | 客户端 |
| 4 | 服务器 |
| 5 | 广播 |
| 6 | 保留用于 NTP 控制消息 |
| 7 | 保留用于私人使用 |
Stratum (sys.stratum, peer.stratum, pkt.stratum): 这是一个整数, 指示本地时钟的层级, 值定义如下:
| 值 | 含义 |
|---|---|
| 0 | 未指定 |
| 1 | 主要参考 (例如, 校准的原子钟, 无线电时钟) |
| 2-255 | 辅助参考 (通过 NTP) |
出于比较目的, 零值被认为大于任何其他值. 请注意, 编码为数据包变量的整数的最大值受参数 NTP.MAXSTRATUM 的限制.
Poll Interval (sys.poll, peer.hostpoll, peer.peerpoll, pkt.poll): 这是一个有符号整数, 指示传输消息之间的最小间隔, 以2的幂次方表示的秒数. 例如, 值6表示最小间隔为64秒.
Precision (sys.precision, peer.precision, pkt.precision): 这是一个有符号整数, 指示各种时钟的精度, 以最接近2的幂次方表示的秒数. 该值必须四舍五入到下一个更大的2的幂次方; 例如, 50Hz (20ms) 或60Hz (16.67ms) 电源频率时钟将被分配值-5 (31.25ms), 而1000Hz (1ms) 晶体控制时钟将被分配值-9 (1.95ms).
Root Delay (sys.rootdelay, peer.rootdelay, pkt.rootdelay): 这是一个有符号定点数, 指示到同步子网根部主要参考源的总往返延迟, 以秒为单位. 请注意, 此变量可以取正值和负值, 具体取决于时钟精度和偏斜.
Root Dispersion (sys.rootdispersion, peer.rootdispersion, pkt.rootdispersion): 这是一个有符号定点数, 指示相对于同步子网根部主要参考源的最大误差, 以秒为单位. 只有大于零的正值是可能的.
Reference Clock Identifier (sys.refid, peer.refid, pkt.refid): 这是一个32位代码, 标识特定的参考时钟. 对于层级0 (未指定) 或层级1 (主要参考源), 这是一个四字节, 左对齐, 零填充的 ASCII 字符串. 例如:
| 层级 | 代码 | 含义 |
|---|---|---|
| 0 | DCN | DCN 路由协议 |
| 0 | TSP | TSP 时间协议 |
| 1 | ATOM | 原子钟 (校准) |
| 1 | WWVB | WWVB LF (5频段) 无线电 |
| 1 | GOES | GOES UHF (9频段) 卫星 |
| 1 | WWV | WWV HF (7频段) 无线电 |
对于层级2及以上 (辅助参考), 这是选择用于同步的对等节点的四字节互联网地址.
Reference Timestamp (sys.reftime, peer.reftime, pkt.reftime): 这是本地时钟最后更新时的本地时间, 以时间戳格式表示. 如果本地时钟从未同步, 则该值为零.
Originate Timestamp (peer.org, pkt.org): 这是对等节点发送其最新 NTP 消息时的本地时间, 以时间戳格式表示. 如果对等节点变得不可达, 则该值设置为零.
Receive Timestamp (peer.rec, pkt.rec): 这是来自对等节点的最新 NTP 消息到达时的本地时间, 以时间戳格式表示. 如果对等节点变得不可达, 则该值设置为零.
Transmit Timestamp (peer.xmt, pkt.xmt): 这是 NTP 消息离开发送方时的本地时间, 以时间戳格式表示.
3.2.2. System Variables (系统变量)
表1显示了完整的系统变量集. 除了前面描述的公共变量外, 操作系统还使用以下变量来同步本地时钟.
Local Clock (sys.clock): 这是当前本地时间, 以时间戳格式表示. 本地时间从特定机器的硬件时钟派生, 并根据所使用的设计以不同间隔递增. 第5节描述了包括调整和偏斜补偿机制的适当设计.
Clock Source (sys.peer): 这是一个选择器, 标识当前同步源. 通常这将是一个指向包含对等节点变量的结构的指针. 特殊值 NULL 表示当前没有有效的同步源.
3.2.3. Peer Variables (对等节点变量)
表2显示了完整的对等节点变量集. 除了前面描述的公共变量外, 对等节点管理和测量功能还使用以下变量.
Configured Bit (peer.config): 这是一个位, 指示关联是从配置信息创建的, 如果对等节点变得不可达, 不应解除.
Update Timestamp (peer.update): 这是收到最近 NTP 消息时的本地时间, 以时间戳格式表示. 用于计算偏斜离散度.
Reachability Register (peer.reach): 这是一个 NTP.WINDOW 位的移位寄存器, 用于确定对等节点的可达性状态, 位从最低有效 (最右) 端进入. 如果此寄存器中至少有一位设置为1, 则认为对等节点可达.
Peer Timer (peer.timer): 这是一个整数计数器, 用于控制传输 NTP 消息之间的间隔. 一旦设置为非零值, 计数器以一秒间隔递减, 直到达到零, 此时调用传输过程. 请注意, 此计时器的操作独立于本地时钟更新, 这意味着计时系统和间隔计时器系统架构必须相互独立.
3.2.4. Packet Variables (数据包变量)
表3显示了完整的数据包变量集. 除了前面描述的公共变量外, 还定义了以下变量.
Version Number (pkt.version): 这是一个整数, 指示发送方的版本号. NTP 消息将始终以当前版本号 NTP.VERSION 发送, 如果版本号与 NTP.VERSION 匹配, 将始终被接受. 在版本号更改时, 可能会根据具体情况提供例外. 附录 D 中总结了本版本与以前版本 NTP 之间互操作的具体指南.
3.2.5. Clock-Filter Variables (时钟过滤变量)
当使用第4节中建议的过滤和选择算法时, 除了前面描述的变量外, 还定义了以下状态变量.
Filter Register (peer.filter): 这是一个 NTP.SHIFT 级的移位寄存器, 每级存储一个3元组, 由与单次观测相关的测量延迟, 测量偏移量和计算离散度组成. 这些3元组从最高有效 (最左) 端进入, 向最低有效 (最右) 端移动, 并随着新观测的到来最终被丢弃.
Valid Data Counter (peer.valid): 这是一个整数计数器, 指示过滤寄存器中剩余的有效样本. 用于确定可达性状态以及何时应增加或减少轮询间隔.
Offset (peer.offset): 这是一个有符号定点数, 指示对等节点时钟相对于本地时钟的偏移量, 以秒为单位.
Delay (peer.delay): 这是一个有符号定点数, 指示对等节点时钟相对于本地时钟通过它们之间网络路径的往返延迟, 以秒为单位. 请注意, 此变量可以取正值和负值, 具体取决于时钟精度和偏斜误差累积.
Dispersion (peer.dispersion): 这是一个有符号定点数, 指示对等节点时钟相对于本地时钟通过它们之间网络路径的最大误差, 以秒为单位. 只有大于零的正值是可能的.
3.2.6. Authentication Variables (认证变量)
当使用附录 C 中建议的认证机制时, 除了前面描述的变量外, 还定义了以下状态变量. 这些变量仅在实现附录 C 中描述的可选认证机制时使用.
Authentication Enabled Bit (peer.authenable): 这是一个位, 指示关联将在认证模式下运行.
Authenticated Bit (peer.authentic): 这是一个位, 指示从对等节点收到的最后一条消息已被正确认证.
Key Identifier (peer.hostkeyid, peer.peerkeyid, pkt.keyid): 这是一个整数, 标识用于生成消息认证码的加密密钥.
Cryptographic Keys (sys.key): 这是一组64位 DES 密钥. 每个密钥按 Berkeley Unix 发行版中的方式构建, 由八个字节组成, 其中每个字节的七个低位对应于 DES 位1-7, 高位对应于 DES 奇偶校验位8.
Crypto-Checksum (pkt.check): 这是由加密过程计算的加密校验和.
3.2.7. Parameters (参数)
表4显示了在互联网系统中运行的所有实现假定的参数. 为了避免不必要的网络开销和稳定的对等关联, 有必要就这些参数的值达成一致. 以下参数假定为固定的, 适用于所有关联.
Version Number (NTP.VERSION): 这是当前 NTP 版本号 (3).
NTP Port (NTP.PORT): 这是互联网号码分配机构分配给 NTP 的端口号 (123).
Maximum Stratum (NTP.MAXSTRATUM): 这是可以编码为数据包变量的最大层级值, 也被子网路由算法解释为 "无穷大" 或不可达.
Maximum Clock Age (NTP.MAXAGE): 这是参考时钟在最后一次更新后被认为有效的最大间隔, 以秒为单位.
Maximum Skew (NTP.MAXSKEW): 这是在 NTP.MAXAGE 确定的间隔内由于本地时钟偏斜引起的最大偏移误差, 以秒为单位. 比率 φ = NTP.MAXSKEW / NTP.MAXAGE 被解释为由于所有原因引起的最大可能偏斜率.
Maximum Distance (NTP.MAXDISTANCE): 当使用第4节中建议的选择算法时, 这是可接受用于同步的对等节点的最大同步距离.
Minimum Poll Interval (NTP.MINPOLL): 这是互联网系统中任何对等节点允许的最小轮询间隔, 以2的幂次方表示的秒数.
Maximum Poll Interval (NTP.MAXPOLL): 这是互联网系统中任何对等节点允许的最大轮询间隔, 以2的幂次方表示的秒数.
Minimum Select Clocks (NTP.MINCLOCK): 当使用第4节中建议的选择算法时, 这是可接受用于同步的最小对等节点数.
Maximum Select Clocks (NTP.MAXCLOCK): 当使用第4节中建议的选择算法时, 这是考虑选择的最大对等节点数.
Minimum Dispersion (NTP.MINDISPERSE): 当使用第4节中建议的过滤算法时, 这是每个层级的最小离散度增量, 以秒为单位.
Maximum Dispersion (NTP.MAXDISPERSE): 当使用第4节中建议的过滤算法时, 这是最大对等节点离散度和缺失数据假定的离散度, 以秒为单位.
Reachability Register Size (NTP.WINDOW): 这是可达性寄存器 (peer.reach) 的大小, 以位为单位.
Filter Size (NTP.SHIFT): 当使用第4节中建议的过滤算法时, 这是时钟过滤器 (peer.filter) 移位寄存器的大小, 以级为单位.
Filter Weight (NTP.FILTER): 当使用第4节中建议的过滤算法时, 这是用于计算过滤器离散度的权重.
Select Weight (NTP.SELECT): 当使用第4节中建议的选择算法时, 这是用于计算选择离散度的权重.
3.3. Modes of Operation (操作模式)
除广播模式外, 当两个对等节点交换消息, 并且其中一个或两个创建和维护称为 association (关联) 的协议机器实例时, 就形成了 NTP 关联. 关联可以在由主机模式变量 (peer.mode) 指示的五种模式之一下运行: symmetric active (对称主动), symmetric passive (对称被动), client (客户端), server (服务器) 和 broadcast (广播). 它们定义如下.
Symmetric Active (对称主动, 1): 以此模式运行的主机定期发送消息, 无论对等节点的可达性状态或层级如何. 通过以此模式运行, 主机声明它愿意由对等节点同步, 并同步对等节点.
Symmetric Passive (对称被动, 2): 此类型的关联通常在以对称主动模式运行的对等节点的消息到达时创建, 并且只要对等节点可达并在小于或等于主机的层级级别上运行, 关联就会持续存在. 否则, 关联将被解除. 但是, 关联将始终持续到至少发送一条消息作为回复为止. 通过以此模式运行, 主机声明它愿意由对等节点同步, 并同步对等节点.
Client (客户端, 3): 以此模式运行的主机定期发送消息, 无论对等节点的可达性状态或层级如何. 通过以此模式运行, 主机 (通常是 LAN 工作站) 声明它愿意由对等节点同步, 但不同步对等节点.
Server (服务器, 4): 此类型的关联通常在客户端请求消息到达时创建, 并且仅为响应该请求而存在, 之后关联将被解除. 通过以此模式运行, 主机 (通常是 LAN 时间服务器) 声明它愿意同步对等节点, 但不由对等节点同步.
Broadcast (广播, 5): 以此模式运行的主机定期发送消息, 无论对等节点的可达性状态或层级如何. 通过以此模式运行, 主机 (通常是在高速广播介质上运行的 LAN 时间服务器) 声明它愿意同步所有对等节点, 但不由它们中的任何一个同步.
以客户端模式运行的主机偶尔向以服务器模式运行的主机发送 NTP 消息, 可能在重启后立即发送, 此后按周期间隔发送. 服务器只需交换地址和端口, 填写所需信息并将消息返回给客户端即可作出响应. 服务器在客户端请求之间无需保留状态信息, 而客户端可以自由管理发送 NTP 消息的间隔以适应本地条件. 在这些模式下, 本文档中描述的协议机器可以大大简化为简单的远程过程调用机制, 而不会显著损失精度或健壮性, 尤其是在高速 LAN 上运行时.
在对称模式中, 客户端/服务器的区别 (几乎) 消失了. 对称被动模式旨在供在同步子网的根节点附近 (最低层级) 运行并间歇性地拥有相对大量对等节点的时间服务器使用. 在此模式下, 无需事先知道对等节点的身份, 因为带有其状态变量的关联仅在 NTP 消息到达时才创建. 此外, 当对等节点变得不可达或在更高层级级别上运行 (因而不符合作为同步源的条件) 时, 状态存储可以被重用.
对称主动模式旨在供在同步子网的末端节点附近 (最高层级) 运行的时间服务器使用. 通常可以通过在下一个较低层级级别上的两个对等节点和在同一层级级别上的一个对等节点来维持可靠的时间服务, 因此持续轮询的速率通常并不显著, 即使在连接丢失且每次轮询都返回错误消息时也是如此.
通常, 一个对等节点以主动模式 (对称主动, 客户端或广播模式) 运行, 由启动文件配置, 而另一个以被动模式 (对称被动或服务器模式) 运行, 通常无需事先配置. 但是, 两个对等节点都可以配置为以对称主动模式运行. 当两个对等节点以相同模式 (但不是对称主动模式) 运行时, 会产生错误条件. 在这种情况下, 每个对等节点都将忽略来自另一个的消息, 因此先前的关联 (如果有) 将由于可达性失败而被解除.
广播模式旨在用于具有大量工作站且不需要最高精度的高速 LAN. 在典型场景中, LAN 上的一个或多个时间服务器向工作站发送周期性广播, 然后工作站基于几毫秒量级的预配置延迟来确定时间. 与客户端/服务器模式一样, 在此模式下协议机器可以大大简化; 但是, 在使用多个时间服务器以增强可靠性的情况下, 时钟选择算法的修改形式可能会有用.
3.4. Event Processing (事件处理)
NTP 中感兴趣的重要事件发生在专用于每个具有活动关联的对等节点的对等节点计时器 (peer.timer) 到期时, 以及来自各个对等节点的 NTP 消息到达时. 事件也可能因操作员命令或检测到的系统故障 (例如主参考源故障) 而发生. 本节描述在这些事件发生时调用的过程.
3.4.1. Notation Conventions (符号约定)
NTP 过滤和选择算法作用于一组变量: 时钟偏移 (θ, THETA), 往返延迟 (δ, DELTA) 和离散度 (ε, EPSILON). 当需要区分它们时, 小写希腊字母用于相对于对等节点的变量, 而大写希腊字母用于相对于主参考源的变量, 即通过对等节点到达同步子网根节点的变量. 当上下文不清楚时, 将使用下标来标识特定的对等节点. 这些算法基于一个称为同步距离 (λ, LAMBDA) 的量, 它根据下面描述的往返延迟和离散度计算得出.
如附录 H 所述, 对等节点离散度 ε 包括以下贡献: 测量误差 ρ = 1 << sys.precision, 偏斜误差累积 φτ (其中 φ = NTP.MAXSKEW / NTP.MAXAGE 是最大偏斜率, τ = sys.clock - peer.update 是自上次更新以来的间隔), 以及由时钟过滤算法计算的过滤 (采样) 离散度 ε_σ. 根离散度 EPSILON 包括所选对等节点离散度 ε 和偏斜误差累积 φτ 的贡献, 以及对等节点本身的根离散度. 系统离散度包括由时钟选择算法计算的选择 (采样) 离散度 ε_ξ 和提供给本地时钟算法的绝对初始时钟偏移 |THETA|. ε 和 EPSILON 都是动态量, 因为它们取决于自上次更新以来经过的时间 τ, 以及由算法计算的采样离散度.
每次更新相关的对等节点变量时, 与该对等节点关联的所有离散度都会更新以反映偏斜误差累积. 计算可以总结如下:
- θ = peer.offset
- δ = peer.delay
- ε = peer.dispersion = ρ + φτ + ε_σ
- λ = ε + |δ| / 2
其中 τ 是从原始时间戳 (由此确定 θ 和 δ) 被发送到当前时间的间隔, ε_σ 是过滤离散度 (参见下面的时钟过滤过程). 通过对等节点 i 相对于同步子网根节点的变量确定如下:
- THETA_i = θ_i
- DELTA_i = peer.rootdelay + δ_i
- EPSILON_i = peer.rootdispersion + ε_i + φτ_i
- LAMBDA_i = EPSILON_i + |DELTA_i| / 2
其中所有变量都被理解为属于第 i 个对等节点. 最后, 假设选择第 i 个对等节点进行同步, 系统变量确定如下:
- THETA = 组合后的最终偏移
- DELTA = DELTA_i
- EPSILON = EPSILON_i + ε_ξ + |THETA|
- LAMBDA = LAMBDA_i
其中 ε_ξ 是选择离散度 (参见下面的时钟选择过程).
完成这些计算的非正式伪代码如下所示. 请注意, 伪代码并非以任何特定语言表示, 尽管它与 C 语言有许多相似之处. 关于重要算法的具体细节在附录 I 的 C 语言例程中进一步说明.
3.4.2. Transmit Procedure (发送过程)
发送过程在对等节点计时器递减到零时执行, 适用于除以下情况外的所有模式: 与广播服务器一起的客户端模式, 以及所有情况下的服务器模式. 在与广播服务器一起的客户端模式下, 从不发送消息. 在服务器模式下, 仅响应接收到的消息才发送消息. 当到达的 NTP 消息不会产生持久关联时, 接收过程也会调用此过程.
begin transmit procedure
pkt.peeraddr <- peer.hostaddr; /* copy system and peer variables */
pkt.peerport <- peer.hostport;
pkt.hostaddr <- peer.peeraddr;
pkt.hostport <- peer.peerport;
pkt.leap <- sys.leap;
pkt.version <- NTP.VERSION;
pkt.mode <- peer.mode;
pkt.stratum <- sys.stratum;
pkt.poll <- peer.hostpoll;
pkt.precision <- sys.precision;
pkt.rootdelay <- sys.rootdelay;
if (sys.leap = 11₂ or (sys.clock - sys.reftime) > NTP.MAXAGE)
skew <- NTP.MAXSKEW;
else
skew <- φ(sys.clock - sys.reftime);
pkt.rootdispersion <- sys.rootdispersion + (1 << sys.precision) + skew;
pkt.refid <- sys.refid;
pkt.reftime <- sys.reftime;
pkt.org <- peer.org; /* copy timestamps */
pkt.rec <- peer.rec;
pkt.xmt <- sys.clock;
peer.xmt <- pkt.xmt;
#ifdef (authentication implemented) /* see Appendix C */
call encrypt;
#endef
send packet;
peer.reach <- peer.reach << 1; /* update reachability */
if (peer.reach = 0 and peer.config = 0)
begin
demobilize association;
exit;
endif
if (peer.reach & 6 != 0) /* test two low-order bits */
if (peer.valid < NTP.SHIFT) /* valid data received */
peer.valid <- peer.valid + 1;
else peer.hostpoll <- peer.hostpoll + 1;
else begin
peer.valid <- peer.valid - 1; /* nothing heard */
peer.hostpoll <- peer.hostpoll - 1;
call clock-filter(0, 0, NTP.MAXDISPERSE);
call clock-select; /* select clock source */
endif
call poll-update;
end transmit procedure;
3.4.3. Receive Procedure (接收过程)
接收过程在 NTP 消息到达时执行. 它验证消息, 解释各种模式, 并调用其他过程来过滤数据和选择同步源. 如果数据包中的版本号与当前版本不匹配, 则可以丢弃该消息; 但是, 在版本更改时, 可以逐案给出建议的例外. 如果实现了附录 B 中描述的 NTP 控制消息且数据包模式为 6 (控制), 则调用控制消息过程. IP 和 UDP 头中的源和目标互联网地址与端口将与正确的对等节点匹配. 如果没有匹配项, 则创建协议机器的新实例并启用该关联.
begin receive procedure
if (pkt.version != NTP.VERSION) exit;
#ifdef (control messages implemented)
if (pkt.mode = 6) call control-message;
#endef
for (all associations) /* access control goes here */
match addresses and ports to associations;
if (no matching association)
call receive-instantiation procedure; /* create association */
#ifdef (authentication implemented) /* see Appendix C */
call decrypt;
#endef
if (pkt.mode = 0) /* for compatibility */
mode <- (see Section 3.3);
else
mode <- pkt.mode;
case (mode, peer.hostmode) /* see Table 5 */
error: if (peer.config = 0) demobilize association;
break;
recv: call packet; /* process packet */
if (valid header) begin
peer.reach <- peer.reach | 1;
if (valid data) call clock-update;
endif
else
if (peer.config = 0) demobilize association;
break;
xmit: call packet; /* process packet */
peer.hostpoll <- peer.peerpoll;
call poll-update;
call transmit;
if (peer.config = 0) demobilize association;
break;
pkt: call packet; /* process packet */
if (valid header) begin
peer.reach <- peer.reach | 1;
if (valid data) call clock-update;
endif
else if (peer.config = 0) begin
peer.hostpoll <- peer.peerpoll;
call poll-update;
call transmit;
demobilize association;
endif
endcase
end receive procedure;
3.4.4. Packet Procedure (数据包过程)
数据包过程检查消息有效性, 计算延迟/偏移采样, 并调用其他过程来过滤数据和选择同步源. 测试 1 要求发送时间戳与从同一对等节点接收到的最后一个时间戳不匹配; 否则, 该消息可能是旧的副本. 测试 2 要求原始时间戳与发送到同一对等节点的最后一个时间戳匹配; 否则, 该消息可能是乱序的, 伪造的或更糟的. 在广播模式 (5) 的情况下, 表观往返延迟将为零, 并且可能无法实现时间传输操作的完整精度. 但是, 所达到的精度对于大多数用途可能已经足够. 调用轮询更新过程时使用参数 peer.hostpoll (peer.peerpoll 可能已更改).
begin packet procedure
peer.rec <- sys.clock; /* capture receive timestamp */
if (pkt.mode != 5) begin
test1 <- (pkt.xmt != peer.org); /* test 1 */
test2 <- (pkt.org = peer.xmt); /* test 2 */
endif
else begin
pkt.org <- peer.rec; /* fudge missing timestamps */
pkt.rec <- pkt.xmt;
test1 <- true; /* fake tests */
test2 <- true;
endif
peer.org <- pkt.xmt; /* update originate timestamp */
peer.peerpoll <- pkt.poll; /* adjust poll interval */
call poll-update(peer.hostpoll);
test3 <- (pkt.org != 0 and pkt.rec != 0); /* test 3 */
δ <- (T_i - T_(i-3)) - (T_(i-1) - T_(i-2))
θ <- ((T_(i-2) - T_(i-3)) + (T_(i-1) - T_i)) / 2
ε <- (1 << sys.precision) + φ(T_i - T_(i-3))
test4 <- (|δ| < NTP.MAXDISPERSE and ε < NTP.MAXDISPERSE); /* test 4 */
#ifdef (authentication implemented) /* test 5 */
test5 <- ((peer.config = 1 and peer.authenable = 0) or peer.authentic = 1);
#endef
test6 <- (pkt.leap != 11₂ and /* test 6 */
pkt.reftime <= pkt.xmt < pkt.reftime + NTP.MAXAGE)
test7 <- (pkt.stratum <= sys.stratum and /* test 7 */
pkt.stratum < NTP.MAXSTRATUM);
test8 <- (|pkt.rootdelay| < NTP.MAXDISPERSE and /* test 8 */
pkt.rootdispersion < NTP.MAXDISPERSE);
if (not valid header) exit;
peer.leap <- pkt.leap; /* copy packet variables */
peer.stratum <- pkt.stratum;
peer.precision <- pkt.precision;
peer.rootdelay <- pkt.rootdelay;
peer.rootdispersion <- pkt.rootdispersion;
peer.refid <- pkt.refid;
peer.reftime <- pkt.reftime;
if (valid data) call clock-filter(θ, δ, ε); /* process sample */
end packet procedure;
3.4.5. Clock-Update Procedure (时钟更新过程)
当时钟过滤过程为当前对等节点确定了有效的时钟偏移, 延迟和离散度数据时, 接收过程会调用时钟更新过程. 时钟选择和时钟组合过程的结果是最终的时钟校正值 THETA, 本地时钟过程使用它来更新本地时钟. 如果没有候选者在这些过程中存活下来, 时钟更新过程将退出而不做任何进一步的操作.
begin clock-update procedure
call clock-select; /* select clock source */
if (sys.peer != peer) exit;
LAMBDA <- distance(peer); /* update system variables */
if (LAMBDA >= NTP.MAXDISTANCE) exit;
sys.leap <- peer.leap;
sys.stratum <- peer.stratum + 1;
sys.refid <- peer.peeraddr;
call local-clock;
if (local clock reset) begin /* if reset, clear state variables */
sys.leap <- 11₂;
for (all peers) call clear;
endif
else begin
sys.peer <- peer; /* if not, adjust local clock */
sys.rootdelay <- DELTA;
sys.rootdispersion <- EPSILON + max(ε_ξ + |THETA|, NTP.MINDISPERSE);
endif
sys.reftime <- sys.clock;
end clock-update procedure;
在某些系统配置中, 精确的定时信息源以间隔一秒的定时脉冲序列的形式提供. 通常, 这是在时间码信息源 (例如无线电时钟甚至 NTP 本身) 之外提供的, 用于对秒, 分, 时和日进行编号. 在这些配置中, 系统变量被设置为引用脉冲所源自的源. 对于那些支持主参考源 (例如无线电时钟或校准过的原子钟) 的配置, 只要这是实际的同步源, 无论是否使用主时钟过程, 层级都设置为 1.
时钟选择和本地时钟算法的规范不是 NTP 规范的组成部分, 因为可能存在提供等效性能的其他算法. 但是, 第 4 节描述了一种在互联网环境中运行良好的时钟选择算法, 第 5 节描述了一种本地时钟算法, 并推荐使用它们. 第 4 节中描述的时钟选择算法通常在所有可用对等节点中挑选层级最低且同步距离最小的对等节点, 除非该对等节点看起来是错误报时者 (falseticker). 其结果是, 所有算法共同作用以构建相对于主参考时间服务器的最小权重生成树, 从而形成分层主从同步子网.
3.4.6. Primary-Clock Procedure (主时钟过程)
当诸如无线电时钟之类的主参考源连接到主机时, 将其信息合并到数据库中会很方便, 就好像该时钟被表示为普通对等节点一样. 在主时钟过程中, 大约每分钟轮询一次时钟, 并使用返回的时间码为本地时钟生成新的更新. 当主时钟对等节点的 peer.timer 递减到零时, 不调用发送过程; 相反, 会轮询无线电时钟, 通常使用为此目的指定的 ASCII 字符串. 当从无线电时钟接收到有效的时间码时, 它将转换为 NTP 时间戳格式并更新对等节点变量. peer.leap 的值根据时间码中闰秒警告位的状态设置 (如果可用), 或由操作员手动设置. peer.peeraddr 的值 (在调用时钟更新过程时将成为 sys.refid 的值) 被设置为描述时钟类型的 ASCII 字符串 (参见附录 A).
begin primary-clock-update procedure
peer.leap <- "from" radio or operator; /* copy variables */
peer.peeraddr <- ASCII identifier;
peer.rec <- radio timestamp;
peer.reach <- 1;
call clock-filter(sys.clock - peer.rec, 0, 1 << peer.precision);
call clock-update; /* update local clock */
end primary-clock-update procedure;
3.4.7. Initialization Procedures (初始化过程)
初始化过程用于设置和初始化系统, 其对等节点和关联.
3.4.7.1. Initialization Procedure (初始化过程)
初始化过程在 NTP 守护进程重启或重新启动时调用. 本地时钟在重启时大概是未定义的; 但是, 在某些设备中, 可以从重启环境 (例如电池备份的时钟/日历) 获得估计值. 精度变量由本地硬件时钟的固有架构决定. 仅当实现了附录 C 中描述的认证机制时才使用认证变量. 这些变量的值使用超出 NTP 本身范围的过程确定.
begin initialization procedure
#ifdef (authentication implemented) /* see Appendix C */
sys.keys <- as required;
#endef;
sys.leap <- 11₂; /* copy variables */
sys.stratum <- 0 (undefined);
sys.precision <- host precision;
sys.rootdelay <- 0 (undefined);
sys.rootdispersion <- 0 (undefined);
sys.refid <- 0 (undefined);
sys.reftime <- 0 (undefined);
sys.clock <- external reference;
sys.peer <- NULL;
sys.poll <- NTP.MINPOLL;
for (all configured peers) /* create configured associations */
call initialization-instantiation procedure;
end initialization procedure;
3.4.7.2. Initialization-Instantiation Procedure (初始化实例化过程)
这个特定于实现的过程由初始化过程调用以定义关联. 对等节点的地址和模式使用在重启过程中读取的信息或作为操作员命令的结果来确定. 仅当实现了附录 C 中描述的认证机制时才使用认证变量. 这些变量的值使用超出 NTP 本身范围的过程确定. 按照建议设置认证位后, 只有经过正确认证的对等节点才能成为同步源.
begin initialization-instantiation procedure
peer.config <- 1;
#ifdef (authentication implemented) /* see Appendix C */
peer.authenable <- 1 (suggested);
peer.authentic <- 0;
peer.hostkeyid <- as required;
peer.peerkeyid <- 0;
#endef;
peer.peeraddr <- peer IP address; /* copy variables */
peer.peerport <- NTP.PORT;
peer.hostaddr <- host IP address;
peer.hostport <- NTP.PORT;
peer.mode <- host mode;
peer.peerpoll <- 0 (undefined);
peer.timer <- 0;
peer.delay <- 0 (undefined);
peer.offset <- 0 (undefined);
call clear; /* initialize association */
end initialization-instantiation procedure;
3.4.7.3. Receive-Instantiation Procedure (接收实例化过程)
当发现新的对等节点时, 接收过程会调用接收实例化过程. 它初始化对等节点变量并启用该关联. 如果消息来自以客户端模式 (3) 运行的对等节点, 则主机模式设置为服务器模式 (4); 否则, 设置为对称被动模式 (2). 仅当实现了附录 C 中描述的认证机制时才使用认证变量. 如果实现, 则只有经过正确认证的非配置对等节点才能成为同步源.
begin receive-instantiation procedure
#ifdef (authentication implemented) /* see Appendix C */
peer.authenable <- 0;
peer.authentic <- 0;
peer.hostkeyid <- as required;
peer.peerkeyid <- 0;
#endef
peer.config <- 0; /* copy variables */
peer.peeraddr <- pkt.peeraddr;
peer.peerport <- pkt.peerport;
peer.hostaddr <- pkt.hostaddr;
peer.hostport <- pkt.hostport;
if (pkt.mode = 3) /* determine mode */
peer.mode <- 4;
else
peer.mode <- 2;
peer.peerpoll <- 0 (undefined);
peer.timer <- 0;
peer.delay <- 0 (undefined);
peer.offset <- 0 (undefined);
call clear; /* initialize association */
end receive-instantiation procedure;
3.4.7.4. Primary Clock-Instantiation Procedure (主时钟实例化过程)
此过程由初始化过程调用, 以便设置主时钟的状态变量. peer.precision 的值根据无线电时钟规格和硬件接口确定. peer.rootdispersion 的值名义上是无线电时钟固有最大误差的十倍; 例如, 校准过的原子钟为 10 μs, WWVB 或 GOES 无线电时钟为 10 ms, 精度较低的 WWV 无线电时钟为 100 ms.
begin clock-instantiation procedure
peer.config <- 1; /* copy variables */
peer.peeraddr <- 0 undefined;
peer.peerport <- 0 (not used);
peer.hostaddr <- 0 (not used);
peer.hostport <- 0 (not used);
peer.leap <- 11₂;
peer.mode <- 0 (not used);
peer.stratum <- 0;
peer.peerpoll <- 0 (undefined);
peer.precision <- clock precision;
peer.rootdelay <- 0;
peer.rootdispersion <- clock dispersion;
peer.refid <- 0 (not used);
peer.reftime <- 0 (undefined);
peer.timer <- 0;
peer.delay <- 0 (undefined);
peer.offset <- 0 (undefined);
call clear; /* initialize association */
end clock-instantiation procedure;
在某些涉及校准过的原子钟或 LORAN-C 接收机的配置中, 主参考源可能仅提供秒脉冲, 而缺少可从中导出秒, 分等编号的完整时间码. 在这些配置中, 秒编号可以从其他源导出, 例如无线电时钟甚至其他 NTP 对等节点. 在这些配置中, 主时钟变量应反映主参考源, 而不是秒编号源; 但是, 如果秒编号源失效或已知运行不正确, 则应抑制来自主参考源的更新, 就像它已失效一样.
3.4.8. Clear Procedure (清除过程)
当发生导致可达性状态发生重大变化或本地时钟可能中断的事件时, 会调用清除过程.
begin clear procedure
peer.org <- 0 (undefined); /* mark timestamps undefined */
peer.rec <- 0 (undefined);
peer.xmt <- 0 (undefined);
peer.reach <- 0; /* reset state variables */
peer.filter <- [0, 0, NTP.MAXDISPERSE]; /* all stages */
peer.valid <- 0;
peer.dispersion <- NTP.MAXDISPERSE;
peer.hostpoll <- NTP.MINPOLL; /* reset poll interval */
call poll-update;
call clock-select; /* select clock source */
end clear procedure;
3.4.9. Poll-Update Procedure (轮询更新过程)
当发生可能导致轮询间隔或对等节点计时器发生变化的重大事件时, 会调用轮询更新过程. 它检查主机轮询间隔 (peer.hostpoll) 和对等节点轮询间隔 (peer.peerpoll) 的值, 并将每个值钳制在有效范围内. 如果选择该对等节点进行同步, 则该值将根据计算出的符合度进一步钳制 (参见第 5 节).
begin poll-update procedure
temp <- peer.hostpoll; /* determine host poll interval */
if (peer = sys.peer)
temp <- min(temp, sys.poll, NTP.MAXPOLL);
else
temp <- min(temp, NTP.MAXPOLL);
peer.hostpoll <- max(temp, NTP.MINPOLL);
temp <- 1 << min(peer.hostpoll, max(peer.peerpoll, NTP.MINPOLL));
if (peer.timer = 0) /* reset peer timer */
peer.timer <- temp;
else if (peer.timer > temp)
peer.timer <- (sys.clock & (temp - 1)) + 1;
end poll-update procedure;
3.5. Synchronization Distance Procedure (同步距离过程)
距离过程根据对等节点 peer 的对等节点变量计算同步距离.
begin distance(peer) procedure;
DELTA <- peer.rootdelay + |peer.delay|;
EPSILON <- peer.rootdispersion + peer.dispersion + φ(sys.clock - peer.update);
LAMBDA <- EPSILON + |DELTA| / 2;
end distance procedure;
请注意, 在某些情况下 DELTA 可能为负, 但 EPSILON 和 LAMBDA 始终为正.
3.6. Access Control Issues (访问控制问题)
NTP 的设计使得时间服务器上的意外或恶意数据修改 (tampering, 篡改) 或破坏 (jamming, 干扰) 通常不应导致同步子网中其他位置的计时错误. 但是, 这种方法的成功依赖于冗余时间服务器和多样化网络路径, 以及篡改或干扰不会同时发生在同步子网的许多时间服务器上的假设. 原则上, 子网的脆弱性可以通过选择已知可信的时间服务器, 并仅允许这些时间服务器成为同步源来设计. 附录 C 中描述的认证过程代表了实施这一点的一种机制. 但是, 加密算法非常消耗 CPU 资源, 除非采取发送过程描述中提到的预防措施, 否则可能严重降低精度.
虽然不是 NTP 本身的必需功能, 但某些实现可能包含访问控制功能, 以防止未经授权的访问并控制哪些对等节点可以更新本地时钟. 为此, 区分三种访问类别是有用的: 预先授权为可信的, 预先授权为友好的, 以及所有其他 (未预先授权的) 访问. 可以想象, 预先授权是通过配置文件中的条目或某种票据管理系统 (如 Kerberos [STE88]) 来实现的. 在此模型中, 只有可信访问才能导致对等节点成为同步源. 友好访问不能导致对等节点成为同步源, 但 NTP 消息和时间戳将按指定返回.
除非意图隐藏时间服务器本身的存在, 否则通过限制未预先授权的访问来维护秘密时钟似乎没有用处. 行为良好的互联网主机应在服务未实现或资源不可用时返回 ICMP 服务不可用错误消息. 但是, 对于 NTP, 所需资源极少, 因此几乎没有必要限制仅用于读取时钟的请求. 因此, 一种简单而有效的访问控制机制是将以对称模式或客户端模式 (模式 1, 2 和 3) 预先配置的所有关联视为可信的, 并将所有其他关联 (无论是否预先配置) 视为友好的.