Added debugging code
[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 };
21
22 static int g_config_keys_num = STATIC_ARRAY_SIZE (g_config_keys);
23
24 # define CHRONY_DEFAULT_HOST "localhost"
25 # define CHRONY_DEFAULT_PORT "323"
26
27 /* Copied from chrony/candm.h */
28 #define PROTO_VERSION_NUMBER 6
29
30 #define REQ_N_SOURCES 14
31 #define REQ_SOURCE_DATA 15
32
33 #define PKT_TYPE_CMD_REQUEST 1
34 #define PKT_TYPE_CMD_REPLY 2
35
36
37 static int g_is_connected = 0;
38 static int g_chrony_socket = -1;
39 static char *g_chrony_host = NULL;
40 static char *g_chrony_port = NULL;
41 static uint32_t g_chrony_seq = 0;
42 //static char  ntpd_port[16];
43
44 typedef struct
45 {
46         uint32_t f_n_sources;
47         int32_t EOR;
48 } tChrony_Req_N_Sources;
49
50 typedef struct
51 {
52         struct
53         {
54                 uint8_t f_version;
55                 uint8_t f_type;
56                 uint8_t f_dummy0;
57                 uint8_t f_dummy1;
58                 uint16_t f_cmd;
59                 uint16_t f_cmd_try;
60                 uint32_t f_seq;
61
62                 uint32_t f_dummy2;
63                 uint32_t f_dummy3;
64         } header;
65         union
66         {
67                 tChrony_Req_N_Sources n_sources;
68         } body;
69 } tChrony_Request;
70
71 typedef struct
72 {
73         struct
74         {
75                 uint8_t f_version;
76                 uint8_t f_type;
77                 uint8_t f_dummy0;
78                 uint8_t f_dummy1;
79                 uint16_t f_cmd;
80                 uint16_t f_reply;
81                 uint16_t f_status;
82                 uint16_t f_dummy2;
83                 uint16_t f_dummy3;
84                 uint16_t f_dummy4;
85                 uint32_t f_seq;
86                 uint16_t f_dummy5;
87                 uint16_t f_dummy6;
88         } header;
89
90         union
91         {
92         } data;
93 } tChrony_Response;
94
95 /*****************************************************************************/
96 /* Internal functions */
97 /*****************************************************************************/
98 /* Code from: http://long.ccaba.upc.edu/long/045Guidelines/eva/ipv6.html#daytimeClient6 */
99 static int
100 connect_client (const char *hostname,
101                 const char *service,
102                 int         family,
103                 int         socktype)
104 {
105         struct addrinfo hints, *res, *ressave;
106         int n, sockfd;
107
108         memset(&hints, 0, sizeof(struct addrinfo));
109
110         hints.ai_family = family;
111         hints.ai_socktype = socktype;
112
113         n = getaddrinfo(hostname, service, &hints, &res);
114
115         if (n <0)
116         {
117                 ERROR ("chrony plugin: getaddrinfo error:: [%s]", gai_strerror(n));
118                 return -1;
119         }
120
121         ressave = res;
122
123         sockfd=-1;
124         while (res)
125         {
126                 sockfd = socket(res->ai_family,
127                 res->ai_socktype,
128                 res->ai_protocol);
129
130                 if (!(sockfd < 0))
131                 {
132                         if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
133                         {
134                                 break;
135                         }
136
137                         close(sockfd);
138                         sockfd=-1;
139                 }
140                 res=res->ai_next;
141         }
142
143         freeaddrinfo(ressave);
144         return sockfd;
145 }
146
147 static int chrony_connect()
148 {
149         DEBUG("chrony plugin: Connecting to %s:%s", g_chrony_host, g_chrony_port);
150         int socket = connect_client(g_chrony_host, g_chrony_port,  AF_UNSPEC, SOCK_DGRAM);
151         if (socket < 0)
152         {
153                 ERROR ("chrony plugin: Error connecting to daemon. Errno = %d", errno);
154                 return (1);
155         }
156         //TODO: Set timeouts!
157         DEBUG("chrony plugin: Connected");
158         g_chrony_socket = socket;
159         return (0);
160 }
161
162 static int chrony_send_request(const tChrony_Request *p_req, size_t p_req_size)
163 {
164         if (send(g_chrony_socket,p_req,p_req_size,0) < 0)
165         {
166                 ERROR ("chrony plugin: Error sending packet. Errno = %d", errno);
167                 return (1);
168         } else {
169                 return (0);
170         }
171 }
172
173 static int chrony_recv_response(tChrony_Response *p_resp, size_t p_resp_max_size, size_t *p_resp_size)
174 {
175         ssize_t rc = recv(g_chrony_socket,p_resp,p_resp_max_size,0);
176         if (rc <= 0)
177         {
178                 ERROR ("chrony plugin: Error receiving packet. Errno = %d", errno);
179                 return (1);
180         } else {
181                 *p_resp_size = rc;
182                 return (0);
183         }
184 }
185
186 static int chrony_query(int p_command, tChrony_Request *p_req, tChrony_Response *p_resp, size_t *p_resp_size)
187 {
188         /* Check connection. We simply perform one try as collectd already handles retries */
189         assert(p_req);
190         assert(p_resp);
191         assert(p_resp_size);
192         if (g_is_connected == 0)
193         {
194                 if (chrony_connect() == 0)
195                 {
196                         g_is_connected = 1;
197                 } else {
198                         ERROR ("chrony plugin: Unable to connect. Errno = %d", errno);
199                         return 1;
200                 }
201         }
202
203
204         do
205         {
206                 int valid_command = 0;
207                 size_t req_size = sizeof(p_req->header);
208                 size_t resp_size = sizeof(p_resp->header);
209                 switch (p_command)
210                 {
211                 case REQ_N_SOURCES:
212                         req_size += sizeof(p_req->body.n_sources);
213                         valid_command = 1;
214                         break;
215                 default:
216                         break;
217                 }
218
219                 if (valid_command == 0)
220                 {
221                         break;
222                 }
223
224                 p_req->header.f_cmd     = p_command;
225                 p_req->header.f_cmd_try = 0;
226                 p_req->header.f_seq     = g_chrony_seq++;
227                 
228                 DEBUG("chrony plugin: Sending request");
229                 if (chrony_send_request(p_req,req_size) != 0)
230                 {
231                         break;
232                 }
233
234                 DEBUG("chrony plugin: Waiting for response");
235                 if (chrony_recv_response(p_resp,resp_size,p_resp_size) != 0)
236                 {
237                         break;
238                 }
239                 DEBUG("chrony plugin: Received response");
240
241                 return (0);
242         } while (0);
243         
244         return (1);
245 }
246
247 static void chrony_init_req(tChrony_Request *p_req)
248 {
249         p_req->header.f_version = PROTO_VERSION_NUMBER;
250         p_req->header.f_type    = PKT_TYPE_CMD_REQUEST;
251         p_req->header.f_dummy0  = 0;
252         p_req->header.f_dummy1  = 0;
253         p_req->header.f_dummy2  = 0;
254         p_req->header.f_dummy3  = 0;
255 }
256
257
258 /*****************************************************************************/
259 /* Exported functions */
260 /*****************************************************************************/
261 static int chrony_config(const char *p_key, const char *p_value)
262 {
263         //Parse config variables
264         if (strcasecmp(p_key, "Host") == 0)
265         {
266                 if (g_chrony_host != NULL)
267                 {
268                         free (g_chrony_host);
269                 }
270                 if ((g_chrony_host = strdup (p_value)) == NULL)
271                 {
272                         ERROR ("chrony plugin: Error duplicating host name");
273                         return (1);
274                 }
275         } else if (strcasecmp(p_key, "Port") == 0)
276         {
277                 if (g_chrony_port != NULL)
278                 {
279                         free (g_chrony_port);
280                 }
281                 if ((g_chrony_port = strdup (p_value)) == NULL)
282                 {
283                         ERROR ("chrony plugin: Error duplicating port name");
284                         return (1);
285                 }
286         }
287         return (0);
288 }
289
290 static int chrony_read (void)
291 {
292         //plugin_dispatch_values (&vl);
293         int status;
294         tChrony_Request  chrony_req;
295         tChrony_Response chrony_resp;
296         size_t chrony_resp_size;
297
298         DEBUG("chrony plugin: Requesting data");
299         chrony_init_req(&chrony_req);
300         status = chrony_query (REQ_N_SOURCES, &chrony_req, &chrony_resp, &chrony_resp_size);
301         if (status != 0)
302         {
303                 ERROR ("chrony plugin: chrony_query (REQ_N_SOURCES) failed with status %i", status);
304                 return (status);
305         }
306         return (0);
307 }
308
309 static int chrony_shutdown()
310 {
311         if (g_is_connected != 0)
312         {
313                 close(g_chrony_socket);
314                 g_is_connected = 0;
315         }
316         if (g_chrony_host != NULL)
317         {
318                 free (g_chrony_host);
319                 g_chrony_host = NULL;
320         }
321         if (g_chrony_port != NULL)
322         {
323                 free (g_chrony_port);
324                 g_chrony_port = NULL;
325         }
326         return (0);
327 }
328
329 void module_register (void)
330 {
331         plugin_register_config ("chrony", chrony_config, g_config_keys, g_config_keys_num);
332         plugin_register_read ("chrony", chrony_read);
333         plugin_register_shutdown ("chrony", chrony_shutdown);
334 }