Skip to content

Instantly share code, notes, and snippets.

@Torstein-Eide
Created December 28, 2025 12:56
Show Gist options
  • Select an option

  • Save Torstein-Eide/141a5819c0664a1655b3d3635387041f to your computer and use it in GitHub Desktop.

Select an option

Save Torstein-Eide/141a5819c0664a1655b3d3635387041f to your computer and use it in GitHub Desktop.
Simple Network Time Protocol (SNTP) example (Header Processing, error handling, verbose output, updates linux server)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <errno.h>
#define NTP_TIMESTAMP_DELTA 2208988800ull //Diff btw a UNIX timestamp (Starting Jan, 1st 1970) and a NTP timestamp (Starting Jan, 1st 1900)
#define NTP_FRAC_SCALE 4294967296.0 //2^32
// NTP packet structure
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 trans_ts_sec;
uint32_t trans_ts_frac;
} ntp_packet;
// helper function for converting NTP format to double
double ntp_to_double(uint32_t sec, uint32_t frac) {
return (double)(ntohl(sec) - NTP_TIMESTAMP_DELTA) + (double)ntohl(frac) / NTP_FRAC_SCALE;
}
// helper function for converting UTC time to NTP format
void double_to_ntp(double t, uint32_t *sec, uint32_t *frac) {
*sec = htonl((uint32_t)(t + NTP_TIMESTAMP_DELTA));
double f = (t - (uint32_t)t) * NTP_FRAC_SCALE;
*frac = htonl((uint32_t)f);
}
void format_time(double t, char* buf) {
time_t now = (time_t)t;
struct tm *tm_info = gmtime(&now);
double fraction = t - (double)now;
strftime(buf, 20, "%Y-%m-%d %H:%M:%S", tm_info);
sprintf(buf + 19, ".%06d UTC", (int)(fraction * 1000000));
}
int main() {
const char *hostname = "192.168.2.1";
int sockfd;
ntp_packet packet;
struct hostent *server;
struct sockaddr_in serv_addr;
char time_buf[64];
printf("[INFO] SNTP client starting\n");
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) { fprintf(stderr, "Error creating socket\n"); exit(1); }
server = gethostbyname(hostname);
if (!server) { fprintf(stderr, "Error lookup\n"); exit(1); }
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
memcpy(&serv_addr.sin_addr.s_addr, server->h_addr_list[0], server->h_length); // Fixed: Use h_addr_list[0]
serv_addr.sin_port = htons(123);
printf("[INFO] Server : %s (%s)\n", hostname, inet_ntoa(*(struct in_addr*)server->h_addr_list[0])); // Fixed: Use h_addr_list[0]
printf("[INFO] Mode : client (3)\n");
printf("[INFO] Version : NTPv4\n");
memset(&packet, 0, sizeof(ntp_packet));
packet.li_vn_mode = 0x23; // LI=0, VN=4, Mode=3
// T1: Originate Timestamp (Client side)
struct timespec t1_spec;
clock_gettime(CLOCK_REALTIME, &t1_spec);
double t1 = (double)t1_spec.tv_sec + (double)t1_spec.tv_nsec / 1e9;
double_to_ntp(t1, &packet.orig_ts_sec, &packet.orig_ts_frac);
format_time(t1, time_buf);
printf("[INFO] Originate time T1 : %s\n", time_buf);
printf("[INFO] Packet sent, waiting for reply...\n");
if (sendto(sockfd, &packet, sizeof(ntp_packet), 0, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
fprintf(stderr, "Error sending packet\n"); exit(1);
}
// Set receive timeout to 5 seconds
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
socklen_t len = sizeof(serv_addr);
if (recvfrom(sockfd, &packet, sizeof(ntp_packet), 0, (struct sockaddr *)&serv_addr, &len) < 0) {
if (errno == EWOULDBLOCK || errno == ETIMEDOUT) {
fprintf(stderr, "[ERROR] Timeout waiting for NTP reply\n");
} else {
fprintf(stderr, "[ERROR] Error receiving packet\n");
}
exit(2);
}
close(sockfd);
// Check for Kiss-of-Death
if (packet.stratum == 0) {
printf("[ERROR] Kiss-of-Death received: %.4s\n", (char*)&packet.ref_id);
close(sockfd);
exit(1);
}
// T4: Destination Timestamp (Client side)
struct timespec t4_spec;
clock_gettime(CLOCK_REALTIME, &t4_spec);
double t4 = (double)t4_spec.tv_sec + (double)t4_spec.tv_nsec / 1e9;
printf("[INFO] Packet received\n\n");
printf("[HEADER]\n");
printf(" LI : %d\n", packet.li_vn_mode >> 6);
printf(" VN : %d\n", (packet.li_vn_mode >> 3) & 7);
printf(" Mode : %d\n", packet.li_vn_mode & 7);
printf(" Stratum : %d\n", packet.stratum);
printf(" Poll : %d\n", packet.poll);
printf(" Precision : %d\n", packet.precision);
if (packet.stratum == 1) {
printf(" Ref ID : %.4s\n", (char*)&packet.ref_id);
} else {
struct in_addr ref_addr;
ref_addr.s_addr = packet.ref_id;
printf(" Ref ID : %s\n", inet_ntoa(ref_addr));
}
double t2 = ntp_to_double(packet.recv_ts_sec, packet.recv_ts_frac);
double t3 = ntp_to_double(packet.trans_ts_sec, packet.trans_ts_frac);
printf("\n[TIMESTAMPS]\n");
format_time(t1, time_buf); printf(" T1 Originate : %s\n", time_buf);
format_time(t2, time_buf); printf(" T2 Receive : %s\n", time_buf);
format_time(t3, time_buf); printf(" T3 Transmit : %s\n", time_buf);
format_time(t4, time_buf); printf(" T4 Destination: %s\n", time_buf);
double delay = (t4 - t1) - (t3 - t2);
double offset = ((t2 - t1) + (t3 - t4)) / 2.0;
printf("\n[CALCULATION]\n");
printf(" Round-trip delay : %f s\n", delay);
printf(" Clock offset : %f s\n", offset);
// Update system time
printf("\n[ACTION]\n");
struct timeval tv;
gettimeofday(&tv, NULL);
double total_offset = offset;
tv.tv_sec += (time_t)total_offset;
tv.tv_usec += (suseconds_t)((total_offset - (time_t)total_offset) * 1000000);
if (tv.tv_usec >= 1000000) {
tv.tv_sec += 1;
tv.tv_usec -= 1000000;
} else if (tv.tv_usec < 0) {
tv.tv_sec -= 1;
tv.tv_usec += 1000000;
}
if (settimeofday(&tv, NULL) == -1) {
perror("Failed to set system time");
printf(" System time update failed (requires root privileges)\n");
return 1;
} else {
printf(" System time updated successfully\n");
return 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment