scratch/protocols/ntp.c
2024-12-26 14:02:40 -03:00

153 lines
4.1 KiB
C

// NTP client simple enough to print the current date and time in ctime() and ISO 8601 format
// https://datatracker.ietf.org/doc/html/rfc5905
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define NTP_PORT 123
#define _INT2STR(x) #x
#define MACRO_INT2STR(x) _INT2STR(x)
#define NTP_PORT_STR MACRO_INT2STR(NTP_PORT)
//const uint8_t NTP_PORT = 123;
const uint64_t NTP_UNIX_DELTA = 2208988800ull; // January 1, 1970 in NTP timestamp format
const uint8_t NTP_CURRENT_VER = 4;
struct ntp_packet
{
uint8_t leap_ver_mode;
uint8_t stratum;
uint8_t poll;
uint8_t precision;
uint32_t root_delay;
uint32_t root_dispersion;
uint32_t ref_id;
uint32_t ref_tstamp_sec;
uint32_t ref_tstamp_frac;
uint32_t origin_tstamp_sec;
uint32_t origin_tstamp_frac;
uint32_t rx_tstamp_sec;
uint32_t rx_tstamp_frac;
uint32_t tx_tstamp_sec;
uint32_t tx_tstamp_frac;
};
//#include <sys/time.h>
//int set_system_date(time_t date)
//{
// if (settimeofday(&(struct timeval){.tv_sec = date, .tv_usec = 0}, NULL) < 0) {
// perror("settimeofday");
// return 1;
// }
// return 0;
//}
int main(const int argc, const char **argv)
{
if (argc < 2) {
fprintf(stderr, "usage: ./ntp [ntp server hostname]\n");
return 1;
}
int ntp_server_fd;
// create UDP connection
if ((ntp_server_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror("socket");
return 1;
}
const struct timeval time_val = {.tv_sec = 4, .tv_usec = 0};
if (setsockopt(ntp_server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &(int){1}, sizeof(int)) < 0
|| setsockopt(ntp_server_fd, SOL_SOCKET, SO_RCVTIMEO, &time_val, sizeof(struct timeval)) < 0
|| setsockopt(ntp_server_fd, SOL_SOCKET, SO_SNDTIMEO, &time_val, sizeof(struct timeval)) < 0
) {
perror("setsockopt");
return 1;
}
struct addrinfo *res;
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_DGRAM,
.ai_protocol = IPPROTO_UDP,
.ai_flags = 0,
};
int ret_val = 0;
if ((ret_val = getaddrinfo(argv[1], MACRO_INT2STR(NTP_PORT), &hints, &res)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret_val));
return 1;
}
for (; res != NULL; res = res->ai_next) {
//char h[NI_MAXHOST] = {0};
//getnameinfo(res->ai_addr, res->ai_addrlen, h, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
//printf("getaddrinfo ip: %s\n", h);
if (connect(ntp_server_fd, res->ai_addr, res->ai_addrlen) < 0) {
perror("connect");
freeaddrinfo(res);
return 1;
} else { // if zero, we already connected
break;
}
}
freeaddrinfo(res);
struct ntp_packet ntp_pk = {
.leap_ver_mode = 0x23, // leap = 0, vn = 4, mode = 3 (client)
};
// send packet
if (send(ntp_server_fd, &ntp_pk, sizeof(struct ntp_packet), 0) < 0) {
perror("send");
return 1;
}
// receive answer packet from the server
if (recv(ntp_server_fd, &ntp_pk, sizeof(struct ntp_packet), 0) < 0) {
perror("recv");
return 1;
}
// close connection with the server
shutdown(ntp_server_fd, SHUT_RDWR);
close(ntp_server_fd);
ntp_pk.leap_ver_mode = ntohs(ntp_pk.leap_ver_mode);
ntp_pk.stratum = ntohs(ntp_pk.stratum);
//ntp_pk.ref_id = ntohl(ntp_pk.ref_id);
printf("NTP version: %u\n", ntp_pk.leap_ver_mode);
printf("stratum: %u\n", ntp_pk.stratum);
if ((ntp_pk.leap_ver_mode & 0x38) >= NTP_CURRENT_VER) {
fprintf(stderr, "invalid version number, got %u", ntp_pk.leap_ver_mode & 0x38);
return 1;
}
// check if it is a KoD packet
if (ntp_pk.stratum == 0) {
char kod_msg[5] = {0};
memcpy(&kod_msg, &ntp_pk.ref_id, 4);
printf("KoD msg: %s\n", kod_msg);
}
ntp_pk.tx_tstamp_sec = ntohl(ntp_pk.tx_tstamp_sec);
ntp_pk.tx_tstamp_frac = ntohl(ntp_pk.tx_tstamp_frac);
time_t ntp_time = (time_t)(ntp_pk.tx_tstamp_sec - NTP_UNIX_DELTA);
char fmt_time[64] = {0};
strftime(fmt_time, sizeof(fmt_time), "%Y-%m-%d %H:%M:%S%z", localtime(&ntp_time));
printf("Server NTP timestamp: %u (frac): %u\n", ntp_pk.tx_tstamp_sec, ntp_pk.tx_tstamp_frac);
printf("Server NTP time (ctime fmt'd): %s", ctime(&ntp_time));
printf("Server NTP time (ISO 8601): %s\n", fmt_time);
return 0;
}