261f67cf0385e951cb1124d30922ca932109fb8a
1 /* chrony plugin for collectd
2 (c) 2015 by Claudius M Zingerli, ZSeng
3 Internas roughly based on the ntpd plugin
4 License: GPL2
5 */
6 /* TODO:
7 * - More robust udp parsing (using offsets instead of structs?)
8 * - Plausibility checks on values received
9 *
10 *
11 */
13 /* getaddrinfo */
14 #include <sys/types.h>
15 #include <sys/socket.h>
16 #include <netdb.h>
18 #include "collectd.h"
19 #include "common.h" /* auxiliary functions */
20 #include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
22 static const char *g_config_keys[] =
23 {
24 "Host",
25 "Port",
26 "Timeout"
27 };
29 static int g_config_keys_num = STATIC_ARRAY_SIZE (g_config_keys);
31 # define CHRONY_DEFAULT_HOST "localhost"
32 # define CHRONY_DEFAULT_PORT "323"
33 # define CHRONY_DEFAULT_TIMEOUT 2
35 /* Copied from chrony/candm.h */
36 /*BEGIN*/
37 #define PROTO_VERSION_NUMBER 6
39 enum
40 {
41 REQ_N_SOURCES = 14,
42 REQ_SOURCE_DATA = 15,
43 REQ_TRACKING = 33,
44 REQ_SOURCE_STATS = 34
45 } eDaemonRequests;
47 #define PKT_TYPE_CMD_REQUEST 1
48 #define PKT_TYPE_CMD_REPLY 2
50 #define RPY_NULL 1
51 #define RPY_N_SOURCES 2
52 #define RPY_SOURCE_DATA 3
53 #define RPY_MANUAL_TIMESTAMP 4
54 #define RPY_TRACKING 5
55 #define RPY_SOURCE_STATS 6
56 #define RPY_RTC 7
58 #define IPADDR_UNSPEC 0
59 #define IPADDR_INET4 1
60 #define IPADDR_INET6 2
62 #define ATTRIB_PACKED __attribute__((packed))
63 typedef struct ATTRIB_PACKED
64 {
65 int32_t value;
66 } tFloat;
68 typedef struct ATTRIB_PACKED
69 {
70 uint32_t tv_sec_high;
71 uint32_t tv_sec_low;
72 uint32_t tv_nsec;
73 } tTimeval;
75 /*END*/
77 typedef enum
78 {
79 STT_SUCCESS = 0,
80 STT_FAILED = 1,
81 STT_UNAUTH = 2,
82 STT_INVALID = 3,
83 STT_NOSUCHSOURCE = 4,
84 STT_INVALIDTS = 5,
85 STT_NOTENABLED = 6,
86 STT_BADSUBNET = 7,
87 STT_ACCESSALLOWED = 8,
88 STT_ACCESSDENIED = 9,
89 STT_NOHOSTACCESS = 10,
90 STT_SOURCEALREADYKNOWN = 11,
91 STT_TOOMANYSOURCES = 12,
92 STT_NORTC = 13,
93 STT_BADRTCFILE = 14,
94 STT_INACTIVE = 15,
95 STT_BADSAMPLE = 16,
96 STT_INVALIDAF = 17,
97 STT_BADPKTVERSION = 18,
98 STT_BADPKTLENGTH = 19,
99 } eChrony_Status;
101 typedef struct ATTRIB_PACKED
102 {
103 uint8_t f_dummy0[80];
104 } tChrony_Req_Tracking;
106 typedef struct ATTRIB_PACKED
107 {
108 uint32_t f_n_sources;
109 } tChrony_N_Sources;
111 typedef struct ATTRIB_PACKED
112 {
113 int32_t f_index;
114 uint8_t f_dummy0[44];
115 } tChrony_Req_Source_data;
117 typedef struct ATTRIB_PACKED
118 {
119 int32_t f_index;
120 uint8_t f_dummy0[56];
121 } tChrony_Req_Source_stats;
123 #define IPV6_STR_MAX_SIZE 40
124 typedef struct ATTRIB_PACKED
125 {
126 union
127 {
128 uint32_t ip4;
129 uint8_t ip6[16];
130 } addr;
131 uint16_t f_family;
132 } tChrony_IPAddr;
134 typedef struct ATTRIB_PACKED
135 {
136 tChrony_IPAddr addr;
137 uint16_t dummy; /* FIXME: Strange dummy space. Needed on gcc 4.8.3 on x86_64 */
138 int16_t f_poll; // 2^f_poll = Time between polls (s)
139 uint16_t f_stratum; //Remote clock stratum
140 uint16_t f_state; //0 = RPY_SD_ST_SYNC, 1 = RPY_SD_ST_UNREACH, 2 = RPY_SD_ST_FALSETICKER
141 //3 = RPY_SD_ST_JITTERY, 4 = RPY_SD_ST_CANDIDATE, 5 = RPY_SD_ST_OUTLIER
142 uint16_t f_mode; //0 = RPY_SD_MD_CLIENT, 1 = RPY_SD_MD_PEER, 2 = RPY_SD_MD_REF
143 uint16_t f_flags; //unused
144 uint16_t f_reachability; //???
146 uint32_t f_since_sample; //Time since last sample (s)
147 tFloat f_origin_latest_meas; //
148 tFloat f_latest_meas; //
149 tFloat f_latest_meas_err; //
150 } tChrony_Resp_Source_data;
152 typedef struct ATTRIB_PACKED
153 {
154 uint32_t f_ref_id;
155 tChrony_IPAddr addr;
156 uint16_t dummy; /* FIXME: Strange dummy space. Needed on gcc 4.8.3 on x86_64 */
157 uint32_t f_n_samples; //Number of measurements done
158 uint32_t f_n_runs; //How many measurements to come
159 uint32_t f_span_seconds; //For how long we're measuring
160 tFloat f_rtc_seconds_fast; //???
161 tFloat f_rtc_gain_rate_ppm; //Estimated relative frequency error
162 tFloat f_skew_ppm; //Clock skew (ppm) (worst case freq est error (skew: peak2peak))
163 tFloat f_est_offset; //Estimated offset of source
164 tFloat f_est_offset_err; //Error of estimation
165 } tChrony_Resp_Source_stats;
167 typedef struct ATTRIB_PACKED
168 {
169 uint32_t f_ref_id;
170 tChrony_IPAddr addr;
171 uint16_t dummy; /* FIXME: Strange dummy space. Needed on gcc 4.8.3 on x86_64 */
172 uint16_t f_stratum;
173 uint16_t f_leap_status;
174 tTimeval f_ref_time;
175 tFloat f_current_correction;
176 tFloat f_last_offset;
177 tFloat f_rms_offset;
178 tFloat f_freq_ppm;
179 tFloat f_resid_freq_ppm;
180 tFloat f_skew_ppm;
181 tFloat f_root_delay;
182 tFloat f_root_dispersion;
183 tFloat f_last_update_interval;
184 } tChrony_Resp_Tracking;
186 typedef struct ATTRIB_PACKED
187 {
188 struct
189 {
190 uint8_t f_version;
191 uint8_t f_type;
192 uint8_t f_dummy0;
193 uint8_t f_dummy1;
194 uint16_t f_cmd;
195 uint16_t f_cmd_try;
196 uint32_t f_seq;
198 uint32_t f_dummy2;
199 uint32_t f_dummy3;
200 } header; /* Packed: 20Bytes */
201 union
202 {
203 tChrony_N_Sources n_sources;
204 tChrony_Req_Source_data source_data;
205 tChrony_Req_Source_stats source_stats;
206 tChrony_Req_Tracking tracking;
207 } body;
208 uint8_t padding[4+16]; /* Padding to match minimal response size */
209 } tChrony_Request;
211 typedef struct ATTRIB_PACKED
212 {
213 struct
214 {
215 uint8_t f_version;
216 uint8_t f_type;
217 uint8_t f_dummy0;
218 uint8_t f_dummy1;
219 uint16_t f_cmd;
220 uint16_t f_reply;
221 uint16_t f_status;
222 uint16_t f_dummy2;
223 uint16_t f_dummy3;
224 uint16_t f_dummy4;
225 uint32_t f_seq;
226 uint32_t f_dummy5;
227 uint32_t f_dummy6;
228 } header; /* Packed: 28 Bytes */
230 union
231 {
232 tChrony_N_Sources n_sources;
233 tChrony_Resp_Source_data source_data;
234 tChrony_Resp_Source_stats source_stats;
235 tChrony_Resp_Tracking tracking;
236 } body;
238 uint8_t padding[1024];
239 } tChrony_Response;
241 static int g_is_connected = 0;
242 static int g_chrony_socket = -1;
243 static time_t g_chrony_timeout = 0;
244 static char *g_chrony_host = NULL;
245 static char *g_chrony_port = NULL;
246 static uint32_t g_chrony_seq = 0;
248 /*****************************************************************************/
249 /* Internal functions */
250 /*****************************************************************************/
251 /* Code from: http://long.ccaba.upc.edu/long/045Guidelines/eva/ipv6.html#daytimeClient6 */
252 /*BEGIN*/
253 static int
254 connect_client (const char *p_hostname,
255 const char *service,
256 int family,
257 int socktype)
258 {
259 struct addrinfo hints, *res, *ressave;
260 int n, sockfd;
262 memset(&hints, 0, sizeof(struct addrinfo));
264 hints.ai_family = family;
265 hints.ai_socktype = socktype;
267 n = getaddrinfo(p_hostname, service, &hints, &res);
269 if (n <0)
270 {
271 ERROR ("chrony plugin: getaddrinfo error:: [%s]", gai_strerror(n));
272 return -1;
273 }
275 ressave = res;
277 sockfd=-1;
278 while (res)
279 {
280 sockfd = socket(res->ai_family,
281 res->ai_socktype,
282 res->ai_protocol);
284 if (!(sockfd < 0))
285 {
286 if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
287 {
288 break;
289 }
291 close(sockfd);
292 sockfd=-1;
293 }
294 res=res->ai_next;
295 }
297 freeaddrinfo(ressave);
298 return sockfd;
299 }
300 /*Code originally from: git://git.tuxfamily.org/gitroot/chrony/chrony.git:util.c */
301 /*char * UTI_IPToString(IPAddr *addr)*/
302 char * niptoha(const tChrony_IPAddr *addr,char *p_buf, size_t p_buf_size)
303 {
304 unsigned long a, b, c, d, ip;
305 const uint8_t *ip6;
307 switch (ntohs(addr->f_family))
308 {
309 case IPADDR_UNSPEC:
310 snprintf(p_buf, p_buf_size, "[UNSPEC]");
311 break;
312 case IPADDR_INET4:
313 ip = ntohl(addr->addr.ip4);
314 a = (ip>>24) & 0xff;
315 b = (ip>>16) & 0xff;
316 c = (ip>> 8) & 0xff;
317 d = (ip>> 0) & 0xff;
318 snprintf(p_buf, p_buf_size, "%ld.%ld.%ld.%ld", a, b, c, d);
319 break;
320 case IPADDR_INET6:
321 ip6 = addr->addr.ip6;
323 #ifdef FEAT_IPV6
324 inet_ntop(AF_INET6, ip6, p_buf, p_bug_size);
325 #else
326 #if 0
327 /* FIXME: Detect little endian systems */
328 snprintf(p_buf, p_buf_size, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
329 ip6[0], ip6[1], ip6[2], ip6[3], ip6[4], ip6[5], ip6[6], ip6[7],
330 ip6[8], ip6[9], ip6[10], ip6[11], ip6[12], ip6[13], ip6[14], ip6[15]);
331 #else
332 snprintf(p_buf, p_buf_size, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
333 ip6[5], ip6[4], ip6[3], ip6[2], ip6[1], ip6[0], ip6[9], ip6[8],
334 ip6[7], ip6[6], ip6[5], ip6[4], ip6[3], ip6[2], ip6[1], ip6[0]);
335 #endif
336 #endif
337 break;
338 default:
339 snprintf(p_buf, p_buf_size, "[UNKNOWN]");
340 }
341 return p_buf;
342 }
343 /*END*/
345 static int chrony_set_timeout()
346 {
347 struct timeval tv;
348 tv.tv_sec = g_chrony_timeout;
349 tv.tv_usec = 0;
351 assert(g_chrony_socket>=0);
352 if (setsockopt(g_chrony_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval)) < 0)
353 {
354 return 1;
355 }
356 return 0;
357 }
359 static int chrony_connect()
360 {
361 if (g_chrony_host == NULL)
362 {
363 g_chrony_host = strdup(CHRONY_DEFAULT_HOST);
364 }
365 if (g_chrony_port == NULL)
366 {
367 g_chrony_port = strdup(CHRONY_DEFAULT_PORT);
368 }
369 if (g_chrony_timeout <= 0)
370 {
371 g_chrony_timeout = CHRONY_DEFAULT_TIMEOUT;
372 }
375 DEBUG("chrony plugin: Connecting to %s:%s", g_chrony_host, g_chrony_port);
376 int socket = connect_client(g_chrony_host, g_chrony_port, AF_UNSPEC, SOCK_DGRAM);
377 if (socket < 0)
378 {
379 ERROR ("chrony plugin: Error connecting to daemon. Errno = %d", errno);
380 return 1;
381 }
382 DEBUG("chrony plugin: Connected");
383 g_chrony_socket = socket;
385 if (chrony_set_timeout())
386 {
387 ERROR ("chrony plugin: Error setting timeout to %lds. Errno = %d", g_chrony_timeout, errno);
388 return 1;
389 }
390 return 0;
391 }
393 static int chrony_send_request(const tChrony_Request *p_req, size_t p_req_size)
394 {
395 if (send(g_chrony_socket,p_req,p_req_size,0) < 0)
396 {
397 ERROR ("chrony plugin: Error sending packet. Errno = %d", errno);
398 return 1;
399 } else {
400 return 0;
401 }
402 }
404 static int chrony_recv_response(tChrony_Response *p_resp, size_t p_resp_max_size, size_t *p_resp_size)
405 {
406 ssize_t rc = recv(g_chrony_socket,p_resp,p_resp_max_size,0);
407 if (rc <= 0)
408 {
409 ERROR ("chrony plugin: Error receiving packet. Errno = %d", errno);
410 return 1;
411 } else {
412 *p_resp_size = rc;
413 return 0;
414 }
415 }
417 static int chrony_query(const int p_command, tChrony_Request *p_req, tChrony_Response *p_resp, size_t *p_resp_size)
418 {
419 /* Check connection. We simply perform one try as collectd already handles retries */
420 assert(p_req);
421 assert(p_resp);
422 assert(p_resp_size);
424 if (g_is_connected == 0)
425 {
426 if (chrony_connect() == 0)
427 {
428 g_is_connected = 1;
429 } else {
430 ERROR ("chrony plugin: Unable to connect. Errno = %d", errno);
431 return 1;
432 }
433 }
436 do
437 {
438 int valid_command = 0;
439 size_t req_size = sizeof(p_req->header) + sizeof(p_req->padding);
440 size_t resp_size = sizeof(p_resp->header);
441 uint16_t resp_code = RPY_NULL;
442 switch (p_command)
443 {
444 case REQ_TRACKING:
445 req_size += sizeof(p_req->body.tracking);
446 resp_size += sizeof(p_resp->body.tracking);
447 resp_code = RPY_TRACKING;
448 valid_command = 1;
449 break;
450 case REQ_N_SOURCES:
451 req_size += sizeof(p_req->body.n_sources);
452 resp_size += sizeof(p_resp->body.n_sources);
453 resp_code = RPY_N_SOURCES;
454 valid_command = 1;
455 break;
456 case REQ_SOURCE_DATA:
457 req_size += sizeof(p_req->body.source_data);
458 resp_size += sizeof(p_resp->body.source_data);
459 resp_code = RPY_SOURCE_DATA;
460 valid_command = 1;
461 break;
462 case REQ_SOURCE_STATS:
463 req_size += sizeof(p_req->body.source_stats);
464 resp_size += sizeof(p_resp->body.source_stats);
465 resp_code = RPY_SOURCE_STATS;
466 valid_command = 1;
467 break;
468 default:
469 ERROR ("chrony plugin: Unknown request command (Was: %d)", p_command);
470 break;
471 }
473 if (valid_command == 0)
474 {
475 break;
476 }
478 p_req->header.f_cmd = htons(p_command);
479 p_req->header.f_cmd_try = 0;
480 p_req->header.f_seq = htonl(g_chrony_seq++);
482 DEBUG("chrony plugin: Sending request (.cmd = %d, .seq = %d)",p_command,g_chrony_seq-1);
483 if (chrony_send_request(p_req,req_size) != 0)
484 {
485 break;
486 }
488 DEBUG("chrony plugin: Waiting for response");
489 if (chrony_recv_response(p_resp,resp_size,p_resp_size) != 0)
490 {
491 break;
492 }
493 DEBUG("chrony plugin: Received response: .version = %u, .type = %u, .cmd = %u, .reply = %u, .status = %u, .seq = %u",
494 p_resp->header.f_version,p_resp->header.f_type,ntohs(p_resp->header.f_cmd),
495 ntohs(p_resp->header.f_reply),ntohs(p_resp->header.f_status),ntohl(p_resp->header.f_seq));
497 if (p_resp->header.f_version != p_req->header.f_version)
498 {
499 ERROR("chrony plugin: Wrong protocol version (Was: %d, expected: %d)", p_resp->header.f_version, p_req->header.f_version);
500 return 1;
501 }
502 if (p_resp->header.f_type != PKT_TYPE_CMD_REPLY)
503 {
504 ERROR("chrony plugin: Wrong packet type (Was: %d, expected: %d)", p_resp->header.f_type, PKT_TYPE_CMD_REPLY);
505 return 1;
506 }
507 if (p_resp->header.f_seq != p_req->header.f_seq)
508 {
509 /* FIXME: Implement sequence number handling */
510 ERROR("chrony plugin: Unexpected sequence number (Was: %d, expected: %d)", p_resp->header.f_seq, p_req->header.f_seq);
511 return 1;
512 }
513 if (p_resp->header.f_cmd != p_req->header.f_cmd)
514 {
515 ERROR("chrony plugin: Wrong reply command (Was: %d, expected: %d)", p_resp->header.f_cmd, p_req->header.f_cmd);
516 return 1;
517 }
519 if (ntohs(p_resp->header.f_reply) != resp_code)
520 {
521 ERROR("chrony plugin: Wrong reply code (Was: %d, expected: %d)", ntohs(p_resp->header.f_reply), resp_code);
522 return 1;
523 }
525 switch (p_resp->header.f_status)
526 {
527 case STT_SUCCESS:
528 DEBUG("chrony plugin: Reply packet status STT_SUCCESS");
529 break;
530 default:
531 ERROR("chrony plugin: Reply packet contains error status: %d (expected: %d)", p_resp->header.f_status, STT_SUCCESS);
532 return 1;
533 }
535 //Good result
536 return 0;
537 } while (0);
539 //Some error occured
540 return 1;
541 }
543 static void chrony_init_req(tChrony_Request *p_req)
544 {
545 DEBUG("chrony plugin: Clearing %ld bytes",sizeof(*p_req));
546 memset(p_req,0,sizeof(*p_req));
547 p_req->header.f_version = PROTO_VERSION_NUMBER;
548 p_req->header.f_type = PKT_TYPE_CMD_REQUEST;
549 p_req->header.f_dummy0 = 0;
550 p_req->header.f_dummy1 = 0;
551 p_req->header.f_dummy2 = 0;
552 p_req->header.f_dummy3 = 0;
553 }
555 /* Code from: git://git.tuxfamily.org/gitroot/chrony/chrony.git:util.c (GPLv2) */
556 /*BEGIN*/
557 #define FLOAT_EXP_BITS 7
558 #define FLOAT_EXP_MIN (-(1 << (FLOAT_EXP_BITS - 1)))
559 #define FLOAT_EXP_MAX (-FLOAT_EXP_MIN - 1)
560 #define FLOAT_COEF_BITS ((int)sizeof (int32_t) * 8 - FLOAT_EXP_BITS)
561 #define FLOAT_COEF_MIN (-(1 << (FLOAT_COEF_BITS - 1)))
562 #define FLOAT_COEF_MAX (-FLOAT_COEF_MIN - 1)
564 /* double UTI_tFloatNetworkToHost(tFloat f) */
565 double ntohf(tFloat p_float)
566 {
567 int32_t exp, coef;
568 uint32_t uval;
570 uval = ntohl(p_float.value);
571 exp = (uval >> FLOAT_COEF_BITS) - FLOAT_COEF_BITS;
572 if (exp >= 1 << (FLOAT_EXP_BITS - 1))
573 {
574 exp -= 1 << FLOAT_EXP_BITS;
575 }
577 //coef = (x << FLOAT_EXP_BITS) >> FLOAT_EXP_BITS;
578 coef = uval % (1U << FLOAT_COEF_BITS);
579 if (coef >= 1 << (FLOAT_COEF_BITS - 1))
580 {
581 coef -= 1 << FLOAT_COEF_BITS;
582 }
583 return coef * pow(2.0, exp);
584 }
585 /*END*/
587 /* Code from: collectd/src/ntpd.c (MIT) */
588 /*BEGIN*/
589 static void chrony_push_data(char *type, char *type_inst, double value)
590 {
591 value_t values[1];
592 value_list_t vl = VALUE_LIST_INIT;
594 values[0].gauge = value; //TODO: Check type??? (counter, gauge, derive, absolute)
596 vl.values = values;
597 vl.values_len = 1;
598 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
599 sstrncpy (vl.plugin, "chrony", sizeof (vl.plugin));
600 sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
601 sstrncpy (vl.type, type, sizeof (vl.type));
602 sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
604 plugin_dispatch_values (&vl);
605 }
606 /*END*/
608 /*****************************************************************************/
609 /* Exported functions */
610 /*****************************************************************************/
611 static int chrony_config(const char *p_key, const char *p_value)
612 {
613 assert(p_key);
614 assert(p_value);
615 /* Parse config variables */
616 if (strcasecmp(p_key, "Host") == 0)
617 {
618 if (g_chrony_host != NULL)
619 {
620 free (g_chrony_host);
621 }
622 if ((g_chrony_host = strdup (p_value)) == NULL)
623 {
624 ERROR ("chrony plugin: Error duplicating host name");
625 return 1;
626 }
627 } else if (strcasecmp(p_key, "Port") == 0)
628 {
629 if (g_chrony_port != NULL)
630 {
631 free (g_chrony_port);
632 }
633 if ((g_chrony_port = strdup (p_value)) == NULL)
634 {
635 ERROR ("chrony plugin: Error duplicating port name");
636 return 1;
637 }
638 } else if (strcasecmp(p_key, "Timeout") == 0)
639 {
640 time_t tosec = strtol(p_value,NULL,0);
641 g_chrony_timeout = tosec;
642 } else {
643 WARNING("chrony plugin: Unknown configuration variable: %s %s",p_key,p_value);
644 return 1;
645 }
647 return 0;
648 }
651 static int chrony_request_daemon_stats()
652 {
653 //Tracking request
654 size_t chrony_resp_size;
655 tChrony_Request chrony_req;
656 tChrony_Response chrony_resp;
657 char src_addr[IPV6_STR_MAX_SIZE];
659 chrony_init_req(&chrony_req);
660 int rc = chrony_query(REQ_TRACKING, &chrony_req, &chrony_resp, &chrony_resp_size);
661 if (rc != 0)
662 {
663 ERROR ("chrony plugin: chrony_query (REQ_TRACKING) failed with status %i", rc);
664 return rc;
665 }
667 memset(src_addr, 0, sizeof(src_addr));
668 niptoha(&chrony_resp.body.tracking.addr, src_addr, sizeof(src_addr));
669 DEBUG("chrony plugin: Daemon stat: .addr = %s, .ref_id= %u, .stratum = %u, .leap_status = %u, .ref_time = %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",
670 src_addr,
671 ntohs(chrony_resp.body.tracking.f_ref_id), //FIXME: 16bit
672 ntohs(chrony_resp.body.tracking.f_stratum),
673 ntohs(chrony_resp.body.tracking.f_leap_status),
674 ntohl(chrony_resp.body.tracking.f_ref_time.tv_sec_high), //tTimeval
675 ntohf(chrony_resp.body.tracking.f_current_correction),
676 ntohf(chrony_resp.body.tracking.f_last_offset),
677 ntohf(chrony_resp.body.tracking.f_rms_offset),
678 ntohf(chrony_resp.body.tracking.f_freq_ppm),
679 ntohf(chrony_resp.body.tracking.f_skew_ppm),
680 ntohf(chrony_resp.body.tracking.f_root_delay),
681 ntohf(chrony_resp.body.tracking.f_root_dispersion),
682 ntohf(chrony_resp.body.tracking.f_last_update_interval)
683 );
684 #if 0
685 chrony_push_data("clock_skew_ppm", src_addr,ntohf(chrony_resp.body.source_stats.f_skew_ppm));
686 chrony_push_data("frequency_error", src_addr,ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm)); /* unit: ppm */
687 chrony_push_data("time_offset", src_addr,ntohf(chrony_resp.body.source_stats.f_est_offset)); /* unit: s */
688 #endif
689 return 0;
690 }
693 static int chrony_request_sources_count(unsigned int *p_count)
694 {
695 int rc;
696 size_t chrony_resp_size;
697 tChrony_Request chrony_req;
698 tChrony_Response chrony_resp;
700 DEBUG("chrony plugin: Requesting data");
701 chrony_init_req(&chrony_req);
702 rc = chrony_query (REQ_N_SOURCES, &chrony_req, &chrony_resp, &chrony_resp_size);
703 if (rc != 0)
704 {
705 ERROR ("chrony plugin: chrony_query (REQ_N_SOURCES) failed with status %i", rc);
706 return rc;
707 }
709 *p_count = ntohl(chrony_resp.body.n_sources.f_n_sources);
710 DEBUG("chrony plugin: Getting data of %d clock sources", *p_count);
712 return 0;
713 }
716 static int chrony_request_source_data(int p_src_idx)
717 {
718 //Source data request
719 size_t chrony_resp_size;
720 tChrony_Request chrony_req;
721 tChrony_Response chrony_resp;
722 char src_addr[IPV6_STR_MAX_SIZE];
724 chrony_init_req(&chrony_req);
725 chrony_req.body.source_data.f_index = htonl(p_src_idx);
726 int rc = chrony_query(REQ_SOURCE_DATA, &chrony_req, &chrony_resp, &chrony_resp_size);
727 if (rc != 0)
728 {
729 ERROR ("chrony plugin: chrony_query (REQ_SOURCE_DATA) failed with status %i", rc);
730 return rc;
731 }
732 memset(src_addr, 0, sizeof(src_addr));
733 niptoha(&chrony_resp.body.source_data.addr, src_addr, sizeof(src_addr));
734 DEBUG("chrony plugin: 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",
735 p_src_idx,
736 src_addr,
737 ntohs(chrony_resp.body.source_data.f_poll),
738 ntohs(chrony_resp.body.source_data.f_stratum),
739 ntohs(chrony_resp.body.source_data.f_state),
740 ntohs(chrony_resp.body.source_data.f_mode),
741 ntohs(chrony_resp.body.source_data.f_flags),
742 ntohs(chrony_resp.body.source_data.f_reachability),
743 ntohl(chrony_resp.body.source_data.f_since_sample),
744 ntohf(chrony_resp.body.source_data.f_origin_latest_meas),
745 ntohf(chrony_resp.body.source_data.f_latest_meas),
746 ntohf(chrony_resp.body.source_data.f_latest_meas_err)
747 );
748 chrony_push_data("clock_stratum", src_addr,ntohs(chrony_resp.body.source_data.f_stratum));
749 chrony_push_data("clock_state", src_addr,ntohs(chrony_resp.body.source_data.f_state));
750 chrony_push_data("clock_mode", src_addr,ntohs(chrony_resp.body.source_data.f_mode));
751 chrony_push_data("clock_reachability",src_addr,ntohs(chrony_resp.body.source_data.f_reachability));
752 chrony_push_data("clock_last_meas", src_addr,ntohs(chrony_resp.body.source_data.f_since_sample));
754 return 0;
755 }
758 static int chrony_request_source_stats(int p_src_idx)
759 {
760 //Source stats request
761 size_t chrony_resp_size;
762 tChrony_Request chrony_req;
763 tChrony_Response chrony_resp;
764 char src_addr[IPV6_STR_MAX_SIZE];
766 chrony_init_req(&chrony_req);
767 chrony_req.body.source_stats.f_index = htonl(p_src_idx);
768 int rc = chrony_query(REQ_SOURCE_STATS, &chrony_req, &chrony_resp, &chrony_resp_size);
769 if (rc != 0)
770 {
771 ERROR ("chrony plugin: chrony_query (REQ_SOURCE_STATS) failed with status %i", rc);
772 return rc;
773 }
775 memset(src_addr, 0, sizeof(src_addr));
776 niptoha(&chrony_resp.body.source_stats.addr, src_addr, sizeof(src_addr));
777 DEBUG("chrony plugin: Source[%d] stat: .addr = %s, .ref_id= %u, .n_samples = %u, .n_runs = %u, .span_seconds = %u, .rtc_seconds_fast = %f, .rtc_gain_rate_ppm = %f, .skew_ppm= %f, .est_offset = %f, .est_offset_err = %f",
778 p_src_idx,
779 src_addr,
780 ntohl(chrony_resp.body.source_stats.f_ref_id),
781 ntohl(chrony_resp.body.source_stats.f_n_samples),
782 ntohl(chrony_resp.body.source_stats.f_n_runs),
783 ntohl(chrony_resp.body.source_stats.f_span_seconds),
784 ntohf(chrony_resp.body.source_stats.f_rtc_seconds_fast),
785 ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm),
786 ntohf(chrony_resp.body.source_stats.f_skew_ppm),
787 ntohf(chrony_resp.body.source_stats.f_est_offset),
788 ntohf(chrony_resp.body.source_stats.f_est_offset_err)
789 );
790 chrony_push_data("clock_skew_ppm", src_addr,ntohf(chrony_resp.body.source_stats.f_skew_ppm));
791 chrony_push_data("frequency_error", src_addr,ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm)); /* unit: ppm */
792 chrony_push_data("time_offset", src_addr,ntohf(chrony_resp.body.source_stats.f_est_offset)); /* unit: s */
793 return 0;
794 }
797 static int chrony_read()
798 {
799 int rc;
801 //Get daemon stats
802 rc = chrony_request_daemon_stats();
803 if (rc != 0)
804 {
805 return rc;
806 }
808 //Get number of time sources, then check every source for status
809 unsigned int now_src, n_sources;
810 rc = chrony_request_sources_count(&n_sources);
811 if (rc != 0)
812 {
813 return rc;
814 }
816 for (now_src = 0; now_src < n_sources; ++now_src)
817 {
818 rc = chrony_request_source_data(now_src);
819 if (rc != 0)
820 {
821 return rc;
822 }
824 rc = chrony_request_source_stats(now_src);
825 if (rc != 0)
826 {
827 return rc;
828 }
829 }
830 return 0;
831 }
834 static int chrony_shutdown()
835 {
836 if (g_is_connected != 0)
837 {
838 close(g_chrony_socket);
839 g_is_connected = 0;
840 }
841 if (g_chrony_host != NULL)
842 {
843 free (g_chrony_host);
844 g_chrony_host = NULL;
845 }
846 if (g_chrony_port != NULL)
847 {
848 free (g_chrony_port);
849 g_chrony_port = NULL;
850 }
851 return 0;
852 }
855 void module_register (void)
856 {
857 plugin_register_config( "chrony", chrony_config, g_config_keys, g_config_keys_num);
858 plugin_register_read( "chrony", chrony_read);
859 plugin_register_shutdown("chrony", chrony_shutdown);
860 }