Ubuntu C++ NTP校时程序

需要sudo执行

复制代码
#include <iostream>
#include <cstring>
#include <ctime>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <endian.h>
#include <iomanip>

struct NTPPacket {
    uint8_t li_vn_mode;
    uint8_t stratum;
    uint8_t poll;
    uint8_t precision;
    uint32_t rootDelay;
    uint32_t rootDispersion;
    uint32_t refId;
    uint64_t refTs;
    uint64_t originTs;
    uint64_t recvTs;
    uint64_t transTs;
};

// 正确的 NTP 时间戳转换(处理字节序)
time_t ntp_to_unix(uint64_t ntp_ts) {
    // 关键:将网络字节序转换为主机字节序
    uint64_t host_ts = be64toh(ntp_ts);

    // 提取秒部分(高32位)
    uint32_t seconds = host_ts >> 32;

    // 减去 NTP epoch (1900) 到 Unix epoch (1970) 的秒数
    return (time_t)(seconds - 2208988800UL);
}

// 打印字节序转换后的值
void print_packet_info(const NTPPacket& packet, bool is_big_endian) {
    std::cout << "\n=== NTP Packet Details ===" << std::endl;

    // 1. 第一个字节的解析
    std::cout << "LI-VN-Mode: 0x" << std::hex << std::setw(2) << std::setfill('0')
              << (int)packet.li_vn_mode << std::dec << std::setfill(' ');
    uint8_t mode = packet.li_vn_mode & 0x07;
    uint8_t version = (packet.li_vn_mode >> 3) & 0x07;
    uint8_t leap = (packet.li_vn_mode >> 6) & 0x03;
    std::cout << " (Leap=" << (int)leap << ", Version=" << (int)version
              << ", Mode=" << (int)mode << ")";
    if (mode == 4) std::cout << " [Server Response]";
    else if (mode == 3) std::cout << " [Client Request]";
    std::cout << std::endl;

    // 2. Stratum(层级)
    std::cout << "Stratum: " << (int)packet.stratum;
    if (packet.stratum == 0) std::cout << " (unspecified)";
    else if (packet.stratum == 1) std::cout << " (primary reference)";
    else std::cout << " (secondary reference)";
    std::cout << std::endl;

    // 3. Poll(轮询间隔)
    std::cout << "Poll: " << (int)packet.poll << " (2^" << (int)packet.poll << " = "
              << (1 << packet.poll) << " seconds)" << std::endl;

    // 4. Precision(精度)
    std::cout << "Precision: " << (int)packet.precision << " (2^" << (int)packet.precision
              << " = " << (1.0 / (1 << (-packet.precision))) << " seconds)" << std::endl;

    // 5. Root Delay(根延迟)
    uint32_t rootDelay = ntohl(packet.rootDelay);
    std::cout << "Root Delay: 0x" << std::hex << std::setw(8) << std::setfill('0')
              << packet.rootDelay << std::dec << std::setfill(' ');
    std::cout << " (" << (rootDelay / 65536.0) << " seconds)" << std::endl;

    // 6. Root Dispersion(根离散度)
    uint32_t rootDisp = ntohl(packet.rootDispersion);
    std::cout << "Root Dispersion: 0x" << std::hex << std::setw(8) << std::setfill('0')
              << packet.rootDispersion << std::dec << std::setfill(' ');
    std::cout << " (" << (rootDisp / 65536.0) << " seconds)" << std::endl;

    // 7. Reference ID(参考ID)
    std::cout << "Reference ID: 0x" << std::hex << std::setw(8) << std::setfill('0')
              << ntohl(packet.refId) << std::dec << std::setfill(' ');
    // 尝试作为IP地址解析
    uint32_t refId = ntohl(packet.refId);
    if (refId >= 0x01000000 && refId <= 0xFE000000) {
        struct in_addr addr;
        addr.s_addr = packet.refId;
        std::cout << " (IP: " << inet_ntoa(addr) << ")";
    } else {
        // 可能是4个字符的ID
        char ref[5];
        memcpy(ref, &packet.refId, 4);
        ref[4] = '\0';
        std::cout << " (Code: " << ref << ")";
    }
    std::cout << std::endl;

    // 8. 时间戳(重点!)
    auto print_timestamp = [](const char* name, uint64_t ts) {
        // 原始值
        std::cout << name << " (raw): 0x" << std::hex << std::setw(16) << std::setfill('0')
                  << ts << std::dec << std::setfill(' ');

        // 转换为正确的时间
        uint64_t host_ts = be64toh(ts);
        uint32_t seconds = host_ts >> 32;
        uint32_t fraction = host_ts & 0xFFFFFFFF;

        if (seconds > 0) {
            time_t t = seconds - 2208988800UL;
            std::cout << " => " << ctime(&t);
            std::cout << "    Fraction: 0x" << std::hex << std::setw(8) << std::setfill('0')
                      << fraction << std::dec << std::setfill(' ');
            std::cout << " (" << (fraction / 4294967296.0 * 1000000) << " microseconds)" << std::endl;
        } else {
            std::cout << " (zero)" << std::endl;
        }
    };

    std::cout << "\n--- Timestamps ---" << std::endl;
    print_timestamp("Reference Timestamp", packet.refTs);
    print_timestamp("Origin Timestamp", packet.originTs);
    print_timestamp("Receive Timestamp", packet.recvTs);
    print_timestamp("Transmit Timestamp", packet.transTs);

    // 9. 计算出网络延迟和偏差(如果有Origin和Receive时间)
    if (packet.originTs != 0 && packet.recvTs != 0) {
        uint64_t origin_host = be64toh(packet.originTs);
        uint64_t recv_host = be64toh(packet.recvTs);
        uint64_t trans_host = be64toh(packet.transTs);

        uint32_t origin_sec = origin_host >> 32;
        uint32_t recv_sec = recv_host >> 32;
        uint32_t trans_sec = trans_host >> 32;

        if (origin_sec > 0 && recv_sec > 0 && trans_sec > 0) {
            time_t t1 = origin_sec - 2208988800UL;
            time_t t2 = recv_sec - 2208988800UL;
            time_t t3 = trans_sec - 2208988800UL;

            double origin_f = (origin_host & 0xFFFFFFFF) / 4294967296.0;
            double recv_f = (recv_host & 0xFFFFFFFF) / 4294967296.0;
            double trans_f = (trans_host & 0xFFFFFFFF) / 4294967296.0;

            double origin_time = origin_sec + origin_f;
            double recv_time = recv_sec + recv_f;
            double trans_time = trans_sec + trans_f;

            // 简单计算:假设客户端发送时间和接收时间对称
            std::cout << "\n--- Network Statistics ---" << std::endl;
            std::cout << "Client send time: " << std::fixed << std::setprecision(3) << origin_time << std::endl;
            std::cout << "Server receive time: " << recv_time << std::endl;
            std::cout << "Server transmit time: " << trans_time << std::endl;

            // 往返延迟 = (当前时间 - 发送时间) - (服务器处理时间)
            // 这里简化计算
            time_t now;
            time(&now);
            double rtt = (now + 0.0) - origin_time;
            std::cout << "Estimated RTT: " << rtt << " seconds" << std::endl;
        }
    }
    std::cout << "===========================" << std::endl;
}

