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