Code clean up and better structuring
[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                 *p_resp_size = rc;
372                 return 0;
373         }
374 }
375
376 static int chrony_query(const int p_command, tChrony_Request *p_req, tChrony_Response *p_resp, size_t *p_resp_size)
377 {
378         /* Check connection. We simply perform one try as collectd already handles retries */
379         assert(p_req);
380         assert(p_resp);
381         assert(p_resp_size);
382
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
486                 //Good result
487                 return 0;
488         } while (0);
489         
490         //Some error occured
491         return 1;
492 }
493
494 static void chrony_init_req(tChrony_Request *p_req)
495 {
496         DEBUG("chrony plugin: Clearing %ld bytes",sizeof(*p_req));
497         memset(p_req,0,sizeof(*p_req));
498         p_req->header.f_version = PROTO_VERSION_NUMBER;
499         p_req->header.f_type    = PKT_TYPE_CMD_REQUEST;
500         p_req->header.f_dummy0  = 0;
501         p_req->header.f_dummy1  = 0;
502         p_req->header.f_dummy2  = 0;
503         p_req->header.f_dummy3  = 0;
504 }
505
506 /* Code from: https://github.com/mlichvar/chrony/blob/master/util.c (GPLv2) */
507 /*BEGIN*/
508 #define FLOAT_EXP_BITS 7
509 #define FLOAT_EXP_MIN (-(1 << (FLOAT_EXP_BITS - 1)))
510 #define FLOAT_EXP_MAX (-FLOAT_EXP_MIN - 1)
511 #define FLOAT_COEF_BITS ((int)sizeof (int32_t) * 8 - FLOAT_EXP_BITS)
512 #define FLOAT_COEF_MIN (-(1 << (FLOAT_COEF_BITS - 1)))
513 #define FLOAT_COEF_MAX (-FLOAT_COEF_MIN - 1)
514
515 /* double UTI_FloatNetworkToHost(Float f) */
516 double ntohf(Float f)
517 {
518   int32_t exp, coef, x;
519
520   x = ntohl(f.f);
521   exp = (x >> FLOAT_COEF_BITS) - FLOAT_COEF_BITS;
522   coef = x << FLOAT_EXP_BITS >> FLOAT_EXP_BITS;
523   return coef * pow(2.0, exp);
524 }
525 /*END*/
526
527 /* Code from: collectd/src/ntpd.c (MIT) */
528 /*BEGIN*/
529 static void chrony_push_data(char *type, char *type_inst, double value)
530 {
531         value_t values[1];
532         value_list_t vl = VALUE_LIST_INIT;
533
534         values[0].gauge = value;
535
536         vl.values = values;
537         vl.values_len = 1;
538         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
539         sstrncpy (vl.plugin, "chrony", sizeof (vl.plugin));
540         sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
541         sstrncpy (vl.type, type, sizeof (vl.type));
542         sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
543
544         plugin_dispatch_values (&vl);
545 }
546 /*END*/
547
548 /*****************************************************************************/
549 /* Exported functions */
550 /*****************************************************************************/
551 static int chrony_config(const char *p_key, const char *p_value)
552 {
553         assert(p_key);
554         assert(p_value);
555         /* Parse config variables */
556         if (strcasecmp(p_key, "Host") == 0)
557         {
558                 if (g_chrony_host != NULL)
559                 {
560                         free (g_chrony_host);
561                 }
562                 if ((g_chrony_host = strdup (p_value)) == NULL)
563                 {
564                         ERROR ("chrony plugin: Error duplicating host name");
565                         return 1;
566                 }
567         } else if (strcasecmp(p_key, "Port") == 0)
568         {
569                 if (g_chrony_port != NULL)
570                 {
571                         free (g_chrony_port);
572                 }
573                 if ((g_chrony_port = strdup (p_value)) == NULL)
574                 {
575                         ERROR ("chrony plugin: Error duplicating port name");
576                         return 1;
577                 }
578         } else if (strcasecmp(p_key, "Timeout") == 0)
579         {
580                 time_t tosec = strtol(p_value,NULL,0);
581                 g_chrony_timeout = tosec;
582         } else {
583                 WARNING("chrony plugin: Unknown configuration variable: %s %s",p_key,p_value);
584                 return 1;
585         }
586         return 0;
587 }
588
589 static int chrony_request_sources_count(unsigned int *p_count)
590 {
591         int rc;
592         size_t chrony_resp_size;
593         tChrony_Request  chrony_req;
594         tChrony_Response chrony_resp;
595
596         DEBUG("chrony plugin: Requesting data");
597         chrony_init_req(&chrony_req);
598         rc = chrony_query (REQ_N_SOURCES, &chrony_req, &chrony_resp, &chrony_resp_size);
599         if (rc != 0)
600         {
601                 ERROR ("chrony plugin: chrony_query (REQ_N_SOURCES) failed with status %i", rc);
602                 return rc;
603         }
604         
605         *p_count = ntohl(chrony_resp.body.n_sources.f_n_sources);
606         DEBUG("chrony plugin: Getting data of %d clock sources", *p_count);
607         return 0;
608 }
609
610 static int chrony_request_source_data(int p_src_idx)
611 {
612         //Source data request
613         size_t chrony_resp_size;
614         tChrony_Request  chrony_req;
615         tChrony_Response chrony_resp;
616         char src_addr[IPV6_STR_MAX_SIZE];
617
618         chrony_init_req(&chrony_req);
619         chrony_req.body.source_data.f_index  = htonl(p_src_idx);
620         int rc = chrony_query(REQ_SOURCE_DATA, &chrony_req, &chrony_resp, &chrony_resp_size);
621         if (rc != 0)
622         {
623                 ERROR ("chrony plugin: chrony_query (REQ_SOURCE_DATA) failed with status %i", rc);
624                 return rc;
625         }
626         memset(src_addr, 0, sizeof(src_addr));
627         niptoha(&chrony_resp.body.source_data.addr, src_addr, sizeof(src_addr));
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                 p_src_idx,
630                 src_addr,
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         );
642         chrony_push_data("clock_stratum",     src_addr,ntohs(chrony_resp.body.source_data.f_stratum));
643         chrony_push_data("clock_state",       src_addr,ntohs(chrony_resp.body.source_data.f_state));
644         chrony_push_data("clock_mode",        src_addr,ntohs(chrony_resp.body.source_data.f_mode));
645         chrony_push_data("clock_reachability",src_addr,ntohs(chrony_resp.body.source_data.f_reachability));
646         chrony_push_data("clock_last_meas",   src_addr,ntohs(chrony_resp.body.source_data.f_since_sample));
647         return 0;
648 }
649
650
651 static int chrony_request_source_stats(int p_src_idx)
652 {
653         //Source stats 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];
658
659         chrony_init_req(&chrony_req);
660         chrony_req.body.source_stats.f_index = htonl(p_src_idx);
661         int rc = chrony_query(REQ_SOURCE_STATS, &chrony_req, &chrony_resp, &chrony_resp_size);
662         if (rc != 0)
663         {
664                 ERROR ("chrony plugin: chrony_query (REQ_SOURCE_STATS) failed with status %i", rc);
665                 return rc;
666         }
667
668         memset(src_addr, 0, sizeof(src_addr));
669         niptoha(&chrony_resp.body.source_stats.addr, src_addr, sizeof(src_addr));
670         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",
671                 p_src_idx,
672                 src_addr,
673                 ntohl(chrony_resp.body.source_stats.f_ref_id),
674                 ntohl(chrony_resp.body.source_stats.f_n_samples),
675                 ntohl(chrony_resp.body.source_stats.f_n_runs),
676                 ntohl(chrony_resp.body.source_stats.f_span_seconds),
677                 ntohf(chrony_resp.body.source_stats.f_rtc_seconds_fast),
678                 ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm),
679                 ntohf(chrony_resp.body.source_stats.f_skew_ppm),
680                 ntohf(chrony_resp.body.source_stats.f_est_offset),
681                 ntohf(chrony_resp.body.source_stats.f_est_offset_err)
682         );
683         chrony_push_data("clock_skew_ppm",    src_addr,ntohf(chrony_resp.body.source_stats.f_skew_ppm));
684         chrony_push_data("frequency_error",   src_addr,ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm)); /* unit: ppm */
685         chrony_push_data("time_offset",       src_addr,ntohf(chrony_resp.body.source_stats.f_est_offset)); /* unit: s */
686         return 0;
687 }
688
689 static int chrony_read (void)
690 {
691         //Get number of time sources, then check every source for status
692         int  rc;
693
694         unsigned int now_src, n_sources;
695         rc = chrony_request_sources_count(&n_sources);
696         if (rc != 0)
697         {
698                 return rc;
699         }
700
701         for (now_src = 0; now_src < n_sources; ++now_src)
702         {
703                 rc = chrony_request_source_data(now_src);
704                 if (rc != 0)
705                 {
706                         return rc;
707                 }
708
709                 rc = chrony_request_source_stats(now_src);
710                 if (rc != 0)
711                 {
712                         return rc;
713                 }
714         }
715         return 0;
716 }
717
718 static int chrony_shutdown()
719 {
720         if (g_is_connected != 0)
721         {
722                 close(g_chrony_socket);
723                 g_is_connected = 0;
724         }
725         if (g_chrony_host != NULL)
726         {
727                 free (g_chrony_host);
728                 g_chrony_host = NULL;
729         }
730         if (g_chrony_port != NULL)
731         {
732                 free (g_chrony_port);
733                 g_chrony_port = NULL;
734         }
735         return 0;
736 }
737
738 void module_register (void)
739 {
740         plugin_register_config(  "chrony", chrony_config, g_config_keys, g_config_keys_num);
741         plugin_register_read(    "chrony", chrony_read);
742         plugin_register_shutdown("chrony", chrony_shutdown);
743 }
744