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 */
7 /* getaddrinfo */
8 #include <sys/types.h>
9 #include <sys/socket.h>
10 #include <netdb.h>
12 #include "collectd.h"
13 #include "common.h" /* auxiliary functions */
14 #include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
16 static const char *g_config_keys[] =
17 {
18 "Host",
19 "Port",
20 };
22 static int g_config_keys_num = STATIC_ARRAY_SIZE (g_config_keys);
24 # define CHRONY_DEFAULT_HOST "localhost"
25 # define CHRONY_DEFAULT_PORT "323"
27 /* Copied from chrony/candm.h */
28 #define PROTO_VERSION_NUMBER 6
30 #define REQ_N_SOURCES 14
31 #define REQ_SOURCE_DATA 15
33 #define PKT_TYPE_CMD_REQUEST 1
34 #define PKT_TYPE_CMD_REPLY 2
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];
44 typedef struct
45 {
46 uint32_t f_n_sources;
47 int32_t EOR;
48 } tChrony_Req_N_Sources;
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;
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;
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;
90 union
91 {
92 } data;
93 } tChrony_Response;
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;
108 memset(&hints, 0, sizeof(struct addrinfo));
110 hints.ai_family = family;
111 hints.ai_socktype = socktype;
113 n = getaddrinfo(hostname, service, &hints, &res);
115 if (n <0)
116 {
117 ERROR ("chrony plugin: getaddrinfo error:: [%s]", gai_strerror(n));
118 return -1;
119 }
121 ressave = res;
123 sockfd=-1;
124 while (res)
125 {
126 sockfd = socket(res->ai_family,
127 res->ai_socktype,
128 res->ai_protocol);
130 if (!(sockfd < 0))
131 {
132 if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
133 {
134 break;
135 }
137 close(sockfd);
138 sockfd=-1;
139 }
140 res=res->ai_next;
141 }
143 freeaddrinfo(ressave);
144 return sockfd;
145 }
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 }
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 }
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 }
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 }
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 }
219 if (valid_command == 0)
220 {
221 break;
222 }
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++;
228 DEBUG("chrony plugin: Sending request");
229 if (chrony_send_request(p_req,req_size) != 0)
230 {
231 break;
232 }
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");
241 return (0);
242 } while (0);
244 return (1);
245 }
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 }
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 }
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;
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 }
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 }
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 }