Added license notes
[collectd.git] / src / chrony.c
1 /* chrony plugin for collectd (monitoring of chrony time server daemon)
2  **********************************************************************
3  * Copyright (C) Claudius M Zingerli, ZSeng, 2015-2016 
4  *
5  * Internas roughly based on the ntpd plugin
6  * Some functions copied from chronyd/web (as marked)
7  * 
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of version 2 of the GNU General Public License as
10  * published by the Free Software Foundation.
11  * 
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20  * 
21  **********************************************************************
22 */
23 /* TODO:
24  *      - More robust udp parsing (using offsets instead of structs?)
25  *        -> Currently chrony parses its data the same way as we do (using structs)
26  *      - Plausibility checks on values received
27  *        -> Done at higher levels
28  */
29
30 #include "config.h"
31
32 #if HAVE_SYS_TYPES_H
33 #  include <sys/types.h> /* getaddrinfo */
34 #endif
35 #if HAVE_SYS_SOCKET_H
36 #  include <sys/socket.h>
37 #endif
38 #if HAVE_NETDB_H
39 #  include <netdb.h>
40 #endif
41 #if HAVE_ARPA_INET_H
42 #  include <arpa/inet.h> /* ntohs/ntohl */
43 #endif
44
45 #include "collectd.h"
46 #include "common.h" /* auxiliary functions */
47 #include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
48
49 #define CONFIG_KEY_HOST    "Host"
50 #define CONFIG_KEY_PORT    "Port"
51 #define CONFIG_KEY_TIMEOUT "Timeout"
52
53 #define URAND_DEVICE_PATH "/dev/urandom" /* Used to initialize seq nr generator*/
54 #define RAND_DEVICE_PATH  "/dev/random"  /* Used to initialize seq nr generator (fall back)*/
55
56 static const char *g_config_keys[] =
57 {
58         CONFIG_KEY_HOST,
59         CONFIG_KEY_PORT,
60         CONFIG_KEY_TIMEOUT
61 };
62
63 static int    g_config_keys_num = STATIC_ARRAY_SIZE (g_config_keys);
64 static int    g_is_connected    =  0;
65 static int    g_chrony_socket   = -1;
66 static time_t g_chrony_timeout  = -1;
67 static char  *g_chrony_host     =  NULL;
68 static char  *g_chrony_port     =  NULL;
69 static char  *g_plugin_instance =  NULL;
70 static uint32_t g_chrony_rand   =  1;
71 static uint32_t g_chrony_seq_is_initialized = 0;
72
73 #define PLUGIN_NAME_SHORT "chrony"
74 #define PLUGIN_NAME       PLUGIN_NAME_SHORT " plugin"
75 #define DAEMON_NAME       PLUGIN_NAME_SHORT
76 #define CHRONY_DEFAULT_HOST "localhost"
77 #define CHRONY_DEFAULT_PORT "323"
78 #define CHRONY_DEFAULT_TIMEOUT 2
79
80 /* Return codes (collectd expects non-zero on errors) */
81 #define CHRONY_RC_OK    0
82 #define CHRONY_RC_FAIL  1
83
84 /* Variables adapted from chrony/candm.h (GPL2)*/
85 /*BEGIN*/
86 #define PROTO_VERSION_NUMBER 6
87 #define IPADDR_UNSPEC 0
88 #define IPADDR_INET4  1
89 #define IPADDR_INET6  2
90 #define IPV6_STR_MAX_SIZE (8*4+7+1)
91
92 typedef enum
93 {
94         PKT_TYPE_CMD_REQUEST = 1,
95         PKT_TYPE_CMD_REPLY   = 2
96 } ePacketType;
97
98 typedef enum
99 {
100         REQ_N_SOURCES    = 14,
101         REQ_SOURCE_DATA  = 15,
102         REQ_TRACKING     = 33,
103         REQ_SOURCE_STATS = 34
104 } eDaemonRequests;
105
106
107 typedef enum
108 {
109         RPY_NULL             = 1,
110         RPY_N_SOURCES        = 2,
111         RPY_SOURCE_DATA      = 3,
112         RPY_MANUAL_TIMESTAMP = 4,
113         RPY_TRACKING         = 5,
114         RPY_SOURCE_STATS     = 6,
115         RPY_RTC              = 7
116 } eDaemonReplies;
117
118 #define ATTRIB_PACKED __attribute__((packed))
119 typedef struct ATTRIB_PACKED
120 {
121         int32_t value;
122 } tFloat;
123
124 typedef struct ATTRIB_PACKED
125 {
126         uint32_t tv_sec_high;
127         uint32_t tv_sec_low;
128         uint32_t tv_nsec;
129 } tTimeval;
130 /*END*/
131
132 typedef enum
133 {
134         STT_SUCCESS        =  0,
135         STT_FAILED         =  1,
136         STT_UNAUTH         =  2,
137         STT_INVALID        =  3,
138         STT_NOSUCHSOURCE   =  4,
139         STT_INVALIDTS      =  5,
140         STT_NOTENABLED     =  6,
141         STT_BADSUBNET      =  7,
142         STT_ACCESSALLOWED  =  8,
143         STT_ACCESSDENIED   =  9,
144         STT_NOHOSTACCESS   = 10,
145         STT_SOURCEALREADYKNOWN = 11,
146         STT_TOOMANYSOURCES = 12,
147         STT_NORTC          = 13,
148         STT_BADRTCFILE     = 14,
149         STT_INACTIVE       = 15,
150         STT_BADSAMPLE      = 16,
151         STT_INVALIDAF      = 17,
152         STT_BADPKTVERSION  = 18,
153         STT_BADPKTLENGTH   = 19
154 } eChrony_Status;
155
156 /* Chrony client request packets */
157 typedef struct ATTRIB_PACKED
158 {
159         uint8_t f_dummy0[80]; //Chrony expects 80bytes dummy data (Avoiding UDP Amplification)
160 } tChrony_Req_Tracking;
161
162 typedef struct ATTRIB_PACKED
163 {
164         uint32_t f_n_sources;
165 } tChrony_Req_N_Sources;
166
167 typedef struct ATTRIB_PACKED
168 {
169         int32_t f_index;
170         uint8_t f_dummy0[44];
171 } tChrony_Req_Source_data;
172
173 typedef struct ATTRIB_PACKED
174 {
175         int32_t f_index;
176         uint8_t f_dummy0[56];
177 } tChrony_Req_Source_stats;
178
179 typedef struct ATTRIB_PACKED
180 {
181         struct
182         {
183                 uint8_t  f_version;
184                 uint8_t  f_type;
185                 uint8_t  f_dummy0;
186                 uint8_t  f_dummy1;
187                 uint16_t f_cmd;
188                 uint16_t f_cmd_try;
189                 uint32_t f_seq;
190
191                 uint32_t f_dummy2;
192                 uint32_t f_dummy3;
193         } header; /* Packed: 20Bytes */
194         union
195         {
196                 tChrony_Req_N_Sources    n_sources;
197                 tChrony_Req_Source_data  source_data;
198                 tChrony_Req_Source_stats source_stats;
199                 tChrony_Req_Tracking     tracking;
200         } body;
201         uint8_t padding[4+16]; /* Padding to match minimal response size */
202 } tChrony_Request;
203
204 /* Chrony daemon response packets */
205 typedef struct ATTRIB_PACKED
206 {
207         uint32_t f_n_sources;
208 } tChrony_Resp_N_Sources;
209
210 typedef struct ATTRIB_PACKED
211 {
212         union
213         {
214                 uint32_t ip4;
215                 uint8_t  ip6[16];
216         } addr;
217         uint16_t f_family;
218 } tChrony_IPAddr;
219
220 typedef struct ATTRIB_PACKED
221 {
222         tChrony_IPAddr addr;
223         uint16_t dummy;     /* FIXME: Strange dummy space. Needed on gcc 4.8.3/clang 3.4.1 on x86_64 */
224         int16_t  f_poll;    /* 2^f_poll = Time between polls (s) */
225         uint16_t f_stratum; /* Remote clock stratum */
226         uint16_t f_state;   /* 0 = RPY_SD_ST_SYNC,    1 = RPY_SD_ST_UNREACH,   2 = RPY_SD_ST_FALSETICKER */
227         /* 3 = RPY_SD_ST_JITTERY, 4 = RPY_SD_ST_CANDIDATE, 5 = RPY_SD_ST_OUTLIER     */
228         uint16_t f_mode;    /* 0 = RPY_SD_MD_CLIENT,  1 = RPY_SD_MD_PEER,      2 = RPY_SD_MD_REF         */
229         uint16_t f_flags;   /* unused */
230         uint16_t f_reachability;       /* Bit mask of successfull tries to reach the source */
231
232         uint32_t f_since_sample;       /* Time since last sample (s) */
233         tFloat   f_origin_latest_meas; /*  */
234         tFloat   f_latest_meas;        /*  */
235         tFloat   f_latest_meas_err;    /*  */
236 } tChrony_Resp_Source_data;
237
238 typedef struct ATTRIB_PACKED
239 {
240         uint32_t f_ref_id;
241         tChrony_IPAddr addr;
242         uint16_t dummy;               /* FIXME: Strange dummy space. Needed on gcc 4.8.3/clang 3.4.1 on x86_64 */
243         uint32_t f_n_samples;         /* Number of measurements done   */
244         uint32_t f_n_runs;            /* How many measurements to come */
245         uint32_t f_span_seconds;      /* For how long we're measuring  */
246         tFloat   f_rtc_seconds_fast;  /* ??? */
247         tFloat   f_rtc_gain_rate_ppm; /* Estimated relative frequency error */
248         tFloat   f_skew_ppm;          /* Clock skew (ppm) (worst case freq est error (skew: peak2peak)) */
249         tFloat   f_est_offset;        /* Estimated offset of source */
250         tFloat   f_est_offset_err;    /* Error of estimation        */
251 } tChrony_Resp_Source_stats;
252
253 typedef struct ATTRIB_PACKED
254 {
255         uint32_t f_ref_id;
256         tChrony_IPAddr addr;
257         uint16_t dummy;     /* FIXME: Strange dummy space. Needed on gcc 4.8.3/clang 3.4.1 on x86_64 */
258         uint16_t f_stratum;
259         uint16_t f_leap_status;
260         tTimeval f_ref_time;
261         tFloat   f_current_correction;
262         tFloat   f_last_offset;
263         tFloat   f_rms_offset;
264         tFloat   f_freq_ppm;
265         tFloat   f_resid_freq_ppm;
266         tFloat   f_skew_ppm;
267         tFloat   f_root_delay;
268         tFloat   f_root_dispersion;
269         tFloat   f_last_update_interval;
270 } tChrony_Resp_Tracking;
271
272 typedef struct ATTRIB_PACKED
273 {
274         struct
275         {
276                 uint8_t f_version;
277                 uint8_t f_type;
278                 uint8_t f_dummy0;
279                 uint8_t f_dummy1;
280                 uint16_t f_cmd;
281                 uint16_t f_reply;
282                 uint16_t f_status;
283                 uint16_t f_dummy2;
284                 uint16_t f_dummy3;
285                 uint16_t f_dummy4;
286                 uint32_t f_seq;
287                 uint32_t f_dummy5;
288                 uint32_t f_dummy6;
289         } header; /* Packed: 28 Bytes */
290
291         union
292         {
293                 tChrony_Resp_N_Sources         n_sources;
294                 tChrony_Resp_Source_data  source_data;
295                 tChrony_Resp_Source_stats source_stats;
296                 tChrony_Resp_Tracking     tracking;
297         } body;
298
299         uint8_t padding[1024];
300 } tChrony_Response;
301
302
303 /*****************************************************************************/
304 /* Internal functions */
305 /*****************************************************************************/
306
307 /* connect_client code adapted from: http://long.ccaba.upc.edu/long/045Guidelines/eva/ipv6.html#daytimeClient6 */
308 /* License granted by Eva M Castro via e-mail on 2016-02-18 under the terms of GPLv3 */
309 static int connect_client (const char *p_hostname,
310                 const char *p_service,
311                 int         p_family,
312                 int         p_socktype)
313 {
314         struct addrinfo hints, *res=NULL, *ressave=NULL;
315         int n, sockfd;
316
317         memset(&hints, 0, sizeof(struct addrinfo));
318
319         hints.ai_family   = p_family;
320         hints.ai_socktype = p_socktype;
321
322         n = getaddrinfo(p_hostname, p_service, &hints, &res);
323
324         if (n <0)
325         {
326                 ERROR (PLUGIN_NAME ": getaddrinfo error:: [%s]", gai_strerror(n));
327                 return -1;
328         }
329
330         ressave = res;
331
332         sockfd=-1;
333         while (res)
334         {
335                 sockfd = socket(res->ai_family,
336                                 res->ai_socktype,
337                                 res->ai_protocol);
338
339                 if (!(sockfd < 0))
340                 {
341                         if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
342                         {
343                                 /* Success */
344                                 break;
345                         }
346
347                         close(sockfd);
348                         sockfd=-1;
349                 }
350                 res=res->ai_next;
351         }
352
353         freeaddrinfo(ressave);
354         return sockfd;
355 }
356
357 /* niptoha code originally from: git://git.tuxfamily.org/gitroot/chrony/chrony.git:util.c */
358 /* Original code licensed as GPLv2, by Richard P. Purnow, Miroslav Lichvar */
359 /* Original name: char * UTI_IPToString(IPAddr *addr)*/
360 static char * niptoha(const tChrony_IPAddr *addr,char *p_buf, size_t p_buf_size)
361 {
362         int rc=1;
363         unsigned long a, b, c, d, ip;
364         const uint8_t *ip6;
365
366         switch (ntohs(addr->f_family))
367         {
368                 case IPADDR_UNSPEC:
369                         rc=snprintf(p_buf, p_buf_size, "[UNSPEC]");
370                         break;
371                 case IPADDR_INET4:
372                         ip = ntohl(addr->addr.ip4);
373                         a = (ip>>24) & 0xff;
374                         b = (ip>>16) & 0xff;
375                         c = (ip>> 8) & 0xff;
376                         d = (ip>> 0) & 0xff;
377                         rc=snprintf(p_buf, p_buf_size, "%ld.%ld.%ld.%ld", a, b, c, d);
378                         break;
379                 case IPADDR_INET6:
380                         ip6 = addr->addr.ip6;
381
382 #ifdef FEAT_IPV6
383                         rc=inet_ntop(AF_INET6, ip6, p_buf, p_bug_size);
384 #else
385 #if defined(BYTE_ORDER) && (BYTE_ORDER == BIG_ENDIAN)
386                         rc=snprintf(p_buf, p_buf_size, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
387                                         ip6[15], ip6[14], ip6[13], ip6[12], ip6[11], ip6[10], ip6[9], ip6[8],
388                                         ip6[7], ip6[6], ip6[5], ip6[4], ip6[3], ip6[2], ip6[1], ip6[0]);
389 #else
390                         rc=snprintf(p_buf, p_buf_size, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
391                                         ip6[0], ip6[1], ip6[2], ip6[3], ip6[4], ip6[5], ip6[6], ip6[7],
392                                         ip6[8], ip6[9], ip6[10], ip6[11], ip6[12], ip6[13], ip6[14], ip6[15]);
393 #endif
394 #endif
395                         break;
396                 default:
397                         rc=snprintf(p_buf, p_buf_size, "[UNKNOWN]");
398         }
399         assert(rc>0);
400         return p_buf;
401 }
402 /*END*/
403
404 static int chrony_set_timeout()
405 {
406         /*Set the socket's  timeout to g_chrony_timeout; a value of 0 signals infinite timeout*/
407         /*Returns 0 on success, !0 on error (check errno)*/
408
409         struct timeval tv;
410         tv.tv_sec  = g_chrony_timeout;
411         tv.tv_usec = 0;
412
413         assert(g_chrony_socket>=0);
414         if (setsockopt(g_chrony_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval)) < 0)
415         {
416                 return CHRONY_RC_FAIL;
417         }
418         return CHRONY_RC_OK;
419 }
420
421 static int chrony_connect()
422 {
423         /*Connects to the chrony daemon*/
424         /*Returns 0 on success, !0 on error (check errno)*/
425         int socket;
426
427         if (g_chrony_host == NULL)
428         {
429                 g_chrony_host = strdup(CHRONY_DEFAULT_HOST);
430                 assert(g_chrony_host);
431         }
432         if (g_chrony_port == NULL)
433         {
434                 g_chrony_port = strdup(CHRONY_DEFAULT_PORT);
435                 assert(g_chrony_port);
436         }
437         if (g_chrony_timeout < 0)
438         {
439                 g_chrony_timeout = CHRONY_DEFAULT_TIMEOUT;
440                 assert(g_chrony_timeout>=0);
441         }
442
443
444         DEBUG(PLUGIN_NAME ": Connecting to %s:%s", g_chrony_host, g_chrony_port);
445         socket = connect_client(g_chrony_host, g_chrony_port,  AF_UNSPEC, SOCK_DGRAM);
446         if (socket < 0)
447         {
448                 ERROR (PLUGIN_NAME ": Error connecting to daemon. Errno = %d", errno);
449                 return CHRONY_RC_FAIL;
450         }
451         DEBUG(PLUGIN_NAME ": Connected");
452         g_chrony_socket = socket;
453
454         if (chrony_set_timeout())
455         {
456                 ERROR (PLUGIN_NAME ": Error setting timeout to %lds. Errno = %d", g_chrony_timeout, errno);
457                 return CHRONY_RC_FAIL;
458         }
459         return CHRONY_RC_OK;
460 }
461
462 static int chrony_send_request(const tChrony_Request *p_req, size_t p_req_size)
463 {
464         if (send(g_chrony_socket,p_req,p_req_size,0) < 0)
465         {
466                 ERROR (PLUGIN_NAME ": Error sending packet. Errno = %d", errno);
467                 return CHRONY_RC_FAIL;
468         }
469         return CHRONY_RC_OK;
470 }
471
472 static int chrony_recv_response(tChrony_Response *p_resp, size_t p_resp_max_size, size_t *p_resp_size)
473 {
474         ssize_t rc = recv(g_chrony_socket,p_resp,p_resp_max_size,0);
475         if (rc <= 0)
476         {
477                 ERROR (PLUGIN_NAME ": Error receiving packet: %s (%d)", strerror(errno), errno);
478                 return CHRONY_RC_FAIL;
479         } else {
480                 *p_resp_size = rc;
481                 return CHRONY_RC_OK;
482         }
483 }
484
485 static int chrony_query(const int p_command, tChrony_Request *p_req, tChrony_Response *p_resp, size_t *p_resp_size)
486 {
487         /* Check connection. We simply perform one try as collectd already handles retries */
488         assert(p_req);
489         assert(p_resp);
490         assert(p_resp_size);
491
492         if (g_is_connected == 0)
493         {
494                 if (chrony_connect() == 0)
495                 {
496                         g_is_connected = 1;
497                 } else {
498                         ERROR (PLUGIN_NAME ": Unable to connect. Errno = %d", errno);
499                         return CHRONY_RC_FAIL;
500                 }
501         }
502
503
504         do
505         {
506                 int valid_command  = 0;
507                 size_t req_size    = sizeof(p_req->header) + sizeof(p_req->padding);
508                 size_t resp_size   = sizeof(p_resp->header);
509                 uint16_t resp_code = RPY_NULL;
510                 switch (p_command)
511                 {
512                         case REQ_TRACKING:
513                                 req_size  += sizeof(p_req->body.tracking);
514                                 resp_size += sizeof(p_resp->body.tracking); 
515                                 resp_code  = RPY_TRACKING;
516                                 valid_command = 1;
517                                 break;
518                         case REQ_N_SOURCES:
519                                 req_size  += sizeof(p_req->body.n_sources);
520                                 resp_size += sizeof(p_resp->body.n_sources); 
521                                 resp_code  = RPY_N_SOURCES;
522                                 valid_command = 1;
523                                 break;
524                         case REQ_SOURCE_DATA:
525                                 req_size  += sizeof(p_req->body.source_data);
526                                 resp_size += sizeof(p_resp->body.source_data); 
527                                 resp_code  = RPY_SOURCE_DATA;
528                                 valid_command = 1;
529                                 break;
530                         case REQ_SOURCE_STATS:
531                                 req_size  += sizeof(p_req->body.source_stats);
532                                 resp_size += sizeof(p_resp->body.source_stats); 
533                                 resp_code  = RPY_SOURCE_STATS;
534                                 valid_command = 1;
535                                 break;
536                         default:
537                                 ERROR (PLUGIN_NAME ": Unknown request command (Was: %d)", p_command);
538                                 break;
539                 }
540
541                 if (valid_command == 0)
542                 {
543                         break;
544                 }
545
546                 uint32_t seq_nr = rand_r(&g_chrony_rand);
547                 p_req->header.f_cmd     = htons(p_command);
548                 p_req->header.f_cmd_try = 0;
549                 p_req->header.f_seq     = seq_nr;
550
551                 DEBUG(PLUGIN_NAME ": Sending request (.cmd = %d, .seq = %d)",p_command, seq_nr);
552                 if (chrony_send_request(p_req,req_size) != 0)
553                 {
554                         break;
555                 }
556
557                 DEBUG(PLUGIN_NAME ": Waiting for response");
558                 if (chrony_recv_response(p_resp,resp_size,p_resp_size) != 0)
559                 {
560                         break;
561                 }
562                 DEBUG(PLUGIN_NAME ": Received response: .version = %u, .type = %u, .cmd = %u, .reply = %u, .status = %u, .seq = %u",
563                                 p_resp->header.f_version, p_resp->header.f_type, ntohs(p_resp->header.f_cmd),
564                                 ntohs(p_resp->header.f_reply), ntohs(p_resp->header.f_status), p_resp->header.f_seq);
565
566                 if (p_resp->header.f_version != p_req->header.f_version)
567                 {
568                         ERROR(PLUGIN_NAME ": Wrong protocol version (Was: %d, expected: %d)", p_resp->header.f_version, p_req->header.f_version);
569                         return CHRONY_RC_FAIL;
570                 }
571                 if (p_resp->header.f_type != PKT_TYPE_CMD_REPLY)
572                 {
573                         ERROR(PLUGIN_NAME ": Wrong packet type (Was: %d, expected: %d)", p_resp->header.f_type, PKT_TYPE_CMD_REPLY);
574                         return CHRONY_RC_FAIL;
575                 }
576                 if (p_resp->header.f_seq != seq_nr)
577                 {
578                         /* FIXME: Implement sequence number handling */
579                         ERROR(PLUGIN_NAME ": Unexpected sequence number (Was: %d, expected: %d)", p_resp->header.f_seq, p_req->header.f_seq);
580                         return CHRONY_RC_FAIL;
581                 }
582                 if (p_resp->header.f_cmd != p_req->header.f_cmd)
583                 {
584                         ERROR(PLUGIN_NAME ": Wrong reply command (Was: %d, expected: %d)", p_resp->header.f_cmd, p_req->header.f_cmd);
585                         return CHRONY_RC_FAIL;
586                 }
587
588                 if (ntohs(p_resp->header.f_reply) !=  resp_code)
589                 {
590                         ERROR(PLUGIN_NAME ": Wrong reply code (Was: %d, expected: %d)", ntohs(p_resp->header.f_reply), resp_code);
591                         return CHRONY_RC_FAIL;
592                 }
593
594                 switch (p_resp->header.f_status)
595                 {
596                         case STT_SUCCESS:
597                                 DEBUG(PLUGIN_NAME ": Reply packet status STT_SUCCESS");
598                                 break;
599                         default:
600                                 ERROR(PLUGIN_NAME ": Reply packet contains error status: %d (expected: %d)", p_resp->header.f_status, STT_SUCCESS);
601                                 return CHRONY_RC_FAIL;
602                 }
603
604                 /* Good result */
605                 return CHRONY_RC_OK;
606         } while (0);
607
608         /* Some error occured */
609         return CHRONY_RC_FAIL;
610 }
611
612 static void chrony_init_req(tChrony_Request *p_req)
613 {
614         memset(p_req,0,sizeof(*p_req));
615         p_req->header.f_version = PROTO_VERSION_NUMBER;
616         p_req->header.f_type    = PKT_TYPE_CMD_REQUEST;
617         p_req->header.f_dummy0  = 0;
618         p_req->header.f_dummy1  = 0;
619         p_req->header.f_dummy2  = 0;
620         p_req->header.f_dummy3  = 0;
621 }
622
623 /* ntohf code originally from: git://git.tuxfamily.org/gitroot/chrony/chrony.git:util.c */
624 /* Original code licensed as GPLv2, by Richard P. Purnow, Miroslav Lichvar */
625 /* Original name: double UTI_tFloatNetworkToHost(tFloat f) */
626 static double ntohf(tFloat p_float)
627 {
628         /* Convert tFloat in Network-bit-order to double in host-bit-order */
629
630 #define FLOAT_EXP_BITS 7
631 #define FLOAT_EXP_MIN (-(1 << (FLOAT_EXP_BITS - 1)))
632 #define FLOAT_EXP_MAX (-FLOAT_EXP_MIN - 1)
633 #define FLOAT_COEF_BITS ((int)sizeof (int32_t) * 8 - FLOAT_EXP_BITS)
634 #define FLOAT_COEF_MIN (-(1 << (FLOAT_COEF_BITS - 1)))
635 #define FLOAT_COEF_MAX (-FLOAT_COEF_MIN - 1)
636
637         int32_t exp, coef;
638         uint32_t uval;
639
640         uval = ntohl(p_float.value);
641         exp = (uval >> FLOAT_COEF_BITS) - FLOAT_COEF_BITS;
642         if (exp >= 1 << (FLOAT_EXP_BITS - 1))
643         {
644                 exp -= 1 << FLOAT_EXP_BITS;
645         }
646
647         /* coef = (x << FLOAT_EXP_BITS) >> FLOAT_EXP_BITS; */
648         coef = uval % (1U << FLOAT_COEF_BITS);
649         if (coef >= 1 << (FLOAT_COEF_BITS - 1))
650         {
651                 coef -= 1 << FLOAT_COEF_BITS; 
652         }
653         return coef * pow(2.0, exp);
654 }
655
656 static void chrony_push_data(char *p_type, char *p_type_inst, double p_value)
657 {
658         value_t values[1];
659         value_list_t vl = VALUE_LIST_INIT;
660
661         values[0].gauge = p_value; /* TODO: Check type??? (counter, gauge, derive, absolute) */
662
663         vl.values     = values;
664         vl.values_len = 1;
665
666         /* XXX: Shall g_chrony_host/g_chrony_port be reflected in the plugin's output? */
667         /* hostname_g is set in daemon/collectd.c (from config, via gethostname or by resolving localhost) */
668         /* defined as: char hostname_g[DATA_MAX_NAME_LEN]; (never NULL) */
669         sstrncpy (vl.host,            hostname_g,          sizeof (vl.host));
670         sstrncpy (vl.plugin,          PLUGIN_NAME_SHORT,   sizeof (vl.plugin));
671         if (g_plugin_instance != NULL) { sstrncpy (vl.plugin_instance, g_plugin_instance,   sizeof (vl.plugin_instance)); }
672         if (p_type            != NULL) { sstrncpy (vl.type,            p_type,              sizeof (vl.type)); }
673         if (p_type_inst       != NULL) { sstrncpy (vl.type_instance,   p_type_inst,         sizeof (vl.type_instance)); }
674
675         plugin_dispatch_values (&vl);
676 }
677
678
679 static void chrony_push_data_valid(char *p_type, char *p_type_inst, const int p_is_valid, double p_value)
680 {
681         /* Push real value if p_is_valid is true, push NAN if p_is_valid is not true (idea from ntp plugin)*/
682         if (p_is_valid == 0)
683         {
684                 p_value = NAN;
685         }
686         chrony_push_data(p_type, p_type_inst, p_value);
687 }
688
689
690 static int chrony_init_seq()
691 {
692         /* Initialize the sequence number generator from /dev/urandom */
693
694         /* Try urandom */
695         int fh = open(URAND_DEVICE_PATH, O_RDONLY);
696         if (fh >= 0)
697         {
698                 ssize_t rc = read(fh, &g_chrony_rand, sizeof(g_chrony_rand));
699                 if (rc != sizeof(g_chrony_rand))
700                 {
701                         ERROR (PLUGIN_NAME ": Reading from random source \'%s\'failed: %s (%d)", URAND_DEVICE_PATH, strerror(errno), errno);
702                         close(fh);
703                         return CHRONY_RC_FAIL;
704                 }
705                 close(fh);
706                 DEBUG(PLUGIN_NAME ": Seeding RNG from " URAND_DEVICE_PATH);
707         } else {
708                 if (errno == ENOENT)
709                 {
710                         /* URAND_DEVICE_PATH device not found. Try RAND_DEVICE_PATH as fall-back */
711                         int fh = open(RAND_DEVICE_PATH, O_RDONLY);
712                         if (fh >= 0)
713                         {
714                                 ssize_t rc = read(fh, &g_chrony_rand, sizeof(g_chrony_rand));
715                                 if (rc != sizeof(g_chrony_rand))
716                                 {
717                                         ERROR (PLUGIN_NAME ": Reading from random source \'%s\'failed: %s (%d)", RAND_DEVICE_PATH, strerror(errno), errno);
718                                         close(fh);
719                                         return CHRONY_RC_FAIL;
720                                 }
721                                 close(fh);
722                                 DEBUG(PLUGIN_NAME ": Seeding RNG from " RAND_DEVICE_PATH);
723                         } else {
724                                 /* Error opening RAND_DEVICE_PATH. Try time(NULL) as fall-back */
725                                 DEBUG(PLUGIN_NAME ": Seeding RNG from time(NULL)");
726                                 g_chrony_rand = time(NULL) ^ getpid();
727                         }
728                 } else {
729                         ERROR (PLUGIN_NAME ": Opening random source \'%s\' failed: %s (%d)", URAND_DEVICE_PATH, strerror(errno), errno);
730                         return CHRONY_RC_FAIL;
731                 }
732         }
733
734
735         return CHRONY_RC_OK;
736 }
737
738 /*****************************************************************************/
739 /* Exported functions */
740 /*****************************************************************************/
741 static int chrony_config(const char *p_key, const char *p_value)
742 {
743         assert(p_key);
744         assert(p_value);
745         /* Parse config variables */
746         if (strcasecmp(p_key, CONFIG_KEY_HOST) == 0)
747         {
748                 if (g_chrony_host != NULL)
749                 {
750                         free (g_chrony_host);
751                 }
752                 if ((g_chrony_host = strdup (p_value)) == NULL)
753                 {
754                         ERROR (PLUGIN_NAME ": Error duplicating host name");
755                         return CHRONY_RC_FAIL;
756                 }
757         } else if (strcasecmp(p_key, CONFIG_KEY_PORT) == 0)
758         {
759                 if (g_chrony_port != NULL)
760                 {
761                         free (g_chrony_port);
762                 }
763                 if ((g_chrony_port = strdup (p_value)) == NULL)
764                 {
765                         ERROR (PLUGIN_NAME ": Error duplicating port name");
766                         return CHRONY_RC_FAIL;
767                 }
768         } else if (strcasecmp(p_key, CONFIG_KEY_TIMEOUT) == 0)
769         {
770                 time_t tosec = strtol(p_value,NULL,0);
771                 g_chrony_timeout = tosec;
772         } else {
773                 WARNING(PLUGIN_NAME ": Unknown configuration variable: %s %s",p_key,p_value);
774                 return CHRONY_RC_FAIL;
775         }
776         /* XXX: We could set g_plugin_instance here to "g_chrony_host-g_chrony_port", but as multiple instances aren't yet supported, we skip this for now */
777
778         return CHRONY_RC_OK;
779 }
780
781
782 static int chrony_request_daemon_stats()
783 {
784         /* Perform Tracking request */
785         int rc;
786         size_t chrony_resp_size;
787         tChrony_Request  chrony_req;
788         tChrony_Response chrony_resp;
789
790         chrony_init_req(&chrony_req);
791         rc = chrony_query(REQ_TRACKING, &chrony_req, &chrony_resp, &chrony_resp_size);
792         if (rc != 0)
793         {
794                 ERROR (PLUGIN_NAME ": chrony_query (REQ_TRACKING) failed with status %i", rc);
795                 return rc;
796         }
797
798 #if COLLECT_DEBUG
799         {
800                 char src_addr[IPV6_STR_MAX_SIZE];
801                 memset(src_addr, 0, sizeof(src_addr));
802                 niptoha(&chrony_resp.body.tracking.addr, src_addr, sizeof(src_addr));
803                 DEBUG(PLUGIN_NAME ": Daemon stat: .addr = %s, .ref_id= %u, .stratum = %u, .leap_status = %u, .ref_time = %u:%u:%u, .current_correction = %f, .last_offset = %f, .rms_offset = %f, .freq_ppm = %f, .skew_ppm = %f, .root_delay = %f, .root_dispersion = %f, .last_update_interval = %f",
804                                 src_addr,
805                                 ntohs(chrony_resp.body.tracking.f_ref_id), //FIXME: 16bit
806                                 ntohs(chrony_resp.body.tracking.f_stratum),
807                                 ntohs(chrony_resp.body.tracking.f_leap_status),
808                                 ntohl(chrony_resp.body.tracking.f_ref_time.tv_sec_high),
809                                 ntohl(chrony_resp.body.tracking.f_ref_time.tv_sec_low),
810                                 ntohl(chrony_resp.body.tracking.f_ref_time.tv_nsec),
811                                 ntohf(chrony_resp.body.tracking.f_current_correction),
812                                 ntohf(chrony_resp.body.tracking.f_last_offset),
813                                 ntohf(chrony_resp.body.tracking.f_rms_offset),
814                                 ntohf(chrony_resp.body.tracking.f_freq_ppm),
815                                 ntohf(chrony_resp.body.tracking.f_skew_ppm),
816                                 ntohf(chrony_resp.body.tracking.f_root_delay),
817                                 ntohf(chrony_resp.body.tracking.f_root_dispersion),
818                                 ntohf(chrony_resp.body.tracking.f_last_update_interval)
819                      );
820         }
821 #endif
822
823         double time_ref = ntohl(chrony_resp.body.tracking.f_ref_time.tv_nsec);
824         time_ref /= 1000000000.0;
825         time_ref += ntohl(chrony_resp.body.tracking.f_ref_time.tv_sec_low);
826         if (chrony_resp.body.tracking.f_ref_time.tv_sec_high)
827         {
828                 double secs_high = ntohl(chrony_resp.body.tracking.f_ref_time.tv_sec_high);
829                 secs_high *= 4294967296.0;
830                 time_ref += secs_high;
831         }
832
833         /* Forward results to collectd-daemon */
834         /* Type_instance is always 'chrony' to tag daemon-wide data */
835         /*                Type               Type_instance  Value */
836         chrony_push_data("clock_stratum",    DAEMON_NAME,   ntohs(chrony_resp.body.tracking.f_stratum));
837         chrony_push_data("time_ref",         DAEMON_NAME,   time_ref); /* unit: s */
838         chrony_push_data("time_offset_ntp",  DAEMON_NAME,   ntohf(chrony_resp.body.tracking.f_current_correction)); /* Offset between system time and NTP, unit: s */ 
839         chrony_push_data("time_offset",      DAEMON_NAME,   ntohf(chrony_resp.body.tracking.f_last_offset)); /* Estimated Offset of the NTP time, unit: s */
840         chrony_push_data("time_offset_rms",  DAEMON_NAME,   ntohf(chrony_resp.body.tracking.f_rms_offset));  /* averaged value of the above, unit: s */
841         chrony_push_data("frequency_error",  DAEMON_NAME,   ntohf(chrony_resp.body.tracking.f_freq_ppm));    /* Frequency error of the local osc, unit: ppm */
842         chrony_push_data("clock_skew_ppm",   DAEMON_NAME,   ntohf(chrony_resp.body.tracking.f_skew_ppm)); 
843         chrony_push_data("root_delay",       DAEMON_NAME,   ntohf(chrony_resp.body.tracking.f_root_delay));  /* Network latency between local daemon and the current source */
844         chrony_push_data("root_dispersion",  DAEMON_NAME,   ntohf(chrony_resp.body.tracking.f_root_dispersion));
845         chrony_push_data("clock_last_update",DAEMON_NAME,   ntohf(chrony_resp.body.tracking.f_last_update_interval));
846
847         return CHRONY_RC_OK;
848 }
849
850
851 static int chrony_request_sources_count(unsigned int *p_count)
852 {
853         /* Requests the number of time sources from the chrony daemon */
854         int rc;
855         size_t chrony_resp_size;
856         tChrony_Request  chrony_req;
857         tChrony_Response chrony_resp;
858
859         DEBUG(PLUGIN_NAME ": Requesting data");
860         chrony_init_req(&chrony_req);
861         rc = chrony_query (REQ_N_SOURCES, &chrony_req, &chrony_resp, &chrony_resp_size);
862         if (rc != 0)
863         {
864                 ERROR (PLUGIN_NAME ": chrony_query (REQ_N_SOURCES) failed with status %i", rc);
865                 return rc;
866         }
867
868         *p_count = ntohl(chrony_resp.body.n_sources.f_n_sources);
869         DEBUG(PLUGIN_NAME ": Getting data of %d clock sources", *p_count);
870
871         return CHRONY_RC_OK;
872 }
873
874
875 static int chrony_request_source_data(int p_src_idx, int *p_is_reachable)
876 {
877         /* Perform Source data request for source #p_src_idx*/
878         int rc;
879         size_t chrony_resp_size;
880         tChrony_Request  chrony_req;
881         tChrony_Response chrony_resp;
882
883         char src_addr[IPV6_STR_MAX_SIZE];
884         memset(src_addr, 0, sizeof(src_addr));
885
886         chrony_init_req(&chrony_req);
887         chrony_req.body.source_data.f_index  = htonl(p_src_idx);
888         rc = chrony_query(REQ_SOURCE_DATA, &chrony_req, &chrony_resp, &chrony_resp_size);
889         if (rc != 0)
890         {
891                 ERROR (PLUGIN_NAME ": chrony_query (REQ_SOURCE_DATA) failed with status %i", rc);
892                 return rc;
893         }
894
895         niptoha(&chrony_resp.body.source_data.addr, src_addr, sizeof(src_addr));
896         DEBUG(PLUGIN_NAME ": Source[%d] data: .addr = %s, .poll = %u, .stratum = %u, .state = %u, .mode = %u, .flags = %u, .reach = %u, .latest_meas_ago = %u, .orig_latest_meas = %f, .latest_meas = %f, .latest_meas_err = %f",
897                         p_src_idx,
898                         src_addr,
899                         ntohs(chrony_resp.body.source_data.f_poll),
900                         ntohs(chrony_resp.body.source_data.f_stratum),
901                         ntohs(chrony_resp.body.source_data.f_state),
902                         ntohs(chrony_resp.body.source_data.f_mode),
903                         ntohs(chrony_resp.body.source_data.f_flags),
904                         ntohs(chrony_resp.body.source_data.f_reachability),
905                         ntohl(chrony_resp.body.source_data.f_since_sample),
906                         ntohf(chrony_resp.body.source_data.f_origin_latest_meas),
907                         ntohf(chrony_resp.body.source_data.f_latest_meas),
908                         ntohf(chrony_resp.body.source_data.f_latest_meas_err)
909              );
910
911         /* Push NaN if source is currently not reachable */
912         int is_reachable = ntohs(chrony_resp.body.source_data.f_reachability) & 0x01; 
913         *p_is_reachable = is_reachable;
914
915         /* Forward results to collectd-daemon */
916         chrony_push_data_valid("clock_stratum",     src_addr, is_reachable, ntohs(chrony_resp.body.source_data.f_stratum));
917         chrony_push_data_valid("clock_state",       src_addr, is_reachable, ntohs(chrony_resp.body.source_data.f_state));
918         chrony_push_data_valid("clock_mode",        src_addr, is_reachable, ntohs(chrony_resp.body.source_data.f_mode));
919         chrony_push_data_valid("clock_reachability",src_addr, is_reachable, ntohs(chrony_resp.body.source_data.f_reachability));
920         chrony_push_data_valid("clock_last_meas",   src_addr, is_reachable, ntohs(chrony_resp.body.source_data.f_since_sample));
921
922         return CHRONY_RC_OK;
923 }
924
925
926 static int chrony_request_source_stats(int p_src_idx, const int *p_is_reachable)
927 {
928         /* Perform Source stats request for source #p_src_idx */
929         int rc;
930         size_t chrony_resp_size;
931         tChrony_Request  chrony_req;
932         tChrony_Response chrony_resp;
933         double skew_ppm, frequency_error, time_offset;
934
935         char src_addr[IPV6_STR_MAX_SIZE];
936         memset(src_addr, 0, sizeof(src_addr));
937
938         if (*p_is_reachable == 0)
939         {
940                 skew_ppm        = 0;
941                 frequency_error = 0;
942                 time_offset     = 0;
943         } else {
944                 chrony_init_req(&chrony_req);
945                 chrony_req.body.source_stats.f_index = htonl(p_src_idx);
946                 rc = chrony_query(REQ_SOURCE_STATS, &chrony_req, &chrony_resp, &chrony_resp_size);
947                 if (rc != 0)
948                 {
949                         ERROR (PLUGIN_NAME ": chrony_query (REQ_SOURCE_STATS) failed with status %i", rc);
950                         return rc;
951                 }
952
953                 skew_ppm        = ntohf(chrony_resp.body.source_stats.f_skew_ppm);
954                 frequency_error = ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm);
955                 time_offset     = ntohf(chrony_resp.body.source_stats.f_est_offset);
956
957                 niptoha(&chrony_resp.body.source_stats.addr, src_addr, sizeof(src_addr));
958                 DEBUG(PLUGIN_NAME ": Source[%d] stat: .addr = %s, .ref_id= %u, .n_samples = %u, " \
959                                 ".n_runs = %u, .span_seconds = %u, .rtc_seconds_fast = %f, " \
960                                 ".rtc_gain_rate_ppm = %f, .skew_ppm= %f, .est_offset = %f, .est_offset_err = %f",
961                                 p_src_idx,
962                                 src_addr,
963                                 ntohl(chrony_resp.body.source_stats.f_ref_id),
964                                 ntohl(chrony_resp.body.source_stats.f_n_samples),
965                                 ntohl(chrony_resp.body.source_stats.f_n_runs),
966                                 ntohl(chrony_resp.body.source_stats.f_span_seconds),
967                                 ntohf(chrony_resp.body.source_stats.f_rtc_seconds_fast),
968                                 frequency_error,
969                                 skew_ppm,
970                                 time_offset,
971                                 ntohf(chrony_resp.body.source_stats.f_est_offset_err)
972                      );
973
974         } //if (*is_reachable)
975
976         /* Forward results to collectd-daemon */
977         chrony_push_data_valid("clock_skew_ppm",    src_addr, *p_is_reachable, skew_ppm);
978         chrony_push_data_valid("frequency_error",   src_addr, *p_is_reachable, frequency_error); /* unit: ppm */
979         chrony_push_data_valid("time_offset",       src_addr, *p_is_reachable, time_offset); /* unit: s */
980
981         return CHRONY_RC_OK;
982 }
983
984 static int chrony_read()
985 {
986         /* collectd read callback: Perform data acquisition */
987         int  rc;
988         unsigned int now_src, n_sources;
989
990         if (g_chrony_seq_is_initialized == 0)
991         {
992                 /* Seed RNG for sequence number generation */
993                 rc = chrony_init_seq();
994                 if (rc != CHRONY_RC_OK)
995                 {
996                         return rc;
997                 }
998                 g_chrony_seq_is_initialized = 1;
999         }
1000
1001         /* Get daemon stats */
1002         rc = chrony_request_daemon_stats();
1003         if (rc != CHRONY_RC_OK)
1004         {
1005                 return rc;
1006         }
1007
1008         /* Get number of time sources, then check every source for status */
1009         rc = chrony_request_sources_count(&n_sources);
1010         if (rc != CHRONY_RC_OK)
1011         {
1012                 return rc;
1013         }
1014
1015         for (now_src = 0; now_src < n_sources; ++now_src)
1016         {
1017                 int is_reachable;
1018                 rc = chrony_request_source_data(now_src, &is_reachable);
1019                 if (rc != CHRONY_RC_OK)
1020                 {
1021                         return rc;
1022                 }
1023
1024                 rc = chrony_request_source_stats(now_src, &is_reachable);
1025                 if (rc != CHRONY_RC_OK)
1026                 {
1027                         return rc;
1028                 }
1029         }
1030         return CHRONY_RC_OK;
1031 }
1032
1033
1034 static int chrony_shutdown()
1035 {
1036         /* Collectd shutdown callback: Free mem */
1037         if (g_is_connected != 0)
1038         {
1039                 close(g_chrony_socket);
1040                 g_is_connected = 0;
1041         }
1042         if (g_chrony_host != NULL)
1043         {
1044                 free (g_chrony_host);
1045                 g_chrony_host = NULL;
1046         }
1047         if (g_chrony_port != NULL)
1048         {
1049                 free (g_chrony_port);
1050                 g_chrony_port = NULL;
1051         }
1052         if (g_plugin_instance != NULL)
1053         {
1054                 free (g_plugin_instance);
1055                 g_plugin_instance = NULL;
1056         }
1057         return CHRONY_RC_OK;
1058 }
1059
1060
1061 void module_register (void)
1062 {
1063         plugin_register_config(  PLUGIN_NAME_SHORT, chrony_config, g_config_keys, g_config_keys_num);
1064         plugin_register_read(    PLUGIN_NAME_SHORT, chrony_read);
1065         plugin_register_shutdown(PLUGIN_NAME_SHORT, chrony_shutdown);
1066 }
1067