// ntp_server.c // Minimal NTP server, cross-platform (Linux/Windows) #include #include #include #include #include #include #ifdef _WIN32 #include #include #include #include #include //#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 #include #include #include #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; }