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 */
7 /* getaddrinfo */
8 #include <sys/types.h>
9 #include <sys/socket.h>
10 #include <netdb.h>
12 #include "collectd.h"
13 #include "common.h" /* auxiliary functions */
14 #include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
16 static const char *g_config_keys[] =
17 {
18 "Host",
19 "Port",
20 "Timeout"
21 };
23 static int g_config_keys_num = STATIC_ARRAY_SIZE (g_config_keys);
25 # define CHRONY_DEFAULT_HOST "localhost"
26 # define CHRONY_DEFAULT_PORT "323"
27 # define CHRONY_DEFAULT_TIMEOUT 2
29 /* Copied from chrony/candm.h */
30 /*BEGIN*/
31 #define PROTO_VERSION_NUMBER 6
33 #define REQ_N_SOURCES 14
34 #define REQ_SOURCE_DATA 15
35 #define REQ_SOURCE_STATS 34
37 #define PKT_TYPE_CMD_REQUEST 1
38 #define PKT_TYPE_CMD_REPLY 2
40 #define RPY_NULL 1
41 #define RPY_N_SOURCES 2
42 #define RPY_SOURCE_DATA 3
43 #define RPY_MANUAL_TIMESTAMP 4
44 #define RPY_TRACKING 5
45 #define RPY_SOURCE_STATS 6
46 #define RPY_RTC 7
48 #define IPADDR_UNSPEC 0
49 #define IPADDR_INET4 1
50 #define IPADDR_INET6 2
52 #define ATTRIB_PACKED __attribute__((packed))
53 typedef struct ATTRIB_PACKED
54 {
55 int32_t f;
56 } Float;
57 /*END*/
59 typedef enum
60 {
61 STT_SUCCESS = 0,
62 STT_FAILED = 1,
63 STT_UNAUTH = 2,
64 STT_INVALID = 3,
65 STT_NOSUCHSOURCE = 4,
66 STT_INVALIDTS = 5,
67 STT_NOTENABLED = 6,
68 STT_BADSUBNET = 7,
69 STT_ACCESSALLOWED = 8,
70 STT_ACCESSDENIED = 9,
71 STT_NOHOSTACCESS = 10,
72 STT_SOURCEALREADYKNOWN = 11,
73 STT_TOOMANYSOURCES = 12,
74 STT_NORTC = 13,
75 STT_BADRTCFILE = 14,
76 STT_INACTIVE = 15,
77 STT_BADSAMPLE = 16,
78 STT_INVALIDAF = 17,
79 STT_BADPKTVERSION = 18,
80 STT_BADPKTLENGTH = 19,
81 } eChrony_Status;
83 static int g_is_connected = 0;
84 static int g_chrony_socket = -1;
85 static time_t g_chrony_timeout = 0;
86 static char *g_chrony_host = NULL;
87 static char *g_chrony_port = NULL;
88 static uint32_t g_chrony_seq = 0;
89 //static char ntpd_port[16];
91 typedef struct ATTRIB_PACKED
92 {
93 uint32_t f_n_sources;
94 } tChrony_N_Sources;
96 typedef struct ATTRIB_PACKED
97 {
98 int32_t f_index;
99 uint8_t f_dummy0[44];
100 } tChrony_Req_Source_data;
102 typedef struct ATTRIB_PACKED
103 {
104 int32_t f_index;
105 uint8_t f_dummy0[56];
106 } tChrony_Req_Source_stats;
108 #define IPV6_STR_MAX_SIZE 40
109 typedef struct ATTRIB_PACKED
110 {
111 union
112 {
113 uint32_t ip4;
114 uint8_t ip6[16];
115 } addr;
116 uint16_t f_family;
117 } tChrony_IPAddr;
119 typedef struct ATTRIB_PACKED
120 {
121 tChrony_IPAddr addr;
122 int16_t f_poll;
123 uint16_t f_stratum;
124 uint16_t f_state;
125 uint16_t f_mode;
126 uint16_t f_flags;
127 uint16_t dummy; //FIXME: Strange dummy space. Needed on gcc 4.8.3 on x86_64
128 uint16_t f_reachability;
130 uint32_t f_since_sample;
131 Float f_origin_latest_meas;
132 Float f_latest_meas;
133 Float f_latest_meas_err;
134 } tChrony_Resp_Source_data;
136 typedef struct ATTRIB_PACKED
137 {
138 uint32_t f_ref_id;
139 tChrony_IPAddr addr;
140 uint16_t dummy; //FIXME: Strange dummy space. Needed on gcc 4.8.3 on x86_64
141 uint32_t f_n_samples; //Number of measurements done
142 uint32_t f_n_runs; //How many measurements to come
143 uint32_t f_span_seconds; //For how long we're measuring
144 Float f_rtc_seconds_fast;
145 Float f_rtc_gain_rate_ppm; //Estimated relative frequency error
146 Float f_skew_ppm; //Clock skew
147 Float f_est_offset; //Estimated offset of source
148 Float f_est_offset_err; //Error of estimation
149 } tChrony_Resp_Source_stats;
152 typedef struct ATTRIB_PACKED
153 {
154 struct
155 {
156 uint8_t f_version;
157 uint8_t f_type;
158 uint8_t f_dummy0;
159 uint8_t f_dummy1;
160 uint16_t f_cmd;
161 uint16_t f_cmd_try;
162 uint32_t f_seq;
164 uint32_t f_dummy2;
165 uint32_t f_dummy3;
166 } header; //Packed: 20Bytes
167 union
168 {
169 tChrony_N_Sources n_sources; //Packed: 8 Bytes
170 tChrony_Req_Source_data source_data;
171 tChrony_Req_Source_stats source_stats;
172 } body;
173 uint8_t padding[4+16]; //Padding to match minimal response size
174 } tChrony_Request;
176 typedef struct ATTRIB_PACKED
177 {
178 struct
179 {
180 uint8_t f_version;
181 uint8_t f_type;
182 uint8_t f_dummy0;
183 uint8_t f_dummy1;
184 uint16_t f_cmd;
185 uint16_t f_reply;
186 uint16_t f_status;
187 uint16_t f_dummy2;
188 uint16_t f_dummy3;
189 uint16_t f_dummy4;
190 uint32_t f_seq;
191 uint32_t f_dummy5;
192 uint32_t f_dummy6;
193 } header; //Packed: 24 Bytes
195 /*uint32_t EOR;*/
197 union
198 {
199 tChrony_N_Sources n_sources;
200 tChrony_Resp_Source_data source_data;
201 tChrony_Resp_Source_stats source_stats;
202 } body;
204 uint8_t padding[1024];
205 } tChrony_Response;
207 /*****************************************************************************/
208 /* Internal functions */
209 /*****************************************************************************/
210 /* Code from: http://long.ccaba.upc.edu/long/045Guidelines/eva/ipv6.html#daytimeClient6 */
211 /*BEGIN*/
212 static int
213 connect_client (const char *hostname,
214 const char *service,
215 int family,
216 int socktype)
217 {
218 struct addrinfo hints, *res, *ressave;
219 int n, sockfd;
221 memset(&hints, 0, sizeof(struct addrinfo));
223 hints.ai_family = family;
224 hints.ai_socktype = socktype;
226 n = getaddrinfo(hostname, service, &hints, &res);
228 if (n <0)
229 {
230 ERROR ("chrony plugin: getaddrinfo error:: [%s]", gai_strerror(n));
231 return -1;
232 }
234 ressave = res;
236 sockfd=-1;
237 while (res)
238 {
239 sockfd = socket(res->ai_family,
240 res->ai_socktype,
241 res->ai_protocol);
243 if (!(sockfd < 0))
244 {
245 if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
246 {
247 break;
248 }
250 close(sockfd);
251 sockfd=-1;
252 }
253 res=res->ai_next;
254 }
256 freeaddrinfo(ressave);
257 return sockfd;
258 }
259 /*Code originally from: https://github.com/mlichvar/chrony/blob/master/util.c */
260 /*char * UTI_IPToString(IPAddr *addr)*/
261 char * niptoha(const tChrony_IPAddr *addr,char *p_buf, size_t p_buf_size)
262 {
263 unsigned long a, b, c, d, ip;
264 const uint8_t *ip6;
266 switch (ntohs(addr->f_family))
267 {
268 case IPADDR_UNSPEC:
269 snprintf(p_buf, p_buf_size, "[UNSPEC]");
270 break;
271 case IPADDR_INET4:
272 ip = ntohl(addr->addr.ip4);
273 a = (ip>>24) & 0xff;
274 b = (ip>>16) & 0xff;
275 c = (ip>> 8) & 0xff;
276 d = (ip>> 0) & 0xff;
277 snprintf(p_buf, p_buf_size, "%ld.%ld.%ld.%ld", a, b, c, d);
278 break;
279 case IPADDR_INET6:
280 ip6 = addr->addr.ip6;
281 #if 0
282 //FIXME: Detect little endian systems
283 snprintf(p_buf, p_buf_size, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
284 ip6[0], ip6[1], ip6[2], ip6[3], ip6[4], ip6[5], ip6[6], ip6[7],
285 ip6[8], ip6[9], ip6[10], ip6[11], ip6[12], ip6[13], ip6[14], ip6[15]);
286 #else
287 snprintf(p_buf, p_buf_size, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
288 ip6[5], ip6[4], ip6[3], ip6[2], ip6[1], ip6[0], ip6[9], ip6[8],
289 ip6[7], ip6[6], ip6[5], ip6[4], ip6[3], ip6[2], ip6[1], ip6[0]);
290 #endif
291 break;
292 default:
293 snprintf(p_buf, p_buf_size, "[UNKNOWN]");
294 }
295 return p_buf;
296 }
297 /*END*/
299 static int chrony_set_timeout()
300 {
301 struct timeval tv;
302 tv.tv_sec = g_chrony_timeout;
303 tv.tv_usec = 0;
305 assert(g_chrony_socket>=0);
306 if (setsockopt(g_chrony_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval)) < 0)
307 {
308 return (1);
309 }
310 return (0);
311 }
313 static int chrony_connect()
314 {
315 if (g_chrony_host == NULL)
316 {
317 g_chrony_host = strdup(CHRONY_DEFAULT_HOST);
318 }
319 if (g_chrony_port == NULL)
320 {
321 g_chrony_port = strdup(CHRONY_DEFAULT_PORT);
322 }
323 if (g_chrony_timeout <= 0)
324 {
325 g_chrony_timeout = CHRONY_DEFAULT_TIMEOUT;
326 }
329 DEBUG("chrony plugin: Connecting to %s:%s", g_chrony_host, g_chrony_port);
330 int socket = connect_client(g_chrony_host, g_chrony_port, AF_UNSPEC, SOCK_DGRAM);
331 if (socket < 0)
332 {
333 ERROR ("chrony plugin: Error connecting to daemon. Errno = %d", errno);
334 return (1);
335 }
336 //TODO: Set timeouts!
337 DEBUG("chrony plugin: Connected");
338 g_chrony_socket = socket;
340 if (chrony_set_timeout())
341 {
342 ERROR ("chrony plugin: Error setting timeout to %lds. Errno = %d", g_chrony_timeout, errno);
343 return (1);
344 }
345 return (0);
346 }
348 static int chrony_send_request(const tChrony_Request *p_req, size_t p_req_size)
349 {
350 if (send(g_chrony_socket,p_req,p_req_size,0) < 0)
351 {
352 ERROR ("chrony plugin: Error sending packet. Errno = %d", errno);
353 return (1);
354 } else {
355 return (0);
356 }
357 }
359 static int chrony_recv_response(tChrony_Response *p_resp, size_t p_resp_max_size, size_t *p_resp_size)
360 {
361 ssize_t rc = recv(g_chrony_socket,p_resp,p_resp_max_size,0);
362 if (rc <= 0)
363 {
364 ERROR ("chrony plugin: Error receiving packet. Errno = %d", errno);
365 return (1);
366 } else {
367 #if 0
368 if (rc < p_resp_max_size)
369 {
370 ERROR ("chrony plugin: Received too small response packet. (Should: %ld, was: %ld)", p_resp_max_size, rc);
371 return (1);
372 }
373 #endif
374 *p_resp_size = rc;
375 return (0);
376 }
377 }
379 static int chrony_query(int p_command, tChrony_Request *p_req, tChrony_Response *p_resp, size_t *p_resp_size)
380 {
381 /* Check connection. We simply perform one try as collectd already handles retries */
382 assert(p_req);
383 assert(p_resp);
384 assert(p_resp_size);
385 if (g_is_connected == 0)
386 {
387 if (chrony_connect() == 0)
388 {
389 g_is_connected = 1;
390 } else {
391 ERROR ("chrony plugin: Unable to connect. Errno = %d", errno);
392 return 1;
393 }
394 }
397 do
398 {
399 int valid_command = 0;
400 size_t req_size = sizeof(p_req->header) + sizeof(p_req->padding);
401 size_t resp_size = sizeof(p_resp->header);
402 uint16_t resp_code = RPY_NULL;
403 switch (p_command)
404 {
405 case REQ_N_SOURCES:
406 req_size += sizeof(p_req->body.n_sources);
407 resp_size += sizeof(p_resp->body.n_sources);
408 resp_code = RPY_N_SOURCES;
409 valid_command = 1;
410 break;
411 case REQ_SOURCE_DATA:
412 req_size += sizeof(p_req->body.source_data);
413 resp_size += sizeof(p_resp->body.source_data);
414 resp_code = RPY_SOURCE_DATA;
415 valid_command = 1;
416 break;
417 case REQ_SOURCE_STATS:
418 req_size += sizeof(p_req->body.source_stats);
419 resp_size += sizeof(p_resp->body.source_stats);
420 resp_code = RPY_SOURCE_STATS;
421 valid_command = 1;
422 break;
423 default:
424 ERROR ("chrony plugin: Unknown request command (Was: %d)", p_command);
425 break;
426 }
428 if (valid_command == 0)
429 {
430 break;
431 }
433 p_req->header.f_cmd = htons(p_command);
434 p_req->header.f_cmd_try = 0;
435 p_req->header.f_seq = htonl(g_chrony_seq++);
437 DEBUG("chrony plugin: Sending request");
438 if (chrony_send_request(p_req,req_size) != 0)
439 {
440 break;
441 }
443 DEBUG("chrony plugin: Waiting for response");
444 if (chrony_recv_response(p_resp,resp_size,p_resp_size) != 0)
445 {
446 break;
447 }
448 DEBUG("chrony plugin: Received response: .version = %u, .type = %u, .cmd = %u, .reply = %u, .status = %u, .seq = %u",p_resp->header.f_version,p_resp->header.f_type,ntohs(p_resp->header.f_cmd),ntohs(p_resp->header.f_reply),ntohs(p_resp->header.f_status),ntohl(p_resp->header.f_seq));
450 if (p_resp->header.f_version != p_req->header.f_version)
451 {
452 ERROR("chrony plugin: Wrong protocol version (Was: %d, expected: %d)", p_resp->header.f_version, p_req->header.f_version);
453 return 1;
454 }
455 if (p_resp->header.f_type != PKT_TYPE_CMD_REPLY)
456 {
457 ERROR("chrony plugin: Wrong packet type (Was: %d, expected: %d)", p_resp->header.f_type, PKT_TYPE_CMD_REPLY);
458 return 1;
459 }
460 if (p_resp->header.f_seq != p_req->header.f_seq)
461 {
462 //FIXME: Implement sequence number handling
463 ERROR("chrony plugin: Unexpected sequence number (Was: %d, expected: %d)", p_resp->header.f_seq, p_req->header.f_seq);
464 return 1;
465 }
466 if (p_resp->header.f_cmd != p_req->header.f_cmd)
467 {
468 ERROR("chrony plugin: Wrong reply command (Was: %d, expected: %d)", p_resp->header.f_cmd, p_req->header.f_cmd);
469 return 1;
470 }
472 if (ntohs(p_resp->header.f_reply) != resp_code)
473 {
474 ERROR("chrony plugin: Wrong reply code (Was: %d, expected: %d)", ntohs(p_resp->header.f_reply), p_command);
475 return 1;
476 }
478 switch (p_resp->header.f_status)
479 {
480 case STT_SUCCESS:
481 DEBUG("chrony plugin: Reply packet status STT_SUCCESS");
482 break;
483 default:
484 ERROR("chrony plugin: Reply packet contains error status: %d (expected: %d)", p_resp->header.f_status, STT_SUCCESS);
485 return (1);
486 }
487 return (0);
488 } while (0);
490 return (1);
491 }
493 static void chrony_init_req(tChrony_Request *p_req)
494 {
495 DEBUG("chrony plugin: Clearing %ld bytes",sizeof(*p_req));
496 memset(p_req,0,sizeof(*p_req));
497 p_req->header.f_version = PROTO_VERSION_NUMBER;
498 p_req->header.f_type = PKT_TYPE_CMD_REQUEST;
499 p_req->header.f_dummy0 = 0;
500 p_req->header.f_dummy1 = 0;
501 p_req->header.f_dummy2 = 0;
502 p_req->header.f_dummy3 = 0;
503 }
505 //Code from: https://github.com/mlichvar/chrony/blob/master/util.c (GPLv2)
506 /*BEGIN*/
507 #define FLOAT_EXP_BITS 7
508 #define FLOAT_EXP_MIN (-(1 << (FLOAT_EXP_BITS - 1)))
509 #define FLOAT_EXP_MAX (-FLOAT_EXP_MIN - 1)
510 #define FLOAT_COEF_BITS ((int)sizeof (int32_t) * 8 - FLOAT_EXP_BITS)
511 #define FLOAT_COEF_MIN (-(1 << (FLOAT_COEF_BITS - 1)))
512 #define FLOAT_COEF_MAX (-FLOAT_COEF_MIN - 1)
514 //double UTI_FloatNetworkToHost(Float f)
515 double ntohf(Float f)
516 {
517 int32_t exp, coef, x;
519 x = ntohl(f.f);
520 exp = (x >> FLOAT_COEF_BITS) - FLOAT_COEF_BITS;
521 coef = x << FLOAT_EXP_BITS >> FLOAT_EXP_BITS;
522 return coef * pow(2.0, exp);
523 }
524 /*END*/
526 /* Code from: collectd/src/ntpd.c (MIT) */
527 /*BEGIN*/
528 static void chrony_push_data(char *type, char *type_inst, double value)
529 {
530 value_t values[1];
531 value_list_t vl = VALUE_LIST_INIT;
533 values[0].gauge = value;
535 vl.values = values;
536 vl.values_len = 1;
537 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
538 sstrncpy (vl.plugin, "chrony", sizeof (vl.plugin));
539 sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
540 sstrncpy (vl.type, type, sizeof (vl.type));
541 sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
543 plugin_dispatch_values (&vl);
544 }
545 /*END*/
547 /*****************************************************************************/
548 /* Exported functions */
549 /*****************************************************************************/
550 static int chrony_config(const char *p_key, const char *p_value)
551 {
552 assert(p_key);
553 assert(p_value);
554 //Parse config variables
555 if (strcasecmp(p_key, "Host") == 0)
556 {
557 if (g_chrony_host != NULL)
558 {
559 free (g_chrony_host);
560 }
561 if ((g_chrony_host = strdup (p_value)) == NULL)
562 {
563 ERROR ("chrony plugin: Error duplicating host name");
564 return (1);
565 }
566 } else if (strcasecmp(p_key, "Port") == 0)
567 {
568 if (g_chrony_port != NULL)
569 {
570 free (g_chrony_port);
571 }
572 if ((g_chrony_port = strdup (p_value)) == NULL)
573 {
574 ERROR ("chrony plugin: Error duplicating port name");
575 return (1);
576 }
577 } else if (strcasecmp(p_key, "Timeout") == 0)
578 {
579 time_t tosec = strtol(p_value,NULL,0);
580 g_chrony_timeout = tosec;
581 } else {
582 WARNING("chrony plugin: Unknown configuration variable: %s %s",p_key,p_value);
583 return (1);
584 }
585 return (0);
586 }
588 static int chrony_read (void)
589 {
590 //plugin_dispatch_values (&vl);
591 int status,now_src;
592 char ip_addr_str0[ IPV6_STR_MAX_SIZE];
593 char ip_addr_str1[IPV6_STR_MAX_SIZE];
595 size_t chrony_resp_size;
596 tChrony_Request chrony_req,chrony_req1;
597 tChrony_Response chrony_resp,chrony_resp1;
599 DEBUG("chrony plugin: Requesting data");
600 chrony_init_req(&chrony_req);
601 status = chrony_query (REQ_N_SOURCES, &chrony_req, &chrony_resp, &chrony_resp_size);
602 if (status != 0)
603 {
604 ERROR ("chrony plugin: chrony_query (REQ_N_SOURCES) failed with status %i", status);
605 return (status);
606 }
608 int n_sources = ntohl(chrony_resp.body.n_sources.f_n_sources);
609 DEBUG("chrony plugin: Getting data of %d clock sources", n_sources);
611 for (now_src = 0; now_src < n_sources; ++now_src)
612 {
613 chrony_init_req(&chrony_req);
614 chrony_init_req(&chrony_req1);
615 chrony_req.body.source_data.f_index = htonl(now_src);
616 chrony_req1.body.source_stats.f_index = htonl(now_src);
617 status = chrony_query(REQ_SOURCE_DATA, &chrony_req, &chrony_resp, &chrony_resp_size);
618 if (status != 0)
619 {
620 ERROR ("chrony plugin: chrony_query (REQ_SOURCE_DATA) failed with status %i", status);
621 return (status);
622 }
623 status = chrony_query(REQ_SOURCE_STATS, &chrony_req1, &chrony_resp1, &chrony_resp_size);
624 if (status != 0)
625 {
626 ERROR ("chrony plugin: chrony_query (REQ_SOURCE_STATS) failed with status %i", status);
627 return (status);
628 }
629 memset(ip_addr_str0,0,sizeof(ip_addr_str0));
630 niptoha(&chrony_resp.body.source_data.addr,ip_addr_str0,sizeof(ip_addr_str0));
631 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",
632 now_src,
633 ip_addr_str0,
634 ntohs(chrony_resp.body.source_data.f_poll),
635 ntohs(chrony_resp.body.source_data.f_stratum),
636 ntohs(chrony_resp.body.source_data.f_state),
637 ntohs(chrony_resp.body.source_data.f_mode),
638 ntohs(chrony_resp.body.source_data.f_flags),
639 ntohs(chrony_resp.body.source_data.f_reachability),
640 ntohl(chrony_resp.body.source_data.f_since_sample),
641 ntohf(chrony_resp.body.source_data.f_origin_latest_meas),
642 ntohf(chrony_resp.body.source_data.f_latest_meas),
643 ntohf(chrony_resp.body.source_data.f_latest_meas_err));
644 #if 1
645 memset(ip_addr_str1,0,sizeof(ip_addr_str1));
646 niptoha(&chrony_resp1.body.source_stats.addr,ip_addr_str1,sizeof(ip_addr_str1));
647 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",
648 now_src,
649 ip_addr_str1,
650 ntohl(chrony_resp1.body.source_stats.f_ref_id),
651 ntohl(chrony_resp1.body.source_stats.f_n_samples),
652 ntohl(chrony_resp1.body.source_stats.f_n_runs),
653 ntohl(chrony_resp1.body.source_stats.f_span_seconds),
654 ntohf(chrony_resp1.body.source_stats.f_rtc_seconds_fast),
655 ntohf(chrony_resp1.body.source_stats.f_rtc_gain_rate_ppm),
656 ntohf(chrony_resp1.body.source_stats.f_skew_ppm),
657 ntohf(chrony_resp1.body.source_stats.f_est_offset),
658 ntohf(chrony_resp1.body.source_stats.f_est_offset_err));
659 #endif
660 #if 1
661 chrony_push_data("clock_stratum", ip_addr_str0,ntohs(chrony_resp.body.source_data.f_stratum));
662 chrony_push_data("clock_state", ip_addr_str0,ntohs(chrony_resp.body.source_data.f_state));
663 chrony_push_data("clock_mode", ip_addr_str0,ntohs(chrony_resp.body.source_data.f_mode));
664 chrony_push_data("clock_reachability",ip_addr_str0,ntohs(chrony_resp.body.source_data.f_reachability));
665 chrony_push_data("clock_last_meas", ip_addr_str0,ntohs(chrony_resp.body.source_data.f_since_sample));
666 chrony_push_data("clock_skew_ppm", ip_addr_str1,ntohf(chrony_resp1.body.source_stats.f_skew_ppm));
667 chrony_push_data("frequency_error", ip_addr_str1,ntohf(chrony_resp1.body.source_stats.f_rtc_gain_rate_ppm)); //unit: ppm
668 chrony_push_data("time_offset", ip_addr_str1,ntohf(chrony_resp1.body.source_stats.f_est_offset)); //unit: s
669 #endif
670 }
671 return (0);
672 }
674 static int chrony_shutdown()
675 {
676 if (g_is_connected != 0)
677 {
678 close(g_chrony_socket);
679 g_is_connected = 0;
680 }
681 if (g_chrony_host != NULL)
682 {
683 free (g_chrony_host);
684 g_chrony_host = NULL;
685 }
686 if (g_chrony_port != NULL)
687 {
688 free (g_chrony_port);
689 g_chrony_port = NULL;
690 }
691 return (0);
692 }
694 void module_register (void)
695 {
696 plugin_register_config ("chrony", chrony_config, g_config_keys, g_config_keys_num);
697 plugin_register_read ("chrony", chrony_read);
698 plugin_register_shutdown ("chrony", chrony_shutdown);
699 }