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