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