NTP(Network Time Protocol,网络时间协议)是由RFC 1305定义的时间同步协议。它是通过网络在计算机系统之间进行时钟同步的网络协议。NTP 在公共互联网上通常能够保持时间延迟在几十毫秒以内的精度,并在理想条件下,它能在局域网下达到低于一毫秒的延迟精度。它使用用户数据报协议(UDP)在端口 123 上发送和接受时间戳。它是个 C/S 架构的应用程序。
NTP工作原理
NTP的基本工作原理如图所示。Device A和Device B通过网络相连,它们都有自己独立的系统时钟,需要通过NTP实现各自系统时钟的自动同步。为便于理解,作如下假设:
·在Device A和Device B的系统时钟同步之前,Device A的时钟设定为10:00:00am,Device B的时钟设定为11:00:00am。
·Device B作为NTP时间服务器,即Device A将使自己的时钟与Device B的时钟同步。
·NTP报文在Device A和Device B之间单向传输所需要的时间为1秒。
系统时钟同步的工作过程如下:
·Device A发送一个NTP报文给Device B,该报文带有它离开Device A时的时间戳,该时间戳为10:00:00am(T1)。
·当此NTP报文到达Device B时,Device B加上自己的时间戳,该时间戳为11:00:01am(T2)。
·当此NTP报文离开Device B时,Device B再加上自己的时间戳,该时间戳为11:00:02am(T3)。
·当Device A接收到该响应报文时,Device A的本地时间为10:00:03am(T4)。
至此,Device A已经拥有足够的信息来计算两个重要的参数:
·NTP报文的往返时延Delay=(T4-T1)-(T3-T2)=2秒。
·Device A相对Device B的时间差offset=((T2-T1)+(T3-T4))/2=1小时。
这样,Device A就能够根据这些信息来设定自己的时钟,使之与Device B的时钟同步。
以上内容只是对NTP工作原理的一个粗略描述.
/**
* T1,客户端发送请求时的 本地系统时间戳;
* T2,服务端接收到客户端请求时的 本地系统时间戳;
* T3,服务端发送应答数据包时的 本地系统时间戳;
* T4,客户端接收到服务端应答数据包时的 本地系统时间戳。
*/
NTP 报文格式
/*
* Dissecting NTP packets version 3 and 4 (RFC5905, RFC2030, RFC1769, RFC1361,
* RFC1305).
*
* Those packets have simple structure:
* 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 Identifier |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reference Timestamp (64) |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Originate Timestamp (64) |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Receive Timestamp (64) |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Transmit Timestamp (64) |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Key Identifier (optional) (32) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Message Digest (optional) (128/160) |
* | |
* | |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* NTP timestamps are represented as a 64-bit unsigned fixed-point number,
* in seconds relative to 0h on 1 January 1900. The integer part is in the
* first 32 bits and the fraction part in the last 32 bits.
*
*
* NTP Control messages as defined in version 2, 3 and 4 (RFC1119, RFC1305) use
* the following structure:
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |00 | VN | 110 |R E M| OpCode | Sequence |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Status | Association ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Offset | Count |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* | Data (468 octets max) |
* | |
* | | Padding (zeros) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Authenticator (optional) (96) |
* | |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* Not yet implemented: complete dissection of TPCTRL_OP_SETTRAP,
* NTPCTRL_OP_ASYNCMSG, NTPCTRL_OP_UNSETTRAPSETTRAP Control-Messages
*
*/
NTP Version 3
NTP Version 4
它的字段含义参考如下:
LI 闰秒标识器,占用2个bit
VN 版本号,占用3个bits,表示NTP的版本号,现在为3
Mode 模式,占用3个bits,表示模式
stratum(层),占用8个bits
Poll 测试间隔,占用8个bits,表示连续信息之间的最大间隔
Precision 精度,占用8个bits,,表示本地时钟精度
Root Delay根时延,占用8个bits,表示在主参考源之间往返的总共时延
Root Dispersion根离散,占用8个bits,表示在主参考源有关的名义错误
Reference Identifier参考时钟标识符,占用8个bits,用来标识特殊的参考源
参考时间戳,64bits时间戳,本地时钟被修改的最新时间。
原始时间戳,客户端发送的时间,64bits。
接受时间戳,服务端接受到的时间,64bits。
传送时间戳,服务端送出应答的时间,64bits。
认证符(可选项)
NTP的工作模式
NTP支持以下几种工作模式:
1.客户端/服务器模式
2.对等体模式
3.广播模式
4.组播模式
用户可以根据需要选择一种或几种工作模式进行时间同步,我们主要讲解客户端/服务器模式。
工作过程:
客户端上需要手工指定NTP服务器的地址。客户端向NTP服务器发送NTP时间同步报文。NTP服务器收到报文后会自动工作在服务器模式,并回复应答报文如果客户端可以从多个时间服务器获取时间同步,则客户端收到应答报文后,进行时钟过滤和选择,并与优选的时钟进行时间同步
一些常用的 NTP 服务器地址:
ntp.tencent.com
ntp1.tencent.com
ntp2.tencent.com
ntp3.tencent.com
ntp4.tencent.com
ntp5.tencent.com
ntp.aliyun.com
ntp1.aliyun.com
ntp2.aliyun.com
ntp3.aliyun.com
ntp4.aliyun.com
ntp5.aliyun.com
ntp6.aliyun.com
ntp7.aliyun.com
time.edu.cn
s2c.time.edu.cn
s2f.time.edu.cn
NTP客户端连接服务器获取网络时间戳
typedef enum xntp_mode_t
{
ntp_mode_unknow = 0, ///< 未定义
ntp_mode_initiative = 1, ///< 主动对等体模式
ntp_mode_passive = 2, ///< 被动对等体模式
ntp_mode_client = 3, ///< 客户端模式
ntp_mode_server = 4, ///< 服务器模式
ntp_mode_broadcast = 5, ///< 广播模式或组播模式
ntp_mode_control = 6, ///< 报文为 NTP 控制报文
ntp_mode_reserved = 7, ///< 预留给内部使用
} xntp_mode_t;
static x_void_t output_tu(x_cstring_t xszt_info, xtime_vnsec_t xtm_vnsec)
{
printf("%s : ", xszt_info);
output_ns(xtm_vnsec);
printf("\n");
}
/**********************************************************/
/**
*打上信息标号,输出 NTP 时间戳 的 具体时间信息。
*/
static x_void_t output_tm(x_cstring_t xszt_info, xtime_stamp_t * xtm_stamp)
{
printf("%s : ", xszt_info);
output_ts(xtm_stamp);
printf("\n");
}
...
xtime_vnsec_t ntpcli_get_time(
x_cstring_t xszt_host,
x_uint16_t xut_port,
x_uint32_t xut_tmout)
{
x_int32_t xit_errno = EPERM;
xtime_vnsec_t xtm_vnsec = XTIME_INVALID_VNSEC;
xntp_cliptr_t xntp_this = X_NULL;
do
{
xntp_this = ntpcli_open();
if (X_NULL == xntp_this)
{
break;
}
xit_errno = ntpcli_config(xntp_this, xszt_host, xut_port);
if (0 != xit_errno)
{
errno = xit_errno;
break;
}
xtm_vnsec = ntpcli_req_time(xntp_this, xut_tmout);
} while (0);
if (X_NULL != xntp_this)
{
ntpcli_close(xntp_this);
xntp_this = X_NULL;
}
return xtm_vnsec;
}
/**********************************************************/
/**
*初始化 NTP 的请求数据包。
*/
static x_void_t ntp_init_req_packet(xntp_pack_t * xnpt_dptr)
{
...
xnpt_dptr->xct_lvmflag = NTP_LI_VN_MODE(0, 3, ntp_mode_client);
xnpt_dptr->xct_stratum = 0;
xnpt_dptr->xct_ppoll = 4;
xnpt_dptr->xct_percision = (x_char_t)(-6);
xnpt_dptr->xut_rootdelay = (1 << 16);
xnpt_dptr->xut_rootdisp = (1 << 16);
xnpt_dptr->xut_refid = 0;
xnpt_dptr->xtms_reference.xut_seconds = 0;
xnpt_dptr->xtms_reference.xut_fraction = 0;
xnpt_dptr->xtms_originate.xut_seconds = 0;
xnpt_dptr->xtms_originate.xut_fraction = 0;
xnpt_dptr->xtms_receive .xut_seconds = 0;
xnpt_dptr->xtms_receive .xut_fraction = 0;
xnpt_dptr->xtms_transmit .xut_seconds = 0;
xnpt_dptr->xtms_transmit .xut_fraction = 0;
}
/**********************************************************/
/**
* 发送 NTP 请求,获取服务器时间戳。
*/
xtime_vnsec_t ntpcli_req_time(xntp_cliptr_t xntp_this, x_uint32_t xut_tmout)
{
x_int32_t xit_errno = EPERM;
// 参数验证
if (X_NULL == xntp_this)
{
errno = EINVAL;
return XTIME_INVALID_VNSEC;
}
if (name_is_ipv4(xntp_this->xszt_host, X_NULL))
xit_errno = ntpcli_get_4T(xntp_this, xntp_this->xszt_host, xut_tmout);
else
xit_errno = ntpcli_get_4T_by_name(xntp_this, xut_tmout);
if (0 != xit_errno)
{
errno = xit_errno;
return XTIME_INVALID_VNSEC;
}
return ntp_calc_4T(xntp_this->xtm_4time);
}
/** 常用的 NTP 服务器地址列表 */
x_cstring_t xszt_host[] =
{
"ntp.tencent.com" ,
"ntp1.tencent.com",
"ntp2.tencent.com",
"ntp3.tencent.com",
"ntp4.tencent.com",
"ntp5.tencent.com",
"ntp1.aliyun.com" ,
"ntp2.aliyun.com" ,
"ntp3.aliyun.com" ,
"ntp4.aliyun.com" ,
"ntp5.aliyun.com" ,
"ntp6.aliyun.com" ,
"ntp7.aliyun.com" ,
"time.edu.cn" ,
"s2c.time.edu.cn" ,
"s2f.time.edu.cn" ,
"s2k.time.edu.cn" ,
X_NULL
};
/** 网络地址(IP 或 域名)类型 */
typedef x_char_t x_host_t[TEXT_LEN_256];
/**
* 命令选项的各个参数。
*/
typedef struct xopt_args_t
{
x_bool_t xbt_usage; ///< 是否显示帮助信息
x_uint16_t xut_port; ///< NTP 服务器端口号(默认值为 123)
x_host_t xntp_host; ///< NTP 服务器地址
x_int32_t xit_rept; ///< 请求重复次数(默认值为 1)
x_uint32_t xut_tmout; ///< 网络请求的超时时间(单位为 毫秒,默认值取 3000)
} xopt_args_t;
/** 简单的判断 xopt_args_t 的有效性 */
#define XOPT_VALID(xopt) (('\0' != (xopt).xntp_host[0]) && ((xopt).xit_rept > 0))
/**********************************************************/
/**
* 字符串忽略大小写的比对操作。
*/
static x_int32_t xstr_icmp(x_cstring_t xszt_lcmp, x_cstring_t xszt_rcmp)
{
x_int32_t xit_lvalue = 0;
x_int32_t xit_rvalue = 0;
if (xszt_lcmp == xszt_rcmp)
return 0;
if (X_NULL == xszt_lcmp)
return -1;
if (X_NULL == xszt_rcmp)
return 1;
do
{
if (((xit_lvalue = (*(xszt_lcmp++))) >= 'A') && (xit_lvalue <= 'Z'))
xit_lvalue -= ('A' - 'a');
if (((xit_rvalue = (*(xszt_rcmp++))) >= 'A') && (xit_rvalue <= 'Z'))
xit_rvalue -= ('A' - 'a');
} while (xit_lvalue && (xit_lvalue == xit_rvalue));
return (xit_lvalue - xit_rvalue);
}
/**********************************************************/
/**
*显示应用程序的 命令行格式。
*/
x_void_t usage(x_cstring_t xszt_app)
{
x_int32_t xit_iter = 1;
printf("Usage:\n %s [-h] [-n <number>] -s <host> [-p <port>] [-t <msec>]\n", xszt_app);
printf("\t-h Output usage.\n");
printf("\t-n <number> The times of repetition.\n");
printf("\t-s <host> The host of NTP server, IP or domain.\n");
printf("\t-p <port> The port of NTP server, default 123.\n");
printf("\t-t <msec> Network request timeout in milliseconds, default 3000.\n");
printf("\nCommon NTP server:\n");
for (xit_iter = 0; X_NULL != xszt_host[xit_iter]; ++xit_iter)
{
printf("\t%s\n", xszt_host[xit_iter]);
}
printf("\n");
}
/**********************************************************/
/**
* 从命令行中,提取工作的选项参数信息。
*/
x_void_t get_opt(x_int32_t xit_argc, x_cstring_t xszt_argv[], xopt_args_t * xopt_args)
{
...
for (; xit_iter < xit_argc; ++xit_iter)
{
if (!xopt_args->xbt_usage && (0 == xstr_icmp("-h", xszt_argv[xit_iter])))
{
xopt_args->xbt_usage = X_TRUE;
}
else if (0 == xstr_icmp("-s", xszt_argv[xit_iter]))
{
if ((xit_iter + 1) < xit_argc)
{
...
}
}
else if (0 == xstr_icmp("-n", xszt_argv[xit_iter]))
{
if ((xit_iter + 1) < xit_argc)
xopt_args->xit_rept = atoi(xszt_argv[++xit_iter]);
}
else if (0 == xstr_icmp("-p", xszt_argv[xit_iter]))
{
if ((xit_iter + 1) < xit_argc)
xopt_args->xut_port = (x_uint16_t)atoi(xszt_argv[++xit_iter]);
}
else if (0 == xstr_icmp("-t", xszt_argv[xit_iter]))
{
if ((xit_iter + 1) < xit_argc)
xopt_args->xut_tmout = (x_uint32_t)atoi(xszt_argv[++xit_iter]);
}
}
}
int main(int argc, char * argv[])
{
...
do
{
get_opt(argc, argv, &xopt_args);
if (!XOPT_VALID(xopt_args))
{
usage(argv[0]);
break;
}
if (xopt_args.xbt_usage)
{
usage(argv[0]);
}
xntp_this = ntpcli_open();
if (X_NULL == xntp_this)
{
printf("ntpcli_open() return X_NULL, errno : %d\n", errno);
break;
}
ntpcli_config(xntp_this, xopt_args.xntp_host, xopt_args.xut_port);
for (xit_iter = 0; xit_iter < xopt_args.xit_rept; ++xit_iter)
{
xtm_vnsec = ntpcli_req_time(xntp_this, xopt_args.xut_tmout);
if (XTMVNSEC_IS_VALID(xtm_vnsec))
{
xtm_ltime = time_vnsec();
xtm_descr = time_vtod(xtm_vnsec);
xtm_local = time_vtod(xtm_ltime);
printf("\n[%d] %s:%d : \n",
xit_iter + 1,
xopt_args.xntp_host,
xopt_args.xut_port);
printf("\tNTP response : [ %04d-%02d-%02d %d %02d:%02d:%02d.%03d ]\n",
xtm_descr.ctx_year ,
xtm_descr.ctx_month ,
xtm_descr.ctx_day ,
xtm_descr.ctx_week ,
xtm_descr.ctx_hour ,
xtm_descr.ctx_minute,
xtm_descr.ctx_second,
xtm_descr.ctx_msec );
printf("\tLocal time : [ %04d-%02d-%02d %d %02d:%02d:%02d.%03d ]\n",
xtm_local.ctx_year ,
xtm_local.ctx_month ,
xtm_local.ctx_day ,
xtm_local.ctx_week ,
xtm_local.ctx_hour ,
xtm_local.ctx_minute,
xtm_local.ctx_second,
xtm_local.ctx_msec );
printf("\tDeviation : %lld us\n",
((x_int64_t)(xtm_ltime - xtm_vnsec)) / 10LL);
}
else
{
printf("\n[%d] %s:%d : errno = %d\n",
xit_iter + 1,
xopt_args.xntp_host,
xopt_args.xut_port,
errno);
}
}
} while (0);
if (X_NULL != xntp_this)
{
ntpcli_close(xntp_this);
xntp_this = X_NULL;
}
...
return 0;
}
...
运行结果:
If you need the complete source code, please add the WeChat number (c17865354792)
在客户端/服务器模式中,客户端向服务器发送时钟同步报文,报文中的Mode字段设置为3(客户模式)。服务器端收到报文后会自动工作在服务器模式,并发送应答报文,报文中的Mode字段设置为4(服务器模式)。客户端收到应答报文后,进行时钟过滤和选择,并同步到优选的服务器。
在该模式下,客户端能同步到服务器,而服务器无法同步到客户端。
- tcpdump抓包分析
NTP协议应用于分布式时间服务器和客户端之间,实现客户端和服务器的时间同步,从而使网络内所有设备的时钟基本保持一致。下面的报文是客户端连接服务器时产生的交互过程。
- NTP协议解析
If you need the complete source code, please add the WeChat number (c17865354792)
总结
NTP客户端启动与NTP服务器的时间请求交换。交换的结果是,客户端能够计算链路延迟及其本地偏移,并调整其本地时钟以匹配服务器计算机上的时钟。
Welcome to follow WeChat official account【程序猿编码】
参考:1.RFC 1305
2.http://ntp.neu.edu.cn/archives/92/
3.http://ntp.neu.edu.cn/archives/95