ntp协议及客户端开发
说明:今天下午没什么事,接了个任务,让研究下ntp协议,写个客户端来对时,记录下是学习成果。
参考资料:ntp资料:http://ntp.buptnet.edu.cn/ntp_chinese/lesson/lesson.htm
开源代码:http://doolittle.icarus.com/ntpclient/
代码下载:
linux平台代码:http://pickup.mofile.com/7867848658129149
windows平台代码:http://pickup.mofile.com/0760743850823000
一、ntp介绍:
Network Time Protocol(NTP)是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS等等)做同步
化,它可以提供高精准度的时间校正(LAN上与标准间差小于1毫秒,WAN上几十毫秒),且可介由加密确认的方式来防止恶毒的协议攻击。
二、ntp协议格式:
NTP packet = NTP header + Four TimeStamps = 48byte
NTP header : 16byte
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|LI | VN |Mode | Stratum | Poll | Precision | Root Delay | Root Dispersion | Reference Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
LeapYearIndicator : 2bit
VersionNumber : 3bit
Stratum : 8bit
Mode : 3 bit
PollInterval : 8 bit
Percision : 8bit
Root delay : 32bit
Root Dispersion : 32bit
Reference Identifier : 32bit
Four TimeStamps : 32byte
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Reference Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
Reference Timestamp : 64bit
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Originate Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
Originate Timestamp : 64bit
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Receive Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
Receive Timestamp : 64bit
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Transmit Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
Transmit Timestamp : 64bit
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Authenticator (optional) (96) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
以下为我捕获的ntp客户端与服务端交互的协议包:
NTP client send packet:
/* NTP header: 16 bytes */
1B 00 04 FA 00 01 00 00 00 01 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
/* Originate Timestamp */
CA 10 E7 F9 00 10 62 4D
NTP server response packet:
/* NTP header: 16 bytes */
1C 02 04 EC 00 00 00 29 00 00 07 AF CA 70 01 22
/* Reference Timestamp */
CA 10 E5 43 4D FA C7 59
/* Originate Timestamp */
CA 10 E7 F9 00 10 62 4D
/* Receive Timestamp */
CA 10 E7 F8 F0 7A 59 74
/* Transmit Timestamp */
CA 10 E7 F8 F0 7B 42 AB
三、ntp客户端代码:与ntp服务端通信,获取时间信息并更新本地时间
(注:代码经过精简,删除了一些变量定义及错误处理,完整代码可以下载)
linux平台代码:
#define NTP_SERVER "time.buptnet.edu.cn"
#define NTP_PORT 123
#define JAN_1970 0x83aa7e80 /* 2208988800 1970 - 1900 in seconds */
#define NTPFRAC(x) (4294 * (x) + ((1981 * (x))>>11))
#define USEC(x) (((x) >> 12) - 759 * ((((x) >> 10) + 32768) >> 16))
struct ntptime
{
unsigned int coarse;
unsigned int fine;
};
void send_packet(int fd)
{
unsigned int data[12];
struct timeval now;
int ret;
#define LI 0
#define VN 3
#define MODE 3
#define STRATUM 0
#define POLL 4
#define PREC -6
if (sizeof(data) != 48)
{
fprintf(stderr,"size error/n");
return;
}
memset((char*)data, 0, sizeof(data));
data[0] = htonl((LI << 30) | (VN << 27) | (MODE << 24)
| (STRATUM << 16) | (POLL << 8) | (PREC & 0xff));
data[1] = htonl(1<<16); /* Root Delay (seconds) */
data[2] = htonl(1<<16); /* Root Dispersion (seconds) */
gettimeofday(&now, NULL);
data[10] = htonl(now.tv_sec + JAN_1970); /* Transmit Timestamp coarse */
data[11] = htonl(NTPFRAC(now.tv_usec)); /* Transmit Timestamp fine */
send(fd, data, 48, 0);
}
void get_packet_timestamp(int usd, struct ntptime *udp_arrival_ntp)
{
struct timeval udp_arrival;
gettimeofday(&udp_arrival, NULL);
udp_arrival_ntp->coarse = udp_arrival.tv_sec + JAN_1970;
udp_arrival_ntp->fine = NTPFRAC(udp_arrival.tv_usec);
}
void rfc1305print(unsigned int *data, struct ntptime *arrival, struct timeval* tv)
{
int li, vn, mode, stratum, poll, prec;
int delay, disp, refid;
struct ntptime reftime, orgtime, rectime, xmttime;
struct tm *ltm;
#define Data(i) ntohl(((unsigned int *)data)[i])
li = Data(0) >> 30 & 0x03;
vn = Data(0) >> 27 & 0x07;
mode = Data(0) >> 24 & 0x07;
stratum = Data(0) >> 16 & 0xff;
poll = Data(0) >> 8 & 0xff;
prec = Data(0) & 0xff;
if (prec & 0x80) prec|=0xffffff00;
delay = Data(1);
disp = Data(2);
refid = Data(3);
reftime.coarse = Data(4);
reftime.fine = Data(5);
orgtime.coarse = Data(6);
orgtime.fine = Data(7);
rectime.coarse = Data(8);
rectime.fine = Data(9);
xmttime.coarse = Data(10);
xmttime.fine = Data(11);
#undef Data
tv->tv_sec = xmttime.coarse - JAN_1970;
tv->tv_usec = USEC(xmttime.fine);
}
void set_local_time(struct timeval tv)
{
/* need root user. */
if (0 != getuid() && 0 != geteuid())
return;
settimeofday(&tv, NULL);
}
int main(void)
{
/* create socket. */
sock = socket(PF_INET, SOCK_DGRAM, 0);
/* bind local address. */
memset(&addr_src, 0, addr_len);
addr_src.sin_family = AF_INET;
addr_src.sin_addr.s_addr = htonl(INADDR_ANY);
addr_src.sin_port = htons(0);
bind(sock, (struct sockaddr*)&addr_src, addr_len);
/* connect to ntp server. */
memset(&addr_dst, 0, addr_len);
addr_dst.sin_family = AF_INET;
{
struct hostent* host = gethostbyname(NTP_SERVER);
memcpy(&(addr_dst.sin_addr.s_addr), host->h_addr_list[0], 4);
}
addr_dst.sin_port = htons(NTP_PORT);
connect(sock, (struct sockaddr*)&addr_dst, addr_len);
while (1)
{
fd_set fds_read;
struct timeval timeout;
int ret;
unsigned int buf[12];
int len;
struct sockaddr server;
socklen_t svr_len;
struct ntptime arrival_ntp;
struct timeval newtime;
FD_ZERO(&fds_read);
FD_SET(sock, &fds_read);
timeout.tv_sec = 6;
timeout.tv_usec = 0;
ret = select(sock + 1, &fds_read, NULL, NULL, &timeout);
if (0 == ret || !FD_ISSET(sock, &fds_read))
{
/* send ntp protocol packet. */
send_packet(sock);
continue;
}
/* recv ntp server's response. */
recvfrom(sock, buf, sizeof(buf), 0, &server, &svr_len);
/* get local timestamp. */
get_packet_timestamp(sock, &arrival_ntp);
/* get server's time and print it. */
rfc1305print(buf, &arrival_ntp, &newtime);
/* set local time to the server's time, if you're a root user. */
set_local_time(newtime);
}
close(sock);
return 0;
}
windows平台代码跟linux平台代码差不多,除了注意几个地方:
1、recvfrom的参数svr_len在windows下必须赋值,否则报无效指针:int svr_len = sizeof(struct sockaddr);
2、windows下用_ftime函数来代替gettimeofday函数;
3、windows下用SetLocalTime来代替settimeofday函数;
4、注意一些时间结构的区别。