1 /**
2 * collectd - src/ntpd.c
3 * Copyright (C) 2006 Florian octo Forster
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; only version 2 of the License is applicable.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 * Authors:
19 * Florian octo Forster <octo at verplant.org>
20 **/
22 #include "collectd.h"
23 #include "common.h"
24 #include "plugin.h"
25 #include "configfile.h"
27 #include "ntp_request.h"
29 #define MODULE_NAME "ntpd"
31 #if HAVE_SYS_SOCKET_H
32 # define NTPD_HAVE_READ 1
33 #else
34 # define NTPD_HAVE_READ 0
35 #endif
37 #if HAVE_NETDB_H
38 # include <netdb.h>
39 #endif
40 #if HAVE_SYS_SOCKET_H
41 # include <sys/socket.h>
42 #endif
43 #if HAVE_NETINET_IN_H
44 # include <netinet/in.h>
45 #endif
46 #if HAVE_NETINET_TCP_H
47 # include <netinet/tcp.h>
48 #endif
50 /* drift */
51 static char *time_offset_file = "ntpd/time_offset-%s.rrd";
52 static char *time_offset_ds_def[] =
53 {
54 "DS:ms:GAUGE:"COLLECTD_HEARTBEAT":0:100",
55 NULL
56 };
57 static int time_offset_ds_num = 1;
59 static char *frequency_offset_file = "ntpd/frequency_offset-%s.rrd";
60 static char *frequency_offset_ds_def[] =
61 {
62 "DS:ppm:GAUGE:"COLLECTD_HEARTBEAT":0:100",
63 NULL
64 };
65 static int frequency_offset_ds_num = 1;
67 #if NTPD_HAVE_READ
68 # define NTPD_DEFAULT_HOST "localhost"
69 # define NTPD_DEFAULT_PORT "123"
70 static int sock_descr = -1;
71 static char *ntpd_host = NULL;
72 static char *ntpd_port = NULL;
73 #endif
75 static void ntpd_init (void)
76 {
77 return;
78 }
80 static void ntpd_write (char *host, char *inst, char *val)
81 {
82 rrd_update_file (host, ntpd_file, val, ds_def, ds_num);
83 }
85 #if NTPD_HAVE_READ
86 static void ntpd_submit (double snum, double mnum, double lnum)
87 {
88 char buf[256];
90 if (snprintf (buf, 256, "%u:%.2f:%.2f:%.2f", (unsigned int) curtime,
91 snum, mnum, lnum) >= 256)
92 return;
94 plugin_submit (MODULE_NAME, "-", buf);
95 }
97 static void ntpd_connect (void)
98 {
99 char *host;
100 char *port;
102 struct addrinfo ai_hints;
103 struct addrinfo *ai_list;
104 struct addrinfo *ai_ptr;
105 int status;
107 if (sock_descr >= 0)
108 return (sock_descr);
110 host = ntpd_host;
111 if (host == NULL)
112 host = NTPD_DEFAULT_HOST;
114 port = ntpd_port;
115 if (port == NULL)
116 port = NTPD_DEFAULT_PORT;
118 memset (&ai_hints, '\0', sizeof (ai_hints));
119 ai_hints.ai_flags = AI_ADDRCONFIG;
120 ai_hints.ai_family = PF_UNSPEC;
121 ai_hints.ai_socktype = SOCK_DGRAM;
122 ai_hints.ai_protocol = IPPROTO_UDP;
124 if ((status = getaddrinfo (host, port, &ai_hints, &ai_list)) != 0)
125 {
126 syslog (LOG_ERR, "ntpd plugin: getaddrinfo (%s, %s): %s",
127 host, port,
128 status == EAI_SYSTEM ? strerror (errno) : gai_strerror (status));
129 return (-1);
130 }
132 for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
133 {
134 /* create our socket descriptor */
135 if ((sock_descr = socket (ai_ptr->ai_family,
136 ai_ptr->ai_socktype,
137 ai_ptr->ai_protocol)) < 0)
138 continue;
140 /* connect to the ntpd */
141 if (connect (sock_descr, ai_ptr->ai_addr, ai_ptr->ai_addrlen))
142 {
143 close (sock_descr);
144 sock_descr = -1;
145 continue;
146 }
148 break;
149 }
151 freeaddrinfo (ai_list);
153 if (sock_descr < 0)
154 syslog (LOG_ERR, "ntpd plugin: Unable to connect to server.");
156 return (sock_descr);
157 }
159 /* For a description of the arguments see `ntpd_do_query' below. */
160 static int ntpd_receive_response (int req_code, int *res_items, int *res_size,
161 char **res_data, int res_item_size)
162 {
163 int sd;
164 struct resp_pkt res;
165 ssize_t status;
166 int done;
168 char *items;
169 size_t items_num;
171 int pkt_item_num; /* items in this packet */
172 int pkt_item_len; /* size of the items in this packet */
173 int pkt_sequence;
174 char pkt_recvd[MAXSEQ+1]; /* sequence numbers that have been received */
175 int pkt_recvd_num; /* number of packets that have been received */
176 int pkt_lastseq; /* the last sequence number */
177 ssize_t pkt_padding; /* Padding in this packet */
179 if ((sd = ntpd_connect ()) < 0)
180 return (-1);
182 items = NULL;
183 items_num = 0;
185 memset (pkt_recvd, '\0', sizeof (pkt_recvd));
186 pkt_recvd_num = 0;
187 pkt_lastseq = -1;
189 *res_items = 0;
190 *res_size = 0;
191 *res_data = NULL;
193 done = 0;
194 while (done == 0)
195 {
196 /* TODO calculate time */
197 /* TODO poll(2) */
199 memset ((void *) &res, '\0', sizeof (res));
200 status = recv (sd, (void *) &res, sizeof (res), 0 /* no flags */);
202 if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
203 continue;
205 if (status < 0)
206 return (-1);
208 /*
209 * Do some sanity checks first
210 */
211 if (status < RESP_HEADER_SIZE)
212 {
213 syslog (LOG_WARNING, "ntpd plugin: Short (%i bytes) packet received",
214 (int) status);
215 continue;
216 }
217 if (INFO_MODE (res.rm_vn_mode) != MODE_PRIVATE)
218 {
219 syslog (LOG_NOTICE, "ntpd plugin: Packet received with mode %i",
220 INFO_MODE (res.rm_vn_mode));
221 continue;
222 }
223 if (INFO_IS_AUTH (res.auth_seq))
224 {
225 syslog (LOG_NOTICE, "ntpd plugin: Encrypted packet received");
226 continue;
227 }
228 if (!ISRESPONSE (res.rm_vn_mode))
229 {
230 syslog (LOG_NOTICE, "ntpd plugin: Received request packet, "
231 "wanted response");
232 continue;
233 }
234 if (INFO_MBZ (res.mbz_itemsize))
235 {
236 syslog (LOG_WARNING, "ntpd plugin: Received packet with nonzero "
237 "MBZ field!");
238 continue;
239 }
240 if (res.implementation != req_code)
241 {
242 syslog (LOG_WARNING, "ntpd plugin: Asked for request of type %i, "
243 "got %i", (int) req_code, (int) res.implementation);
244 continue;
245 }
247 /* Check for error code */
248 if (INFO_ERR (res.err_nitems) != INFO_OKAY)
249 {
250 syslog (LOG_ERR, "ntpd plugin: Received error code %i",
251 (int) INFO_ERR(res.err_nitems));
252 return ((int) INFO_ERR (res.err_nitems));
253 }
255 /* extract number of items in this packet and the size of these items */
256 pkt_item_num = INFO_NITEMS (res.err_nitems);
257 pkt_item_len = INFO_ITEMSIZE (res.mbz_itemsize);
259 /* Check if the reported items fit in the packet */
260 if ((pkt_item_num * pkt_item_len) > (status - RESP_HEADER_SIZE))
261 {
262 syslog (LOG_ERR, "ntpd plugin: %i items * %i bytes > "
263 "%i bytes - %i bytes header",
264 (int) pkt_item_num, (int) pkt_item_len,
265 (int) status, (int) RESP_HEADER_SIZE);
266 continue;
267 }
269 /* If this is the first packet (time wise, not sequence wise),
270 * set `res_size'. If it's not the first packet check if the
271 * items have the same size. Discard invalid packets. */
272 if (items_num == 0) /* first packet */
273 {
274 *res_size = pkt_item_len;
275 }
276 else if (*res_size != pkt_item_len)
277 {
278 syslog (LOG_ERR, "Item sizes differ.");
279 continue;
280 }
282 /* Calculate the padding. No idea why there might be any padding.. */
283 pkt_padding = 0;
284 if (res_item_size > pkt_item_len)
285 pkt_padding = res_item_size - pkt_item_len;
287 /* Extract the sequence number */
288 pkt_sequence = INFO_SEQ (res.auth_seq);
289 if ((pkt_sequence < 0) || (pkt_sequence > MAXSEQ))
290 {
291 syslog (LOG_ERR, "ntpd plugin: Received packet with sequence %i",
292 pkt_sequence);
293 continue;
294 }
296 /* Check if this sequence has been received before. If so, discard it. */
297 if (pkt_recvd[pkt_sequence] != '\0')
298 {
299 syslog (LOG_NOTICE, "ntpd plugin: Sequence %i received twice",
300 pkt_sequence);
301 continue;
302 }
304 /* If `pkt_lastseq != -1' another packet without `more bit' has
305 * been received. */
306 if (!ISMORE (res.rm_vn_mode))
307 {
308 if (pkt_lastseq != -1)
309 {
310 syslog (LOG_ERR, "ntpd plugin: Two packets which both "
311 "claim to be the last one in the "
312 "sequence have been received.");
313 continue;
314 }
315 pkt_lastseq = pkt_sequence;
316 }
318 /*
319 * Enough with the checks. Copy the data now.
320 * We start by allocating some more memory.
321 */
322 items = realloc ((void *) *res_data,
323 (items_num + pkt_item_num) * res_item_size);
324 if (items == NULL)
325 {
326 items = *res_data;
327 syslog (LOG_ERR, "ntpd plugin: realloc failed.");
328 continue;
329 }
330 *res_data = items;
332 for (i = 0; i < pkt_item_num; i++)
333 {
334 void *dst = (void *) (*res_data + ((*res_items) * res_item_size));
335 void *src = (void *) (((char *) res.data) + (i * pkt_item_len));
337 /* Set the padding to zeros */
338 if (pkt_padding != 0)
339 memset (dst, '\0', res_item_size);
340 memcpy (dst, src, (size_t) pkt_item_len);
342 (*res_items)++;
343 }
345 pkt_recvd[pkt_sequence] = (char) 1;
346 pkt_recvd_num++;
348 if ((pkt_recvd_num - 1) == pkt_lastseq)
349 done = 1;
350 } /* while (done == 0) */
352 return (0);
353 }
355 /* For a description of the arguments see `ntpd_do_query' below. */
356 static int ntpd_send_request (int req_code, int req_items, int req_size, char *req_data)
357 {
358 int sd;
359 struct req_pkt req;
360 size_t req_data_len;
361 int status;
363 assert (req_items >= 0);
364 assert (req_size >= 0);
366 if ((sd = ntpd_connect ()) < 0)
367 return (-1);
369 memset ((void *) &req, '\0', sizeof (req));
370 req.rm_vn_mode = RM_VN_MODE(0, 0, 0);
371 req.auth_seq = AUTH_SEQ (0, 0);
372 req.implementation = IMPL_XNTPD;
373 req.request = (unsigned char) req_code;
375 req_data_len = (size_t) (req_items * req_size);
377 assert (((req_data != NULL) && (req_data_len > 0))
378 || ((req_data == NULL) && (req_items == 0) && (req_size == 0)));
380 req.err_nitems = ERR_NITEMS (0, qitems);
381 req.mbz_itemsize = MBZ_ITEMSIZE (qsize);
383 if (req_data != NULL)
384 memcpy ((void *) req.data, (const void *) req_data, req_data_len);
386 status = swrite (fd, (const char *) &req, REQ_LEN_NOMAC);
387 if (status < 0)
388 return (status);
390 return (0);
391 }
393 /*
394 * ntpd_do_query:
395 *
396 * req_code: Type of request packet
397 * req_items: Numver of items in the request
398 * req_size: Size of one item in the request
399 * req_data: Data of the request packet
400 * res_items: Pointer to where the number returned items will be stored.
401 * res_size: Pointer to where the size of one returned item will be stored.
402 * res_data: This is where a pointer to the (allocated) data will be stored.
403 * res_item_size: Size of one returned item. (used to calculate padding)
404 *
405 * returns: zero upon success, non-zero otherwise.
406 */
407 static int ntpd_do_query (int req_code, int req_items, int req_size, char *req_data,
408 int *res_items, int *res_size, char **res_data, int res_item_size)
409 {
410 int status;
412 status = ntpd_send_request (req_code, req_items, req_size, req_data);
413 if (status != 0)
414 return (status);
416 status = ntpd_receive_response (req_code, res_items, res_size, res_data,
417 res_item_size);
418 return (status);
419 }
421 static void ntpd_read (void)
422 {
423 static sd = -1;
424 struct req_pkt req;
426 ntpd_connect (&sd);
427 if (sd < 0)
428 return;
430 memset ((void *) &req, '\0', sizeof (req));
431 req.rm_vn_mode = RM_VN_MODE (0, 0, 0); /* response, more, version, mode */
432 req.implementation = IMPL_XNTPD;
433 }
434 #else
435 # define ntpd_read NULL
436 #endif /* NTPD_HAVE_READ */
438 void module_register (void)
439 {
440 plugin_register (MODULE_NAME, ntpd_init, ntpd_read, ntpd_write);
441 }
443 #undef MODULE_NAME