利用NTP协议获取并更新系统时间

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

NTP 提供准确时间,首先要有准确的时间来源,这一时间应该是国际标准时间 UTC。 NTP 获得 UTC 的 时间来源可以是原子钟、天文台、卫星,也可以从 Internet 上获取。这样就有了准确而可靠的时间源。时间是按 NTP 服务器的等级传播。按照距离外部 UTC 源的远近将所有服务器归入不同的 Stratun(层)中。 Stratum-1 在顶层,有外部 UTC 接入,而 Stratum-2 则从 Stratum-1 获取时间,Stratum-3 从 Stratum-2 获取 时间,以此类推,但 Stratum 层的总数限制在 15 以内。所有这些服务器在逻辑上形成阶梯式的架构并相互 连接,而 Stratum-1 的时间服务器是整个系统的基础。

进行网络协议实现时最重要的是了解协议数据格式。NTP 数据包有 48 个字节,其中 NTP 包头 16 字节, 时间戳 32 个字节。

协议格式

image-20230212230320074

报文字段

字段名长 度含义
LI(Leap Indicator)2比特这是一个两位的代码,表示在NTP时间标尺中将要插入的下一跳情况。值为“11”时表示告警状态,警告在当月最后一天的最终时刻插入的迫近闺秒(闺秒),此时时钟不能被同步。
VN(Version Number)3比特NTP的版本号。
Mode3比特NTP的工作模式。不同值表示的含义如下:0:reserved,保留。1:symmetric active,主动对等体模式。2:symmetric passive,被动对等体模式。3:client,客户模式。4:server,服务器模式。5:broadcast,广播模式。6:reserved for NTP control messages,NTP控制报文。7:reserved for private use,内部使用预留。在主/被动对称模式中,有一对 一的连接,双方均可同步对方或被对方同步,先发出申请建立连接的一方工作在主动模式下,另一方工作在被动模式下; 客户/服务器模式与主/被动模式基本相同,惟一区别在于客户方可被服务器同步,但服务器不能 被客户同步;在广播模式中,有一对多的连接,服务器不论客户工作在何种模式下,都会主动发 出时间信息,客户根据此信息调整自己的时间。
Stratum8比特表示本地时钟的层级数,定义了时钟的准确度。层数为1的时钟准确度最高,从1到15依次递减。
Poll Interval8比特轮询时间,即发送报文的最小间隔时间。
Precision8比特时钟的精度。
Root Delay32比特到主参考时钟的总往返延迟时间,它是有 15~16 位小数部分的符号定点小数。
Root Dispersion32比特本地时钟相对于主参考时钟的最大误差,它是有 15~16 位小数部分的无符号定点小 数。
Reference Identifier32比特标识特定参考时钟。
Reference Timestamp64比特本地时钟最后一次被设定或更新的时间,采用 64 位时标格式。如果值为0表示本地时钟从未被同步过。
Originate Timestamp64比特NTP报文离开源端时的本地时间。
Receive Timestamp64比特NTP报文到达目的端的本地时间。
Transmit Timestamp64比特目的端应答报文离开服务器端的本地时间。
Authenticator96比特(可选)验证信息,当实现了 NTP 认证模式时,主要标识符和信息数字域就包括已定义的 信息认证代码(MAC)信息。

示例

由于 NTP 协议中涉及比较多的时间相关的操作,为了简化实现过程,在本实验中,仅要求实现 NTP 协议 客户端部分的网络通信模块,也就是构造 NTP 协议字段进行发送和接收,最后与时间相关的操作不需进行 处理。NTP 协议是作为 OSI 参考模型的高层协议比较适合采用 UDP 传输协议进行数据传输,专用端口号 为 123。在实验中,因为国家授时中心服务器经常难以连接,所以使用cn.pool.ntp.org(IP 地址为 162.159.200.123)作为 NTP(网络时间)服务器。

命令行输入”nslookup 域名“就可以查看对应的IP地址。

实验流程如下:

  1. 获取NTP服务器地址信息;
  2. 新建socket;
  3. 构建ntp协议包;
  4. 向NTP服务器发送请求协议包;
  5. 从NTP服务器接收协议包
  6. 更新当前系统时间;
  • 具体代码如下
/* ntp.c */ 
#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 <time.h> 
#include <sys/ioctl.h> 
#include <unistd.h> 
#include <netinet/in.h> 
#include <string.h> 
#include <netdb.h> 

