Created
December 28, 2025 12:56
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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