ntp-tools
/
ntp-server-minimal.c
/
1 contributor
// 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;
}