// 打印原始字节数据(十六进制)
void print_raw_data(const uint8_t* data, size_t len) {
    std::cout << "\n=== Raw Packet Data (Hex) ===" << std::endl;
    for (size_t i = 0; i < len; i++) {
        std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)data[i] << " ";
        if ((i + 1) % 16 == 0) {
            std::cout << "  ";
            // 打印ASCII
            for (size_t j = i - 15; j <= i; j++) {
                if (data[j] >= 32 && data[j] <= 126) {
                    std::cout << (char)data[j];
                } else {
                    std::cout << ".";
                }
            }
            std::cout << std::endl;
        }
    }
    if (len % 16 != 0) {
        // 补全最后的ASCII
        size_t last_start = (len / 16) * 16;
        for (size_t j = last_start; j < len; j++) {
            if (data[j] >= 32 && data[j] <= 126) {
                std::cout << (char)data[j];
            } else {
                std::cout << ".";
            }
        }
        std::cout << std::endl;
    }
    std::cout << std::dec << std::setfill(' ');
    std::cout << "=============================" << std::endl;
}

// 改进版:获取时间并正确处理字节序
bool get_time_via_ntp(const char* server, time_t& outTime) {
    int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sockfd < 0) return false;

    struct sockaddr_in serverAddr;
    struct hostent *serverHost = gethostbyname(server);
    if (serverHost == nullptr) {
        close(sockfd);
        return false;
    }

    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(123);
    memcpy(&serverAddr.sin_addr, serverHost->h_addr_list[0], serverHost->h_length);

    // 设置超时
    struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    NTPPacket packet;
    memset(&packet, 0, sizeof(packet));
    packet.li_vn_mode = 0x1B;  // NTP 请求

    if (sendto(sockfd, &packet, sizeof(packet), 0,
               (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
        close(sockfd);
        return false;
    }

    socklen_t addrLen = sizeof(serverAddr);
    if (recvfrom(sockfd, &packet, sizeof(packet), 0,
                 (struct sockaddr*)&serverAddr, &addrLen) < 0) {
        close(sockfd);
        return false;
    }

    close(sockfd);

    // 调试输出:打印原始时间戳(可选)
     std::cout << "Raw timestamp: 0x" << std::hex << packet.transTs << std::dec << std::endl;

     // 打印原始数据
     print_raw_data((const uint8_t*)&packet, addrLen);

     // 打印解析后的字段
     print_packet_info(packet, true);

     outTime = ntp_to_unix(packet.transTs);

    // 验证时间合理性(应该在 2020-2030 之间)
    if (outTime < 1577836800 || outTime > 1893456000) {  // 2020-2030
        std::cout << "  Warning: suspicious time: " << ctime(&outTime);
        return false;
    }

    return true;
}

// 备用方案:使用多个服务器并返回成功
bool get_time_with_fallback(time_t& outTime) {
    const char* servers[] = {
        "ntp.aliyun.com",
        "ntp1.aliyun.com",
        "ntp.tencent.com",
        "pool.ntp.org",
        "cn.pool.ntp.org",
        "time.google.com"
    };

    for (int i = 0; i < 6; i++) {
        std::cout << "  Testing: " << servers[i] << " ... ";
        if (get_time_via_ntp(servers[i], outTime)) {
            std::cout << "SUCCESS" << std::endl;
            return true;
        }
        std::cout << "FAILED" << std::endl;
    }
    return false;
}

int main() {
    time_t syncTime = 0;

    std::cout << "=== NTP Time Sync (Fixed Endian) ===" << std::endl;

    if (!get_time_with_fallback(syncTime)) {
        std::cerr << "\nAll NTP servers failed." << std::endl;
        return 1;
    }

    // 验证时间
    struct tm* tm_info = localtime(&syncTime);
    if (tm_info->tm_year < 120 || tm_info->tm_year > 130) {  // 2020-2030
        std::cerr << "Warning: Time seems incorrect: " << ctime(&syncTime);
        std::cerr << "Please check your system's timezone settings." << std::endl;
    }

    // 设置系统时间
    struct timeval tv;
    tv.tv_sec = syncTime;
    tv.tv_usec = 0;

    if (settimeofday(&tv, nullptr) < 0) {
        perror("Failed to set system time (need sudo)");
        return 1;
    }

    std::cout << "\n✓ System time updated to: " << ctime(&syncTime);

    // 显示当前时间
    time_t now;
    time(&now);
    std::cout << "Current system time: " << ctime(&now);

    return 0;
}
相关推荐
RisunJan1 小时前
Linux命令-vgdisplay(显示卷组详细属性)
linux·运维·数据库
深念Y1 小时前
RIO-UL00(EMUI 4.1 / Android 6.0.1 / arm64)开机自启动 sshd
android·linux·华为·安卓·chroot·sshd·emui
Escalating_xu1 小时前
【Linux线程】线程控制全解析:终止、join/detach、cancel、线程栈与 NPTL(下篇)
java·linux·运维
老当益壮梁奶奶2 小时前
Linux软件编程学习笔记(二)Linux文件IO编程入门:从fopen到fgetc
linux·c语言·c++·笔记·学习
sunoo-2292 小时前
C 语言文件 IO 全攻略:从基础函数到实战踩坑(BMP 读取 + 词典查询)
linux·c语言·前端·笔记·vscode·学习
雪的季节2 小时前
C++ 高级(泛型编程与模板)(有空再研究吧)
c++
三十岁老牛再出发2 小时前
8月21日总结
c++·python
草莓熊Lotso2 小时前
【Linux网络加餐】手动部署:SSH 与 Web 服务实战 + 底层原理全解析
linux·运维·网络·人工智能·python·langchain·ssh
CHHH_HHH2 小时前
【Linux系统篇】深入解析Linux文件系统:从磁盘寻址到软硬链接
linux·服务器·开发语言·后端·ubuntu