正确理解NTP报文中的四个时间
NTP对时机制与NTP报文详解
1.概述
实现NTP对时服务时,需要解析NTP报文,前面的一些标志可以理解,但是报文中的四个时间网上说法不一,根据自己的理解整理下。
2.NTP报文格式
NTP报文共48个字节,详细释义如下图所示

3.对时机制

- 客户端发送NTP请求报文:客户端首先向NTP服务器发送一个NTP请求报文,其中包含了该报文离开客户端的时间戳T1。
- NTP请求报文到达NTP服务器:NTP请求报文在经过网络传输后,到达NTP服务器。在这个时刻,服务器的本地时钟时间为T2。当服务器接收到请求报文时,它将记录T2作为报文到达NTP服务器的时间戳。
- NTP应答报文发送:服务端处理接收到的请求报文,并在T3时刻发出NTP应答报文。这个应答报文包括了以下时间戳信息:
T1:请求报文离开客户端时的时间戳。
T2:请求报文到达NTP服务器时的时间戳。
T3:应答报文离开NTP服务器时的时间戳。 - NTP应答报文到达客户端:客户端在接收到NTP应答报文后,记录报文返回的时间戳T4。这个时间戳表示应答报文到达客户端的时间。
- 计算时间差和延迟:客户端使用上述四个时间戳参数(T1、T2、T3、T4)来计算两个关键参数:
NTP报文从客户端到服务器的往返延迟(Delay):Delay = (T4 - T1) - (T3 - T2)
客户端与服务端之间的时间差(Offset):Offset = ((T2 - T1) + (T3 - T4)) / 2 - 客户端调整时钟:NTP客户端根据计算得到的Offset来调整自己的时钟,实现与NTP服务器的时钟同步。
注意这里的T1,T2,T3,T4跟报文中的四个时间并不是完全对应!!!
3 抓包分析
3.1 第一次交互过程
客户端发送初始报文,将报文离开客户端的时间填入Transmit Timestamp(即上文中的T1)。四个时间字段中只有该字段有值。
服务端对第一次请求的回包,含有四个时间。
Reference Timestamp:服务器最近一次接收上层设备授时后的时间
Origin Timestamp:源端时间。取的是客户端请求报文中的Transmit Timestamp
Receive Timestamp:接收时间。收到请求报文后的服务器本地时间(即上文中T2)
Transmit Timestamp:发送时间。服务端发送回复报文时的服务器本地时间。(即上文中T3)
3.2 后续交互过程
客户端第二次发送请求报文时:
- 记录接收服务端第一次回复报文的时间,写入Receive Timestamp(即上文中T4)
- 将服务端回复报文中的Transmit Time填入Origin Time(T3)
- 记录发送第二次请求报文时的时间,填入Transmit(新的T1)

4.结论
源端报文的Transmit Timestamp始终作为目的端报文中的Origin Timestamp。
将对时机制中的T1,T2,T3,T4与报文中的四个时间分开,不要去想报文中四个时间对应的是T几,就很好理解。
5.NTP时间戳与UTC时间的转换
NTP报文中的四个时间,每个时间戳都用8个字节来表示。前四个字节表示自1900-1-1 00:00:00 以来的秒的个数,后四个字节表示秒的小数。这就是涉及到一个常用时间格式如2024-11-29 16:16:53.322到NTP报文中8字节时间戳的转换。
以上图中服务器回复包中的Transmit Time EA F3 9A 97 F4 EE D5 F4为例,先计算秒的个数,直接计算16进制数0xEAF39A97,结果为3941833367,根据该数,就可以精确到具体某一天的某时某分某秒。首先减去1900-1970的秒数:
3941833367-2208988800=1732844567,因为一般的时间戳转换工具都是计算的从1970年开始的秒数。
使用时间戳在线转换工具得到:
与上图中wireshark解析出来的一致。
再看小数部分,计算0xF4EED5F4,注意该二进制实际上表示的是一个十进制0.xxx的小数。所以转换成十进制整数没用。需要根据十进制小数的二进制表示公式进行换算。
6 十进制小数与二进制小数转换
一个十进制小数比如10.625,转换为二进制小数,需要对整数和小数部分分开处理。
整数部分10,转换为二进制为1010不必多说。
小数部分0.625,根据 乘二取整,顺序排列 方法,乘三次二后,可以得到二进制下的0.101.如果只看二进制小数部分的101,会发现是0.625*2的3次方的结果5的二进制表示。由此可以得到一个快速转换的方法:
十进制小数部分0.xxxxxx * 2的n次方 得到的整数,转换为n位二进制,加上小数点,就是十进制小数的二进制表示方式,这里n为精度,因为有些小数是无法用有限个二进制位数完全表示的。
对应上文中的时间戳的小数部分,其实表示的就是没加小数点之前的n位二进制数,或者说0xF4EED5F4,其实代表的是0.F4EED5F4(这里用十六进制暂时表示二进制,实际应该写为32位的二进制,理解即可)。
根据上述反推,可以得到该十进制小数 = 该n位二进制数 / 2的n次方。
由于windows计算器无法直接计算二进制,先转换为十进制进行计算。这里n为32,2的32次方=4,294,967,296,0xF4EED5F4=4109293044,得到0.956769344…,与wireshark解析出来的时间一致。
总结:
有些协议报文中规定时间分别用秒的整数为和秒的小数为来表示,在程序中实现时,整数部分直接赋值,小数部分先转换为0.xx秒后,在*2的n次方即可。
struct timeval trs_t = {0};
gettimeofday(&trs_t, NULL);
trstime_h = htonl(trs_t.tv_sec + sec_1900to1970);
trstime_l = htonl(trs_t.tv_usec / 1000000 * 0x100000000);
更多推荐


所有评论(0)