1 /*
2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 /**
18 * $Id: 77f0db091549300b963b199d3cb3aafc2b663d55 $
19 * @file collectd.c
20 * @brief Helper functions to enabled radsniff to talk to collectd
21 *
22 * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23 */
24 #include <assert.h>
25 #include <ctype.h>
26
27 #ifdef HAVE_COLLECTDC_H
28 #include <collectd/client.h>
29 #include <freeradius-devel/radsniff.h>
30
31 /** Copy a 64bit unsigned integer into a double
32 *
33 */
34 /*
35 static void _copy_uint64_to_double(UNUSED rs_t *conf, rs_stats_value_tmpl_t *tmpl)
36 {
37 assert(tmpl->src);
38 assert(tmpl->dst);
39
40 *((double *) tmpl->dst) = *((uint64_t *) tmpl->src);
41 }
42 */
43
44 /*
45 static void _copy_uint64_to_uint64(UNUSED rs_t *conf, rs_stats_value_tmpl_t *tmpl)
46 {
47 assert(tmpl->src);
48 assert(tmpl->dst);
49
50 *((uint64_t *) tmpl->dst) = *((uint64_t *) tmpl->src);
51 }
52 */
53
_copy_double_to_double(UNUSED rs_t * conf,rs_stats_value_tmpl_t * tmpl)54 static void _copy_double_to_double(UNUSED rs_t *conf, rs_stats_value_tmpl_t *tmpl)
55 {
56 assert(tmpl->src);
57 assert(tmpl->dst);
58
59 *((double *) tmpl->dst) = *((double*) tmpl->src);
60 }
61
62
63 /** Allocates a stats template which describes a single guage/counter
64 *
65 * This is just intended to simplify allocating a fairly complex memory structure
66 * src and dst pointers must be set
67 *
68 * @param ctx Context to allocate collectd struct in.
69 * @param conf Radsniff configuration.
70 * @param plugin_instance usually the type of packet (in our case).
71 * @param type string, the name of a collection of stats e.g. exchange
72 * @param type_instance the name of the counter/guage within the collection e.g. latency.
73 * @param stats structure to derive statistics from.
74 * @param values Value templates used to populate lcc_value_list.
75 * @return a new rs_stats_tmpl_t on success or NULL on failure.
76 */
rs_stats_collectd_init(TALLOC_CTX * ctx,rs_t * conf,char const * plugin_instance,char const * type,char const * type_instance,void * stats,rs_stats_value_tmpl_t const * values)77 static rs_stats_tmpl_t *rs_stats_collectd_init(TALLOC_CTX *ctx, rs_t *conf,
78 char const *plugin_instance,
79 char const *type, char const *type_instance,
80 void *stats,
81 rs_stats_value_tmpl_t const *values)
82 {
83 static char hostname[255];
84 static char fqdn[LCC_NAME_LEN];
85
86 size_t len;
87 int i;
88 char *p;
89
90 rs_stats_tmpl_t *tmpl;
91 lcc_value_list_t *value;
92
93 assert(conf);
94 assert(type);
95 assert(type_instance);
96
97 for (len = 0; values[len].src; len++) {} ;
98 assert(len > 0);
99
100 /*
101 * Initialise hostname once so we don't call gethostname every time
102 */
103 if (*fqdn == '\0') {
104 int ret;
105 struct addrinfo hints, *info = NULL;
106
107 if (gethostname(hostname, sizeof(hostname)) < 0) {
108 ERROR("Error getting hostname: %s", fr_syserror(errno));
109
110 return NULL;
111 }
112
113 memset(&hints, 0, sizeof hints);
114 hints.ai_family = AF_UNSPEC; /*either IPV4 or IPV6*/
115 hints.ai_socktype = SOCK_STREAM;
116 hints.ai_flags = AI_CANONNAME;
117
118 if ((ret = getaddrinfo(hostname, "radius", &hints, &info)) != 0) {
119 ERROR("Error getting hostname: %s", gai_strerror(ret));
120 return NULL;
121 }
122
123 strlcpy(fqdn, info->ai_canonname, sizeof(fqdn));
124
125 freeaddrinfo(info);
126 }
127
128 tmpl = talloc_zero(ctx, rs_stats_tmpl_t);
129 if (!tmpl) {
130 return NULL;
131 }
132
133 tmpl->value_tmpl = talloc_zero_array(tmpl, rs_stats_value_tmpl_t, len);
134 if (!tmpl->value_tmpl) {
135 goto error;
136 }
137
138 tmpl->stats = stats;
139
140 value = talloc_zero(tmpl, lcc_value_list_t);
141 if (!value) {
142 goto error;
143 }
144 tmpl->value = value;
145
146 value->interval = conf->stats.interval;
147 value->values_len = len;
148
149 value->values_types = talloc_zero_array(value, int, len);
150 if (!value->values_types) {
151 goto error;
152 }
153
154 value->values = talloc_zero_array(value, value_t, len);
155 if (!value->values) {
156 goto error;
157 }
158
159 for (i = 0; i < (int) len; i++) {
160 assert(values[i].src);
161 assert(values[i].cb);
162
163 tmpl->value_tmpl[i] = values[i];
164 switch (tmpl->value_tmpl[i].type) {
165 case LCC_TYPE_COUNTER:
166 tmpl->value_tmpl[i].dst = &value->values[i].counter;
167 break;
168
169 case LCC_TYPE_GAUGE:
170 tmpl->value_tmpl[i].dst = &value->values[i].gauge;
171 break;
172
173 case LCC_TYPE_DERIVE:
174 tmpl->value_tmpl[i].dst = &value->values[i].derive;
175 break;
176
177 case LCC_TYPE_ABSOLUTE:
178 tmpl->value_tmpl[i].dst = &value->values[i].absolute;
179 break;
180
181 default:
182 assert(0);
183 }
184 value->values_types[i] = tmpl->value_tmpl[i].type;
185 }
186
187 /*
188 * These should be OK as is
189 */
190 strlcpy(value->identifier.host, fqdn, sizeof(value->identifier.host));
191
192 /*
193 * Plugin is ASCII only and no '/'
194 */
195 fr_prints(value->identifier.plugin, sizeof(value->identifier.plugin),
196 conf->stats.prefix, strlen(conf->stats.prefix), '\0');
197 for (p = value->identifier.plugin; *p; ++p) {
198 if ((*p == '-') || (*p == '/'))*p = '_';
199 }
200
201 /*
202 * Plugin instance is ASCII only (assuming printable only) and no '/'
203 */
204 fr_prints(value->identifier.plugin_instance, sizeof(value->identifier.plugin_instance),
205 plugin_instance, strlen(plugin_instance), '\0');
206 for (p = value->identifier.plugin_instance; *p; ++p) {
207 if ((*p == '-') || (*p == '/')) *p = '_';
208 }
209
210 /*
211 * Type is ASCII only (assuming printable only) and no '/' or '-'
212 */
213 fr_prints(value->identifier.type, sizeof(value->identifier.type),
214 type, strlen(type), '\0');
215 for (p = value->identifier.type; *p; ++p) {
216 if ((*p == '-') || (*p == '/')) *p = '_';
217 }
218
219 fr_prints(value->identifier.type_instance, sizeof(value->identifier.type_instance),
220 type_instance, strlen(type_instance), '\0');
221 for (p = value->identifier.type_instance; *p; ++p) {
222 if ((*p == '-') || (*p == '/')) *p = '_';
223 }
224
225
226 return tmpl;
227
228 error:
229 talloc_free(tmpl);
230 return NULL;
231 }
232
233
234 /** Setup stats templates for latency
235 *
236 */
rs_stats_collectd_init_latency(TALLOC_CTX * ctx,rs_stats_tmpl_t ** out,rs_t * conf,char const * type,rs_latency_t * stats,PW_CODE code)237 rs_stats_tmpl_t *rs_stats_collectd_init_latency(TALLOC_CTX *ctx, rs_stats_tmpl_t **out, rs_t *conf,
238 char const *type, rs_latency_t *stats, PW_CODE code)
239 {
240 rs_stats_tmpl_t **tmpl, *last;
241 char *p;
242 char buffer[LCC_NAME_LEN];
243 tmpl = out;
244
245 rs_stats_value_tmpl_t rtx[(RS_RETRANSMIT_MAX + 1) + 1 + 1]; // RTX bins + 0 bin + lost + NULL
246 int i;
247
248 /* not static so were thread safe */
249 rs_stats_value_tmpl_t const _packet_count[] = {
250 { &stats->interval.received, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
251 { &stats->interval.linked, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
252 { &stats->interval.unlinked, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
253 { &stats->interval.reused, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
254 { NULL, 0, NULL, NULL }
255 };
256
257 rs_stats_value_tmpl_t const _latency[] = {
258 { &stats->latency_smoothed, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
259 { &stats->interval.latency_average, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
260 { &stats->interval.latency_high, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
261 { &stats->interval.latency_low, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
262 { NULL, 0, NULL, NULL }
263 };
264
265 #define INIT_STATS(_ti, _v) do {\
266 strlcpy(buffer, fr_packet_codes[code], sizeof(buffer)); \
267 for (p = buffer; *p; ++p) *p = tolower(*p);\
268 last = *tmpl = rs_stats_collectd_init(ctx, conf, type, _ti, buffer, stats, _v);\
269 if (!*tmpl) {\
270 TALLOC_FREE(*out);\
271 return NULL;\
272 }\
273 tmpl = &(*tmpl)->next;\
274 ctx = *tmpl;\
275 } while (0)
276
277
278 INIT_STATS("radius_count", _packet_count);
279 INIT_STATS("radius_latency", _latency);
280
281 for (i = 0; i < (RS_RETRANSMIT_MAX + 1); i++) {
282 rtx[i].src = &stats->interval.rt[i];
283 rtx[i].type = LCC_TYPE_GAUGE;
284 rtx[i].cb = _copy_double_to_double;
285 rtx[i].dst = NULL;
286 }
287
288 rtx[i].src = &stats->interval.lost;
289 rtx[i].type = LCC_TYPE_GAUGE;
290 rtx[i].cb = _copy_double_to_double;
291 rtx[i].dst = NULL;
292
293 memset(&rtx[++i], 0, sizeof(rs_stats_value_tmpl_t));
294
295 INIT_STATS("radius_rtx", rtx);
296
297 return last;
298 }
299
300 /** Refresh and send the stats to the collectd server
301 *
302 */
rs_stats_collectd_do_stats(rs_t * conf,rs_stats_tmpl_t * tmpls,struct timeval * now)303 void rs_stats_collectd_do_stats(rs_t *conf, rs_stats_tmpl_t *tmpls, struct timeval *now)
304 {
305 rs_stats_tmpl_t *tmpl = tmpls;
306 char identifier[6 * LCC_NAME_LEN];
307 int i;
308
309 while (tmpl) {
310 /*
311 * Refresh the value of whatever were sending
312 */
313 for (i = 0; i < (int) tmpl->value->values_len; i++) {
314 tmpl->value_tmpl[i].cb(conf, &tmpl->value_tmpl[i]);
315 }
316
317 tmpl->value->time = now->tv_sec;
318
319 lcc_identifier_to_string(conf->stats.handle, identifier, sizeof(identifier), &tmpl->value->identifier);
320
321 if (lcc_putval(conf->stats.handle, tmpl->value) < 0) {
322 char const *error;
323
324 error = lcc_strerror(conf->stats.handle);
325 ERROR("Failed PUTVAL \"%s\" interval=%i %" PRIu64 " : %s",
326 identifier,
327 (int) tmpl->value->interval,
328 (uint64_t) tmpl->value->time,
329 error ? error : "unknown error");
330 }
331
332 tmpl = tmpl->next;
333 }
334 }
335
336 /** Connect to a collectd server for stats output
337 *
338 * @param[in,out] conf radsniff configuration, we write the generated handle here.
339 * @return 0 on success -1 on failure.
340 */
rs_stats_collectd_open(rs_t * conf)341 int rs_stats_collectd_open(rs_t *conf)
342 {
343 assert(conf->stats.collectd);
344
345 /*
346 * Tear down stale connections gracefully.
347 */
348 rs_stats_collectd_close(conf);
349
350 /*
351 * There's no way to get the error from the connection handle
352 * because it's freed on failure, before lcc returns.
353 */
354 if (lcc_connect(conf->stats.collectd, &conf->stats.handle) < 0) {
355 ERROR("Failed opening connection to collectd: %s", fr_syserror(errno));
356 return -1;
357 }
358 DEBUG2("Connected to \"%s\"", conf->stats.collectd);
359
360 assert(conf->stats.handle);
361 return 0;
362 }
363
364 /** Close connection
365 *
366 * @param[in,out] conf radsniff configuration.
367 * @return 0 on success -1 on failure.
368 */
rs_stats_collectd_close(rs_t * conf)369 int rs_stats_collectd_close(rs_t *conf)
370 {
371 assert(conf->stats.collectd);
372
373 int ret = 0;
374
375 if (conf->stats.handle) {
376 ret = lcc_disconnect(conf->stats.handle);
377 conf->stats.handle = NULL;
378 }
379
380 return ret;
381 }
382 #endif
383