Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1-only */
2 : /**
3 : * Copyright (C) 2021 Wook Song <wook16.song@samsung.com>
4 : */
5 : /**
6 : * @file ntputil.c
7 : * @date 16 Jul 2021
8 : * @brief NTP utility functions
9 : * @see https://github.com/nnstreamer/nnstreamer
10 : * @author Wook Song <wook16.song@samsung.com>
11 : * @bug No known bugs except for NYI items
12 : * @todo Need to support caching and polling timer mechanism
13 : */
14 :
15 : #include <errno.h>
16 : #include <arpa/inet.h>
17 : #include <netdb.h>
18 : #include <stdint.h>
19 : #include <stdio.h>
20 : #include <string.h>
21 : #include <time.h>
22 : #include <unistd.h>
23 :
24 : #include "ntputil.h"
25 :
26 : /**
27 : *******************************************************************
28 : * NTP Timestamp Format (https://www.ietf.org/rfc/rfc5905.txt p.12)
29 : * 0 1 2 3
30 : * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
31 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32 : * | Seconds |
33 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
34 : * | Fraction |
35 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
36 : *******************************************************************
37 : */
38 : /**
39 : * @brief A custom data type to represent NTP timestamp format
40 : */
41 : typedef struct _ntp_timestamp_t
42 : {
43 : uint32_t sec;
44 : uint32_t frac;
45 : } ntp_timestamp_t;
46 :
47 : /**
48 : *******************************************************************
49 : * NTP Packet Header Format (https://www.ietf.org/rfc/rfc5905.txt p.18)
50 : * 0 1 2 3
51 : * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
52 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
53 : * |LI | VN |Mode | Stratum | Poll | Precision |
54 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
55 : * | Root Delay |
56 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
57 : * | Root Dispersion |
58 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59 : * | Reference ID |
60 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
61 : * | |
62 : * + Reference Timestamp (64) +
63 : * | |
64 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
65 : * | |
66 : * + Origin Timestamp (64) +
67 : * | |
68 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
69 : * | |
70 : * + Receive Timestamp (64) +
71 : * | |
72 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
73 : * | |
74 : * + Transmit Timestamp (64) +
75 : * | |
76 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
77 : * | |
78 : * . .
79 : * . Extension Field 1 (variable) .
80 : * . .
81 : * | |
82 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
83 : * | |
84 : * . .
85 : * . Extension Field 2 (variable) .
86 : * . .
87 : * | |
88 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
89 : * | Key Identifier |
90 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
91 : * | |
92 : * | dgst (128) |
93 : * | |
94 : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
95 : *******************************************************************
96 : */
97 :
98 : /**
99 : * @brief A custom data type to represent NTP packet header format
100 : */
101 : typedef struct _ntp_packet_t
102 : {
103 : uint8_t li_vn_mode;
104 : uint8_t stratum;
105 : uint8_t poll;
106 : uint8_t precision;
107 : uint32_t root_delay;
108 : uint32_t root_dispersion;
109 : uint32_t ref_id;
110 : ntp_timestamp_t ref_ts;
111 : ntp_timestamp_t org_ts;
112 : ntp_timestamp_t recv_ts;
113 : ntp_timestamp_t xmit_ts;
114 : } ntp_packet_t;
115 :
116 : const uint64_t NTPUTIL_TIMESTAMP_DELTA = 2208988800ULL;
117 : const double NTPUTIL_MAX_FRAC_DOUBLE = 4294967295.0L;
118 : const int64_t NTPUTIL_SEC_TO_USEC_MULTIPLIER = 1000000;
119 : const char NTPUTIL_DEFAULT_HNAME[] = "pool.ntp.org";
120 : const uint16_t NTPUTIL_DEFAULT_PORT = 123;
121 :
122 : /**
123 : * @brief Wrapper function of ntohl.
124 : */
125 : uint32_t
126 0 : _convert_to_host_byte_order (uint32_t in)
127 : {
128 0 : return ntohl (in);
129 : }
130 :
131 : /**
132 : * @brief Get NTP timestamps from the given or public NTP servers
133 : * @param[in] hnums A number of hostname and port pairs. If 0 is given,
134 : * the NTP server pool will be used.
135 : * @param[in] hnames A list of hostname
136 : * @param[in] ports A list of port
137 : * @return an Unix epoch time as microseconds on success,
138 : * negative values on error
139 : */
140 : int64_t
141 6 : ntputil_get_epoch (uint32_t hnums, char **hnames, uint16_t * ports)
142 : {
143 : struct sockaddr_in serv_addr;
144 6 : struct hostent *srv = NULL;
145 6 : struct hostent *default_srv = NULL;
146 6 : uint16_t port = -1;
147 6 : int32_t sockfd = -1;
148 : uint32_t i;
149 : int64_t ret;
150 :
151 6 : sockfd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
152 6 : if (sockfd < 0) {
153 0 : ret = -1;
154 0 : goto ret_normal;
155 : }
156 :
157 6 : for (i = 0; i < hnums; ++i) {
158 1 : srv = gethostbyname (hnames[i]);
159 1 : if (srv != NULL) {
160 1 : port = ports[i];
161 1 : break;
162 : }
163 : }
164 :
165 6 : if (srv == NULL) {
166 5 : default_srv = gethostbyname (NTPUTIL_DEFAULT_HNAME);
167 5 : if (default_srv == NULL) {
168 1 : ret = -h_errno;
169 1 : goto ret_close_sockfd;
170 : }
171 4 : srv = default_srv;
172 4 : port = NTPUTIL_DEFAULT_PORT;
173 : }
174 :
175 5 : memset (&serv_addr, 0, sizeof (serv_addr));
176 5 : serv_addr.sin_family = AF_INET;
177 5 : memcpy ((uint8_t *) & serv_addr.sin_addr.s_addr,
178 5 : (uint8_t *) srv->h_addr_list[0], (size_t) srv->h_length);
179 5 : serv_addr.sin_port = htons (port);
180 :
181 5 : ret = connect (sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr));
182 5 : if (ret < 0) {
183 1 : ret = -errno;
184 1 : goto ret_close_sockfd;
185 : }
186 :
187 : {
188 : ntp_packet_t packet;
189 : uint32_t recv_sec;
190 : uint32_t recv_frac;
191 : double frac;
192 : ssize_t n;
193 :
194 4 : memset (&packet, 0, sizeof (packet));
195 :
196 : /* li = 0, vn = 3, mode = 3 */
197 4 : packet.li_vn_mode = 0x1B;
198 :
199 : /* Request */
200 4 : n = write (sockfd, &packet, sizeof (packet));
201 4 : if (n < 0) {
202 1 : ret = -errno;
203 3 : goto ret_close_sockfd;
204 : }
205 :
206 : /* Receive */
207 3 : n = read (sockfd, &packet, sizeof (packet));
208 3 : if (n < 0) {
209 1 : ret = -errno;
210 1 : goto ret_close_sockfd;
211 : }
212 :
213 : /**
214 : * @note ntp_timestamp_t recv_ts in ntp_packet_t means the timestamp as the packet
215 : * left the NTP server. 'sec' corresponds to the seconds passed since 1900
216 : * and 'frac' is needed to convert seconds to smaller units of a second
217 : * such as microsceonds. Note that the bit/byte order of those data should
218 : * be converted to the host's endianness.
219 : */
220 2 : recv_sec = _convert_to_host_byte_order (packet.xmit_ts.sec);
221 2 : recv_frac = _convert_to_host_byte_order (packet.xmit_ts.frac);
222 :
223 : /**
224 : * @note NTP uses an epoch of January 1, 1900 while the Unix epoch is
225 : * the number of seconds that have elapsed since January 1, 1970. For this
226 : * reason, we subtract 70 years worth of seconds from the seconds since 1900
227 : */
228 2 : if (recv_sec <= NTPUTIL_TIMESTAMP_DELTA) {
229 1 : ret = -1;
230 1 : goto ret_close_sockfd;
231 : }
232 :
233 1 : ret = (int64_t) (recv_sec - NTPUTIL_TIMESTAMP_DELTA);
234 1 : ret *= NTPUTIL_SEC_TO_USEC_MULTIPLIER;
235 1 : frac = ((double) recv_frac) / NTPUTIL_MAX_FRAC_DOUBLE;
236 1 : frac *= NTPUTIL_SEC_TO_USEC_MULTIPLIER;
237 :
238 1 : ret += (int64_t) frac;
239 : }
240 :
241 6 : ret_close_sockfd:
242 6 : close (sockfd);
243 :
244 6 : ret_normal:
245 6 : return ret;
246 : }
|