ntp协议及客户端开发

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、注意一些时间结构的区别。

你可能感兴趣的:(windows,server,struct,reference,64bit,delay)