ntp-tools / ntp-server-minimal.c /
a78b58f 2 months ago
1 contributor
183 lines | 5.253kb
// ntp_server.c
// Minimal NTP server, cross-platform (Linux/Windows)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include <sys/time.h>

#ifdef _WIN32
  #include <basetsd.h>
  #include <windef.h> 
  #include <winsock2.h>
  #include <windows.h> 
  #include <ws2tcpip.h>
  //#pragma comment(lib, "ws2_32.lib")
  #define CLOSESOCK closesocket
  // Provide localtime_r compatibility using localtime_s
  #define localtime_r(timep, result) ( localtime_s((result), (timep)) == 0 ? (result) : NULL )
  #define gmtime_r(timep, result)    ( gmtime_s((result), (timep)) == 0 ? (result) : NULL )
#else
  #include <unistd.h>
  #include <arpa/inet.h>
  #include <netinet/in.h>
  #include <sys/socket.h>
  #define CLOSESOCK close
#endif

#define NTP_PORT 123
#define NTP_TIMESTAMP_DELTA 2208988800ull  // 1900 to 1970

typedef struct {
    uint8_t li_vn_mode;
    uint8_t stratum;
    uint8_t poll;
    int8_t  precision;
    uint32_t root_delay;
    uint32_t root_dispersion;
    uint32_t ref_id;
    uint32_t ref_ts_sec;
    uint32_t ref_ts_frac;
    uint32_t orig_ts_sec;
    uint32_t orig_ts_frac;
    uint32_t recv_ts_sec;
    uint32_t recv_ts_frac;
    uint32_t tx_ts_sec;
    uint32_t tx_ts_frac;
} ntp_packet;

#ifdef _WIN32
static void get_ntp_time(uint32_t *sec, uint32_t *frac) {
    FILETIME ft;
    GetSystemTimeAsFileTime(&ft);
    ULARGE_INTEGER uli;
    uli.LowPart  = ft.dwLowDateTime;
    uli.HighPart = ft.dwHighDateTime;

    // Convert 100ns intervals since 1601 → Unix epoch (seconds)
    uint64_t seconds_since_1601 = uli.QuadPart / 10000000ULL;
    uint64_t usec_part = (uli.QuadPart / 10ULL) % 1000000ULL;
    time_t tv_sec = (time_t)(seconds_since_1601 - 11644473600ULL);
    long tv_usec = (long)usec_part;

    // Convert timeval → NTP
    *sec  = htonl((uint32_t)((uint64_t)tv_sec + NTP_TIMESTAMP_DELTA));
    *frac = htonl((uint32_t)((double)tv_usec * (1ULL << 32) / 1.0e6));
}
#else
static void timeval_to_ntp(struct timeval *tv, uint32_t *sec, uint32_t *frac) {
    // Cast tv_sec to uint64_t to avoid overflow
    uint64_t ntp_sec = (uint64_t)tv->tv_sec + NTP_TIMESTAMP_DELTA;
    *sec  = htonl((uint32_t)ntp_sec);
    *frac = htonl((uint32_t)((double)tv->tv_usec * (1ULL<<32) / 1.0e6));
}
#endif 

static void log_request(struct sockaddr_in *cliaddr) {
    char ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &(cliaddr->sin_addr), ip, sizeof(ip));

    time_t now = time(NULL);
    struct tm local_tm, gmt_tm;

    // Get local time + UTC time
    localtime_r(&now, &local_tm);
    gmtime_r(&now, &gmt_tm);

    // Compute offset in seconds
    int offset = (int)difftime(mktime(&local_tm), mktime(&gmt_tm));

    // Convert offset to ±hhmm
    char tzbuf[10];
    int hours = offset / 3600;
    int mins  = abs(offset % 3600) / 60;
    snprintf(tzbuf, sizeof(tzbuf), "%+03d%02d", hours, mins);

    // Format datetime
    char timebuf[64];
    strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S", &local_tm);

    printf("%s%s Request from %s:%d\n",
           timebuf, tzbuf,
           ip,
           ntohs(cliaddr->sin_port));
    fflush(stdout);
}

int main() {
#ifdef _WIN32
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2,2), &wsa) != 0) {
        fprintf(stderr, "WSAStartup failed\n");
        return 1;
    }
#endif

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return 1;
    }

    struct sockaddr_in servaddr, cliaddr;
    socklen_t len = sizeof(cliaddr);
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(NTP_PORT);

    if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind");
        CLOSESOCK(sockfd);
        return 1;
    }

    printf("NTP server listening on UDP port %d...\n", NTP_PORT);

    ntp_packet packet;
    while (1) {
        memset(&packet, 0, sizeof(packet));
        if (recvfrom(sockfd, (char *)&packet, sizeof(packet), 0,
                     (struct sockaddr *)&cliaddr, &len) < 0) {
            perror("recvfrom");
            continue;
        }

        log_request(&cliaddr);

        ntp_packet reply;
        memset(&reply, 0, sizeof(reply));
        reply.li_vn_mode = (0 << 6) | (4 << 3) | 4; // LI=0, VN=4, Mode=4 (server)
        reply.stratum = 1;
        reply.poll = 4;
        reply.precision = -20;

#ifdef _WIN32
		get_ntp_time(&reply.ref_ts_sec, &reply.ref_ts_frac);
        get_ntp_time(&reply.recv_ts_sec, &reply.recv_ts_frac);
        get_ntp_time(&reply.tx_ts_sec, &reply.tx_ts_frac);
#else
        struct timeval tv;
        gettimeofday(&tv, NULL);
    		timeval_to_ntp(&tv, &reply.ref_ts_sec, &reply.ref_ts_frac);
        reply.orig_ts_sec = packet.tx_ts_sec;
        reply.orig_ts_frac = packet.tx_ts_frac;
        timeval_to_ntp(&tv, &reply.recv_ts_sec, &reply.recv_ts_frac);
        timeval_to_ntp(&tv, &reply.tx_ts_sec, &reply.tx_ts_frac);
#endif
        

        if (sendto(sockfd, (char *)&reply, sizeof(reply), 0,
                   (struct sockaddr *)&cliaddr, len) < 0) {
            perror("sendto");
        }
    }

    CLOSESOCK(sockfd);
#ifdef _WIN32
    WSACleanup();
#endif
    return 0;
}