Network Time Protocol(NTP)协议是 使计算机时间同步化的一种协议,可使计算机对其服务器或时钟源(如石英钟,GPS 等)做同步化,提供高精确度的时间校正(LAN 上与标准间差小于 1 毫秒,WAN 上几十毫秒),且可用加密确认的方式来防止恶毒的协议攻击

NTP 数据包有 48 个字节,其中 NTP包头 16 字节,时间戳 32 个字节

 NTP 协议数据格式:

        LI: 跳跃指示器,警告在当月最后一天的最终时刻插入的迫近闺秒(闺秒)

        VN:版本号

       Mode:模式,0-预留;1-对称行为;3-客户机;4-服务器;5-广播;6-NTP 控制信息

       Stratum:对本地时钟级别的整体识别

       Poll:有符号整数表示连续信息间的最大间隔

      Precision:有符号整数表示本地时钟精确度

      Root Delay:有符号固定点序号表示 主要参考源的总延迟,很短时间内的位 15 到 16间的分段点

      Root Dispersion:无符号固定点序号表示 相对于主要参考源的正常差错,很短时间内的位 15 到 16 间的分段点

      Reference Identifier:识别特殊参考源

      Originate Timestamp:这是向服务器请求分离客户机的时间,采用 64 位时标格式

      Receive Timestamp:这是向服务器请求到达客户机的时间,采用 64 位时标格式

      Transmit Timestamp:这是向客户机答复分离服务器的时间,采用 64 位时标格式

      Authenticator(Optional):当实现了 NTP 认证模式时,主要标识符和信息数字域就包括已定义的信息认证代码(MAC)信息

简易 NTP 客户端流程图:

 

#include <sys/socket.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/un.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <string.h>
#include <netdb.h>

#define NTPPORT 123
#define TIMEPORT 37
#define NTPV1 "NTP/V1"
#define NTPV2 "NTP/V2"
#define NTPV3 "NTP/V3"
#define NTPV4 "NTP/V4"
#define TIME "TIME/UDP"

double SecondBef1970;
struct sockaddr_in sin;
struct addrinfo hints, *res=NULL;
int rc,sk;
char Protocol[32];

struct NTPPacket
{
    char Leap_Ver_Mode;
    /* client=0 */
    char Startum;
    char Poll;
    char Precision;
    double RootDelay;
    double Dispersion;
    char RefIdentifier[4];
    char RefTimeStamp[8];
    char OriTimeStamp[8];
    char RecvTimeStamp[8];
    char TransTimeStamp[8];
};

int ConstructPacket(char *Packet);
long GetSecondFrom1900(int End);
long GetNtpTime(int sk, struct addrinfo *res);

int main()
{
    memset(&hints,0,sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;

    /* 调用 getaddrinfo 函数,获取地址信息 */
    rc = getaddrinfo("200.205.253.254", "123", &hints, &res);
    if (rc != 0) 
    {
        perror("getaddrinfo");
        return 0;
    }

    sk = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (sk < 0)
    {
        perror("socket");
    }
    else
    {
        printf("socket success!\n");
    }

    /* 调用取得 NTP 时间函数 */
    GetNtpTime(sk, res);
}

/* 构建 NTP 协议包 */
int ConstructPacket(char *Packet)
{
    char Version = 1;
    long SecondFrom1900;
    long Zero = 0;
    int Port;
    time_t timer;
    
    strcpy(Protocol,NTPV1);
    /* 判断协议版本 */
    if(strcmp(Protocol, NTPV1) || strcmp(Protocol,NTPV2) || strcmp(Protocol, NTPV3) || strcmp(Protocol, NTPV4))
    {
        Port = NTPPORT;
        Version = Protocol[6] - 0x30;
        Packet[0] = (Version << 3) | 3; //LI--Version--Mode
        Packet[1] = 0;                  //Startum
        Packet[2] = 0;                  //Poll interval
        Packet[3] = 0;                  //Precision

        /* 包括 Root delay、Root disperse 和 Ref Indentifier */
        memset(&Packet[4], 0, 12);
        /* 包括 Ref timestamp、Ori timastamp 和 Receive Timestamp */
        memset(&Packet[16], 0, 24);

        time(&timer);
        SecondFrom1900 = SecondBef1970 + (long)timer;
        SecondFrom1900 = htonl(SecondFrom1900);

        memcpy(&Packet[40], &SecondFrom1900, 4);
        memcpy(&Packet[44], &Zero, 4);
        return 48;
    }
    else    // time/udp
    {
        Port = TIMEPORT;
        memset(Packet, 0, 4);
        return 4;
    }
    return 0;
}

/* 计算从 1900 年到现在一共有多少秒 */
long GetSecondFrom1900(int End)
{
    int Ordinal = 0;
    int Run = 0;
    long Result;
    int i;

    for(i = 1900; i < End; i++)
    {
        if(((i%4 == 0) && (i%100 != 0)) || (i%400 == 0)) 
        {
            Run++;
        }
        else
        {
            Ordinal++;
        }
    }
    Result = (Run * 366 + Ordinal * 365) * 24 * 3600;
    return Result;
}

/*获取 NTP 时间*/
long GetNtpTime(int sk, struct addrinfo *res)
{
    char Content[256];
    int PacketLen;
    fd_set PendingData;
    struct timeval BlockTime;
    int FromLen;
    int Count = 0;
    int result, i;
    int re;
    struct NTPPacket RetTime;

    PacketLen = ConstructPacket(Content);
    if(!PacketLen)
    {
        return 0;
    }
    /* 客户端给服务器端发送 NTP 协议数据包 */
    if((result = sendto(sk, Content, PacketLen, 0, res->ai_addr, res->ai_addrlen)) < 0)
    {
        perror("sendto");
    }
    else
    {
        printf("sendto success result=%d \n",result);
    }
    for(i = 0; i < 5; i++)
    {
        printf("in for\n");
        /* 调用 select 函数,并设定超时时间为 1s */
        FD_ZERO(&PendingData);
        FD_SET(sk, &PendingData);
        BlockTime.tv_sec = 1;
        BlockTime.tv_usec = 0;

        if(select(sk+1, &PendingData, NULL, NULL, &BlockTime) > 0)
        {
            FromLen = sizeof(sin);
            /* 接收服务器端的信息 */
            if((Count = recvfrom(sk, Content, 256, 0, res->ai_addr, &(res->ai_addrlen))) < 0)
            {
                perror("recvfrom");
            }
            else
            {
                printf("recvfrom success,Count=%d \n",Count);
            }

            if(Protocol == TIME)
            {
                memcpy(RetTime.TransTimeStamp, Content, 4);

                return 1;
            }
            else if(Count >= 48 && Protocol != TIME)
            {
                RetTime.Leap_Ver_Mode = Content[0];
                RetTime.Startum = Content[1];
                RetTime.Poll = Content[2];
                RetTime.Precision = Content[3];

                memcpy((void *)&RetTime.RootDelay, &Content[4], 4);
                memcpy((void *)&RetTime.Dispersion, &Content[8], 4);
                memcpy((void *)RetTime.RefIdentifier, &Content[12], 4);

                memcpy((void *)RetTime.RefTimeStamp, &Content[16], 8);
                memcpy((void *)RetTime.OriTimeStamp, &Content[24], 8);
                memcpy((void *)RetTime.RecvTimeStamp, &Content[32], 8);
                memcpy((void *)RetTime.TransTimeStamp, &Content[40], 8);

                return 1;
            }

        }
    }
    close(sk);
    return 0;
}