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