Added a call to `poll(2)' to the ntpd plugin. Also added some needed time calculation...
[collectd.git] / src / ntpd.c
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  **/
21
22 #include "collectd.h"
23 #include "common.h"
24 #include "plugin.h"
25 #include "configfile.h"
26
27 #define MODULE_NAME "ntpd"
28
29 #if HAVE_SYS_SOCKET_H
30 # define NTPD_HAVE_READ 1
31 #else
32 # define NTPD_HAVE_READ 0
33 #endif
34
35 #if HAVE_STDINT_H
36 # include <stdint.h>
37 #endif
38 #if HAVE_NETDB_H
39 # include <netdb.h>
40 #endif
41 #if HAVE_SYS_SOCKET_H
42 # include <sys/socket.h>
43 #endif
44 #if HAVE_NETINET_IN_H
45 # include <netinet/in.h>
46 #endif
47 #if HAVE_NETINET_TCP_H
48 # include <netinet/tcp.h>
49 #endif
50 #if HAVE_SYS_POLL_H
51 # include <sys/poll.h>
52 #endif
53
54 /* drift */
55 static char *time_offset_file = "ntpd/time_offset-%s.rrd";
56 static char *time_offset_ds_def[] =
57 {
58         "DS:ms:GAUGE:"COLLECTD_HEARTBEAT":0:100",
59         NULL
60 };
61 static int time_offset_ds_num = 1;
62
63 static char *frequency_offset_file = "ntpd/frequency_offset-%s.rrd";
64 static char *frequency_offset_ds_def[] =
65 {
66         "DS:ppm:GAUGE:"COLLECTD_HEARTBEAT":0:100",
67         NULL
68 };
69 static int frequency_offset_ds_num = 1;
70
71 #if NTPD_HAVE_READ
72 # define NTPD_DEFAULT_HOST "localhost"
73 # define NTPD_DEFAULT_PORT "123"
74 static int   sock_descr = -1;
75 static char *ntpd_host = NULL;
76 static char *ntpd_port = NULL;
77 #endif
78
79 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
80  * The following definitions were copied from the NTPd distribution  *
81  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
82 #define MAXFILENAME 128
83 #define MAXSEQ  127
84 #define MODE_PRIVATE 7
85 #define NTP_OLDVERSION ((u_char) 1) /* oldest credible version */
86 #define IMPL_XNTPD 3
87
88 /* This structure is missing the message authentication code, since collectd
89  * doesn't use it. */
90 struct req_pkt
91 {
92         uint8_t  rm_vn_mode;
93         uint8_t  auth_seq;
94         uint8_t  implementation;                /* implementation number */
95         uint8_t  request;                       /* request number */
96         uint16_t err_nitems;            /* error code/number of data items */
97         uint16_t mbz_itemsize;          /* item size */
98         char     data[MAXFILENAME + 48];        /* data area [32 prev](176 byte max) */
99                                         /* struct conf_peer must fit */
100 };
101 #define REQ_LEN_NOMAC (sizeof(struct req_pkt))
102
103 /*
104  * A response packet.  The length here is variable, this is a
105  * maximally sized one.  Note that this implementation doesn't
106  * authenticate responses.
107  */
108 #define RESP_HEADER_SIZE        (8)
109 #define RESP_DATA_SIZE          (500)
110
111 struct resp_pkt
112 {
113         uint8_t  rm_vn_mode;           /* response, more, version, mode */
114         uint8_t  auth_seq;             /* key, sequence number */
115         uint8_t  implementation;       /* implementation number */
116         uint8_t  request;              /* request number */
117         uint16_t err_nitems;           /* error code/number of data items */
118         uint16_t mbz_itemsize;         /* item size */
119         char     data[RESP_DATA_SIZE]; /* data area */
120 };
121
122 /*
123  * Bit setting macros for multifield items.
124  */
125 #define RESP_BIT        0x80
126 #define MORE_BIT        0x40
127
128 #define ISRESPONSE(rm_vn_mode)  (((rm_vn_mode)&RESP_BIT)!=0)
129 #define ISMORE(rm_vn_mode)      (((rm_vn_mode)&MORE_BIT)!=0)
130 #define INFO_VERSION(rm_vn_mode) ((u_char)(((rm_vn_mode)>>3)&0x7))
131 #define INFO_MODE(rm_vn_mode)   ((rm_vn_mode)&0x7)
132
133 #define RM_VN_MODE(resp, more, version)         \
134                                 ((u_char)(((resp)?RESP_BIT:0)\
135                                 |((more)?MORE_BIT:0)\
136                                 |((version?version:(NTP_OLDVERSION+1))<<3)\
137                                 |(MODE_PRIVATE)))
138
139 #define INFO_IS_AUTH(auth_seq)  (((auth_seq) & 0x80) != 0)
140 #define INFO_SEQ(auth_seq)      ((auth_seq)&0x7f)
141 #define AUTH_SEQ(auth, seq)     ((u_char)((((auth)!=0)?0x80:0)|((seq)&0x7f)))
142
143 #define INFO_ERR(err_nitems)    ((u_short)((ntohs(err_nitems)>>12)&0xf))
144 #define INFO_NITEMS(err_nitems) ((u_short)(ntohs(err_nitems)&0xfff))
145 #define ERR_NITEMS(err, nitems) (htons((u_short)((((u_short)(err)<<12)&0xf000)\
146                                 |((u_short)(nitems)&0xfff))))
147
148 #define INFO_MBZ(mbz_itemsize)  ((ntohs(mbz_itemsize)>>12)&0xf)
149 #define INFO_ITEMSIZE(mbz_itemsize)     ((u_short)(ntohs(mbz_itemsize)&0xfff))
150 #define MBZ_ITEMSIZE(itemsize)  (htons((u_short)(itemsize)))
151 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
152  * End of the copied stuff..                                         *
153  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
154
155 static void ntpd_init (void)
156 {
157         return;
158 }
159
160 static void ntpd_write (char *host, char *inst, char *val)
161 {
162         rrd_update_file (host, time_offset_file, val,
163                         time_offset_ds_def, time_offset_ds_num); /* FIXME filename incorrect */
164 }
165
166 #if NTPD_HAVE_READ
167 static void ntpd_submit (double snum, double mnum, double lnum)
168 {
169         char buf[256];
170
171         if (snprintf (buf, 256, "%u:%.2f:%.2f:%.2f", (unsigned int) curtime,
172                                 snum, mnum, lnum) >= 256)
173                 return;
174
175         plugin_submit (MODULE_NAME, "-", buf);
176 }
177
178 /* returns `tv0 - tv1' in milliseconds or 0 if `tv1 > tv0' */
179 static int timeval_sub (const struct timeval *tv0, const struct timeval *tv1)
180 {
181         int sec;
182         int usec;
183
184         if ((tv0->tv_sec < tv1->tv_sec)
185                         || ((tv0->tv_sec == tv1->tv_sec) && (tv0->tv_usec < tv1->tv_usec)))
186                 return (0);
187
188         sec  = tv0->tv_sec  - tv1->tv_sec;
189         usec = tv0->tv_usec - tv1->tv_usec;
190
191         while (usec < 0)
192         {
193                 usec += 1000000;
194                 sec  -= 1;
195         }
196
197         if (sec < 0)
198                 return (0);
199
200         return ((sec * 1000) + ((usec + 500) / 1000));
201 }
202
203 static int ntpd_connect (void)
204 {
205         char *host;
206         char *port;
207
208         struct addrinfo  ai_hints;
209         struct addrinfo *ai_list;
210         struct addrinfo *ai_ptr;
211         int              status;
212
213         if (sock_descr >= 0)
214                 return (sock_descr);
215
216         host = ntpd_host;
217         if (host == NULL)
218                 host = NTPD_DEFAULT_HOST;
219
220         port = ntpd_port;
221         if (port == NULL)
222                 port = NTPD_DEFAULT_PORT;
223
224         memset (&ai_hints, '\0', sizeof (ai_hints));
225         ai_hints.ai_flags    = AI_ADDRCONFIG;
226         ai_hints.ai_family   = PF_UNSPEC;
227         ai_hints.ai_socktype = SOCK_DGRAM;
228         ai_hints.ai_protocol = IPPROTO_UDP;
229
230         if ((status = getaddrinfo (host, port, &ai_hints, &ai_list)) != 0)
231         {
232                 syslog (LOG_ERR, "ntpd plugin: getaddrinfo (%s, %s): %s",
233                                 host, port,
234                                 status == EAI_SYSTEM ? strerror (errno) : gai_strerror (status));
235                 return (-1);
236         }
237
238         for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
239         {
240                 /* create our socket descriptor */
241                 if ((sock_descr = socket (ai_ptr->ai_family,
242                                                 ai_ptr->ai_socktype,
243                                                 ai_ptr->ai_protocol)) < 0)
244                         continue;
245
246                 /* connect to the ntpd */
247                 if (connect (sock_descr, ai_ptr->ai_addr, ai_ptr->ai_addrlen))
248                 {
249                         close (sock_descr);
250                         sock_descr = -1;
251                         continue;
252                 }
253
254                 break;
255         }
256
257         freeaddrinfo (ai_list);
258
259         if (sock_descr < 0)
260                 syslog (LOG_ERR, "ntpd plugin: Unable to connect to server.");
261
262         return (sock_descr);
263 }
264
265 /* For a description of the arguments see `ntpd_do_query' below. */
266 static int ntpd_receive_response (int req_code, int *res_items, int *res_size,
267                 char **res_data, int res_item_size)
268 {
269         int              sd;
270         struct pollfd    poll_s;
271         struct resp_pkt  res;
272         int              status;
273         int              done;
274         int              i;
275
276         char            *items;
277         size_t           items_num;
278
279         struct timeval   time_end;
280         struct timeval   time_now;
281         int              timeout;
282
283         int              pkt_item_num;        /* items in this packet */
284         int              pkt_item_len;        /* size of the items in this packet */
285         int              pkt_sequence;
286         char             pkt_recvd[MAXSEQ+1]; /* sequence numbers that have been received */
287         int              pkt_recvd_num;       /* number of packets that have been received */
288         int              pkt_lastseq;         /* the last sequence number */
289         ssize_t          pkt_padding;         /* Padding in this packet */
290
291         if ((sd = ntpd_connect ()) < 0)
292                 return (-1);
293
294         items = NULL;
295         items_num = 0;
296
297         memset (pkt_recvd, '\0', sizeof (pkt_recvd));
298         pkt_recvd_num = 0;
299         pkt_lastseq   = -1;
300
301         *res_items = 0;
302         *res_size  = 0;
303         *res_data  = NULL;
304
305         if (gettimeofday (&time_end, NULL) < 0)
306         {
307                 syslog (LOG_ERR, "ntpd plugin: gettimeofday failed: %s",
308                                 strerror (errno));
309                 return (-1);
310         }
311         time_end.tv_sec++; /* wait for a most one second */
312
313         done = 0;
314         while (done == 0)
315         {
316                 if (gettimeofday (&time_now, NULL) < 0)
317                 {
318                         syslog (LOG_ERR, "ntpd plugin: gettimeofday failed: %s",
319                                         strerror (errno));
320                         return (-1);
321                 }
322
323                 /* timeout reached */
324                 if ((timeout = timeval_sub (&time_end, &time_now)) == 0)
325                         break;
326
327                 poll_s.fd      = sd;
328                 poll_s.events  = POLLIN | POLLPRI;
329                 poll_s.revents = 0;
330                 
331                 status = poll (&poll_s, 1, timeout);
332
333                 if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
334                         continue;
335
336                 if (status < 0)
337                 {
338                         syslog (LOG_ERR, "ntpd plugin: poll failed: %s",
339                                         strerror (errno));
340                         return (-1);
341                 }
342
343                 if (status == 0) /* timeout */
344                         break;
345
346                 memset ((void *) &res, '\0', sizeof (res));
347                 status = recv (sd, (void *) &res, sizeof (res), 0 /* no flags */);
348
349                 if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
350                         continue;
351
352                 if (status < 0)
353                         return (-1);
354
355                 /* 
356                  * Do some sanity checks first
357                  */
358                 if (status < RESP_HEADER_SIZE)
359                 {
360                         syslog (LOG_WARNING, "ntpd plugin: Short (%i bytes) packet received",
361                                         (int) status);
362                         continue;
363                 }
364                 if (INFO_MODE (res.rm_vn_mode) != MODE_PRIVATE)
365                 {
366                         syslog (LOG_NOTICE, "ntpd plugin: Packet received with mode %i",
367                                         INFO_MODE (res.rm_vn_mode));
368                         continue;
369                 }
370                 if (INFO_IS_AUTH (res.auth_seq))
371                 {
372                         syslog (LOG_NOTICE, "ntpd plugin: Encrypted packet received");
373                         continue;
374                 }
375                 if (!ISRESPONSE (res.rm_vn_mode))
376                 {
377                         syslog (LOG_NOTICE, "ntpd plugin: Received request packet, "
378                                         "wanted response");
379                         continue;
380                 }
381                 if (INFO_MBZ (res.mbz_itemsize))
382                 {
383                         syslog (LOG_WARNING, "ntpd plugin: Received packet with nonzero "
384                                         "MBZ field!");
385                         continue;
386                 }
387                 if (res.implementation != req_code)
388                 {
389                         syslog (LOG_WARNING, "ntpd plugin: Asked for request of type %i, "
390                                         "got %i", (int) req_code, (int) res.implementation);
391                         continue;
392                 }
393
394                 /* Check for error code */
395                 if (INFO_ERR (res.err_nitems) != 0)
396                 {
397                         syslog (LOG_ERR, "ntpd plugin: Received error code %i",
398                                         (int) INFO_ERR(res.err_nitems));
399                         return ((int) INFO_ERR (res.err_nitems));
400                 }
401
402                 /* extract number of items in this packet and the size of these items */
403                 pkt_item_num = INFO_NITEMS (res.err_nitems);
404                 pkt_item_len = INFO_ITEMSIZE (res.mbz_itemsize);
405
406                 /* Check if the reported items fit in the packet */
407                 if ((pkt_item_num * pkt_item_len) > (status - RESP_HEADER_SIZE))
408                 {
409                         syslog (LOG_ERR, "ntpd plugin: %i items * %i bytes > "
410                                         "%i bytes - %i bytes header",
411                                         (int) pkt_item_num, (int) pkt_item_len,
412                                         (int) status, (int) RESP_HEADER_SIZE);
413                         continue;
414                 }
415
416                 /* If this is the first packet (time wise, not sequence wise),
417                  * set `res_size'. If it's not the first packet check if the
418                  * items have the same size. Discard invalid packets. */
419                 if (items_num == 0) /* first packet */
420                 {
421                         *res_size = pkt_item_len;
422                 }
423                 else if (*res_size != pkt_item_len)
424                 {
425                         syslog (LOG_ERR, "Item sizes differ.");
426                         continue;
427                 }
428
429                 /* Calculate the padding. No idea why there might be any padding.. */
430                 pkt_padding = 0;
431                 if (res_item_size > pkt_item_len)
432                         pkt_padding = res_item_size - pkt_item_len;
433
434                 /* Extract the sequence number */
435                 pkt_sequence = INFO_SEQ (res.auth_seq);
436                 if ((pkt_sequence < 0) || (pkt_sequence > MAXSEQ))
437                 {
438                         syslog (LOG_ERR, "ntpd plugin: Received packet with sequence %i",
439                                         pkt_sequence);
440                         continue;
441                 }
442
443                 /* Check if this sequence has been received before. If so, discard it. */
444                 if (pkt_recvd[pkt_sequence] != '\0')
445                 {
446                         syslog (LOG_NOTICE, "ntpd plugin: Sequence %i received twice",
447                                         pkt_sequence);
448                         continue;
449                 }
450
451                 /* If `pkt_lastseq != -1' another packet without `more bit' has
452                  * been received. */
453                 if (!ISMORE (res.rm_vn_mode))
454                 {
455                         if (pkt_lastseq != -1)
456                         {
457                                 syslog (LOG_ERR, "ntpd plugin: Two packets which both "
458                                                 "claim to be the last one in the "
459                                                 "sequence have been received.");
460                                 continue;
461                         }
462                         pkt_lastseq = pkt_sequence;
463                 }
464
465                 /*
466                  * Enough with the checks. Copy the data now.
467                  * We start by allocating some more memory.
468                  */
469                 items = realloc ((void *) *res_data,
470                                 (items_num + pkt_item_num) * res_item_size);
471                 if (items == NULL)
472                 {
473                         items = *res_data;
474                         syslog (LOG_ERR, "ntpd plugin: realloc failed.");
475                         continue;
476                 }
477                 *res_data = items;
478
479                 for (i = 0; i < pkt_item_num; i++)
480                 {
481                         void *dst = (void *) (*res_data + ((*res_items) * res_item_size));
482                         void *src = (void *) (((char *) res.data) + (i * pkt_item_len));
483
484                         /* Set the padding to zeros */
485                         if (pkt_padding != 0)
486                                 memset (dst, '\0', res_item_size);
487                         memcpy (dst, src, (size_t) pkt_item_len);
488
489                         (*res_items)++;
490                 }
491
492                 pkt_recvd[pkt_sequence] = (char) 1;
493                 pkt_recvd_num++;
494
495                 if ((pkt_recvd_num - 1) == pkt_lastseq)
496                         done = 1;
497         } /* while (done == 0) */
498
499         return (0);
500 }
501
502 /* For a description of the arguments see `ntpd_do_query' below. */
503 static int ntpd_send_request (int req_code, int req_items, int req_size, char *req_data)
504 {
505         int             sd;
506         struct req_pkt  req;
507         size_t          req_data_len;
508         int             status;
509
510         assert (req_items >= 0);
511         assert (req_size  >= 0);
512
513         if ((sd = ntpd_connect ()) < 0)
514                 return (-1);
515
516         memset ((void *) &req, '\0', sizeof (req));
517         req.rm_vn_mode = RM_VN_MODE(0, 0, 0);
518         req.auth_seq   = AUTH_SEQ (0, 0);
519         req.implementation = IMPL_XNTPD;
520         req.request = (unsigned char) req_code;
521
522         req_data_len = (size_t) (req_items * req_size);
523
524         assert (((req_data != NULL) && (req_data_len > 0))
525                         || ((req_data == NULL) && (req_items == 0) && (req_size == 0)));
526
527         req.err_nitems   = ERR_NITEMS (0, req_items);
528         req.mbz_itemsize = MBZ_ITEMSIZE (req_size);
529         
530         if (req_data != NULL)
531                 memcpy ((void *) req.data, (const void *) req_data, req_data_len);
532
533         status = swrite (sd, (const char *) &req, REQ_LEN_NOMAC);
534         if (status < 0)
535                 return (status);
536
537         return (0);
538 }
539
540 /*
541  * ntpd_do_query:
542  *
543  * req_code:      Type of request packet
544  * req_items:     Numver of items in the request
545  * req_size:      Size of one item in the request
546  * req_data:      Data of the request packet
547  * res_items:     Pointer to where the number returned items will be stored.
548  * res_size:      Pointer to where the size of one returned item will be stored.
549  * res_data:      This is where a pointer to the (allocated) data will be stored.
550  * res_item_size: Size of one returned item. (used to calculate padding)
551  *
552  * returns:       zero upon success, non-zero otherwise.
553  */
554 static int ntpd_do_query (int req_code, int req_items, int req_size, char *req_data,
555                 int *res_items, int *res_size, char **res_data, int res_item_size)
556 {
557         int status;
558
559         status = ntpd_send_request (req_code, req_items, req_size, req_data);
560         if (status != 0)
561                 return (status);
562
563         status = ntpd_receive_response (req_code, res_items, res_size, res_data,
564                         res_item_size);
565         return (status);
566 }
567
568 static void ntpd_read (void)
569 {
570         return;
571 }
572 #else
573 # define ntpd_read NULL
574 #endif /* NTPD_HAVE_READ */
575
576 void module_register (void)
577 {
578         plugin_register (MODULE_NAME, ntpd_init, ntpd_read, ntpd_write);
579 }
580
581 #undef MODULE_NAME