#define NTP_PORT 123 /*NTP 专用端口号字符串*/ 
#define TIME_PORT 37 /* TIME/UDP 端口号 */ 
#define NTP_SERVER_IP "210.72.145.44" /*国家授时中心 IP*/ 
#define NTP_PORT_STR "123" /*NTP 专用端口号字符串*/ 
#define NTPV1 "NTP/V1" /*协议及其版本号*/ 
#define NTPV2 "NTP/V2" 
#define NTPV3 "NTP/V3" 
#define NTPV4 "NTP/V4" 
#define TIME "TIME/UDP" 
#define NTP_PCK_LEN 48 
#define LI 0 
#define VN 3 
#define MODE 3 
#define STRATUM 0 
#define POLL 4 
#define PREC -6 
#define JAN_1970 0x83aa7e80 /* 1900 年~1970 年之间的时间秒数 */ 
#define NTPFRAC(x) (4294 * (x) + ((1981 * (x)) >> 11)) 
#define USEC(x) (((x) >> 12) - 759 * ((((x) >> 10) + 32768) >> 16)) 

typedef struct _ntp_time 
{ 
    unsigned int coarse; 
    unsigned int fine; 
} ntp_time; 

struct ntp_packet 
{ 
    unsigned char leap_ver_mode; 
    unsigned char startum; 
    char poll; 
    char precision; 
    int root_delay; 
    int root_dispersion; 
    int reference_identifier; 
    ntp_time reference_timestamp; 
    ntp_time originage_timestamp; 
    ntp_time receive_timestamp; 
    ntp_time transmit_timestamp; 
}; 

char protocol[32]; 


/*构建 NTP 协议包*/ 
int construct_packet(char *packet) 
{ 
    char version = 1; 
    long tmp_wrd; 
    int port; 
    time_t timer; 
    strcpy(protocol, NTPV3); 
    /*判断协议版本*/ 
    if(!strcmp(protocol, NTPV1) || !strcmp(protocol, NTPV2) ||\
       !strcmp(protocol, NTPV3)||!strcmp(protocol, NTPV4)) 
    { 
        memset(packet, 0, NTP_PCK_LEN); 
        port = NTP_PORT; 
        /*设置 16 字节的包头*/ 
        version = protocol[6] - 0x30; 
        tmp_wrd = htonl((LI << 30)|(version << 27) 
                        |(MODE << 24)|(STRATUM << 16)|(POLL << 8)|(PREC & 0xff)); 
        memcpy(packet, &tmp_wrd, sizeof(tmp_wrd)); 

        /*设置 Root Delay、Root Dispersion 和 Reference Indentifier */ 
        tmp_wrd = htonl(1<<16); 
        memcpy(&packet[4], &tmp_wrd, sizeof(tmp_wrd)); 
        memcpy(&packet[8], &tmp_wrd, sizeof(tmp_wrd)); 
        /*设置 Timestamp 部分*/ 
        time(&timer); 
        /*设置 Transmit Timestamp coarse*/ 
        tmp_wrd = htonl(JAN_1970 + (long)timer); 
        memcpy(&packet[40], &tmp_wrd, sizeof(tmp_wrd)); 
        /*设置 Transmit Timestamp fine*/ 
        tmp_wrd = htonl((long)NTPFRAC(timer)); 
        memcpy(&packet[44], &tmp_wrd, sizeof(tmp_wrd)); 
        return NTP_PCK_LEN; 
    } 
    else if (!strcmp(protocol, TIME))/* "TIME/UDP" */ 
    { 
        port = TIME_PORT; 
        memset(packet, 0, 4); 
        return 4; 
    } 
    return 0; 
} 


/*获取 NTP 时间*/ 
int get_ntp_time(int sk, struct addrinfo *addr, struct ntp_packet *ret_time) 
{ 
    fd_set pending_data; 
    struct timeval block_time; 
    char data[NTP_PCK_LEN * 8]; 
    int packet_len, data_len = addr->ai_addrlen, count = 0, result, i, re; 
    if (!(packet_len = construct_packet(data))) 
    { 
        return 0; 
    } 
    /*客户端给服务器端发送 NTP 协议数据包*/ 
    if ((result = sendto(sk, data, 
                         packet_len, 0, addr->ai_addr, data_len)) < 0) 
    { 
        perror("sendto"); 
        return 0; 
    } 

    /*调用 select()函数,并设定超时时间为 1s*/ 
    FD_ZERO(&pending_data); 
    FD_SET(sk, &pending_data); 
    block_time.tv_sec=10; 
    block_time.tv_usec=0;
    if (select(sk + 1, &pending_data, NULL, NULL, &block_time) > 0) 
    { 
        /*接收服务器端的信息*/ 
        if ((count = recvfrom(sk, data, 
                              NTP_PCK_LEN * 8, 0, addr->ai_addr, &data_len)) < 0) 
        { 
            perror("recvfrom"); 
            return 0; 
        } 

        if (protocol == TIME) 
        { 
            memcpy(&ret_time->transmit_timestamp, data, 4); 
            return 1; 
        } 
        else if (count < NTP_PCK_LEN) 
        { 
            return 0; 
        } 
        /* 设置接收 NTP 包的数据结构 */ 
        ret_time->leap_ver_mode = ntohl(data[0]); 
        ret_time->startum = ntohl(data[1]); 
        ret_time->poll = ntohl(data[2]); 
        ret_time->precision = ntohl(data[3]); 
        ret_time->root_delay = ntohl(*(int*)&(data[4])); 
        ret_time->root_dispersion = ntohl(*(int*)&(data[8])); 
        ret_time->reference_identifier = ntohl(*(int*)&(data[12])); 
        ret_time->reference_timestamp.coarse = ntohl (*(int*)&(data[16])); 
        ret_time->reference_timestamp.fine = ntohl(*(int*)&(data[20])); 
        ret_time->originage_timestamp.coarse = ntohl(*(int*)&(data[24])); 
        ret_time->originage_timestamp.fine = ntohl(*(int*)&(data[28])); 
        ret_time->receive_timestamp.coarse = ntohl(*(int*)&(data[32])); 
        ret_time->receive_timestamp.fine = ntohl(*(int*)&(data[36])); 
        ret_time->transmit_timestamp.coarse = ntohl(*(int*)&(data[40])); 
        ret_time->transmit_timestamp.fine = ntohl(*(int*)&(data[44])); 
        return 1; 
    } /* end of if select */ 
    return 0; 
} 


/* 修改本地时间 */ 
int set_local_time(struct ntp_packet * pnew_time_packet) 
{ 
 struct timeval tv; 
 tv.tv_sec = pnew_time_packet->transmit_timestamp.coarse - JAN_1970; 
 tv.tv_usec = USEC(pnew_time_packet->transmit_timestamp.fine); 
 return settimeofday(&tv, NULL); 
} 


int main() 
{ 
    int sockfd, rc; 
    struct addrinfo hints, *res = NULL; 
    struct ntp_packet new_time_packet; 
    memset(&hints, 0, sizeof(hints)); 
    hints.ai_family = AF_UNSPEC; 
    hints.ai_socktype = SOCK_DGRAM; 
    hints.ai_protocol = IPPROTO_UDP; 
    /*调用 getaddrinfo()函数,获取地址信息*/ 
    rc = getaddrinfo(NTP_SERVER_IP, NTP_PORT_STR, &hints, &res);
    if (rc != 0) 
    { 
        perror("getaddrinfo"); 
        return 1; 
    } 
    /* 创建套接字 */ 
    sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (sockfd <0 ) 
    { 
        perror("socket"); 
        return 1; 
    } 
    /*调用取得 NTP 时间的函数*/ 
    if (get_ntp_time(sockfd, res, &new_time_packet))
    { 
        /*调整本地时间*/ 
        if (!set_local_time(&new_time_packet)) 
        { 
            printf("NTP client success!\n"); 
        } 
    } 
    close(sockfd); 
    return 0; 
} 

  • 注意:因为更改系统时间需要根用户权限,所以在运行时需要在前面添加sudo

  • 为了更好地观察程序的效果,先用 date 命令修改一下系统时间,再运行实例程序。运行完了之后再查看系 统时间,可以发现已经恢复准确的系统时间了。具体运行结果如下所示:

    leon@ubuntu:~/letcode/socket$ date			//原来系统时间
    Sun Feb 12 22:32:58 CST 2023
    leon@ubuntu:~/letcode/socket$ date			//故意更改后的系统时间
    Mon Feb 13 22:33:48 CST 2023
    leon@ubuntu:~/letcode/socket$ sudo ./ntp   //运行程序,获取并重设系统时间
    [sudo] leon 的密码: 
    Get time from NTP success!
    NTP client success!
    leon@ubuntu:~/letcode/socket$ date			//验证系统时间是否正确设置
    Sun Feb 12 22:34:15 CST 2023
    
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值