The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
collectd.c
Go to the documentation of this file.
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: c584ec6440eca43d302d3a8a6c9d5fa9745d7a5f $
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/util/syserror.h>
30 #include "radsniff.h"
31 
32 /** Copy a 64bit unsigned integer into a double
33  *
34  */
35 /*
36 static void _copy_uint64_to_double(UNUSED rs_t *conf, rs_stats_value_tmpl_t *tmpl)
37 {
38  assert(tmpl->src);
39  assert(tmpl->dst);
40 
41  *((double *) tmpl->dst) = *((uint64_t *) tmpl->src);
42 }
43 */
44 
45 /*
46 static void _copy_uint64_to_uint64(UNUSED rs_t *conf, rs_stats_value_tmpl_t *tmpl)
47 {
48  assert(tmpl->src);
49  assert(tmpl->dst);
50 
51  *((uint64_t *) tmpl->dst) = *((uint64_t *) tmpl->src);
52 }
53 */
54 
55 static void _copy_double_to_double(UNUSED rs_t *conf, rs_stats_value_tmpl_t *tmpl)
56 {
57  assert(tmpl->src);
58  assert(tmpl->dst);
59 
60  *((double *) tmpl->dst) = *((double*) tmpl->src);
61 }
62 
63 
64 /** Allocates a stats template which describes a single gauge/counter
65  *
66  * This is just intended to simplify allocating a fairly complex memory structure
67  * src and dst pointers must be set
68  *
69  * @param ctx Context to allocate collectd struct in.
70  * @param conf Radsniff configuration.
71  * @param plugin_instance usually the type of packet (in our case).
72  * @param type string, the name of a collection of stats e.g. exchange
73  * @param type_instance the name of the counter/gauge within the collection e.g. latency.
74  * @param stats structure to derive statistics from.
75  * @param values Value templates used to populate lcc_value_list.
76  * @return
77  * - New #rs_stats_tmpl_t on success.
78  * - NULL on failure.
79  */
80 static rs_stats_tmpl_t *rs_stats_collectd_init(TALLOC_CTX *ctx, rs_t *conf,
81  char const *plugin_instance,
82  char const *type, char const *type_instance,
83  void *stats,
84  rs_stats_value_tmpl_t const *values)
85 {
86  static char hostname[255];
87  static char fqdn[LCC_NAME_LEN];
88 
89  size_t len;
90  int i;
91  char *p;
92 
93  rs_stats_tmpl_t *tmpl;
94  lcc_value_list_t *value;
95 
96  assert(conf);
97  assert(type);
98  assert(type_instance);
99 
100  for (len = 0; values[len].src; len++) {} ;
101  assert(len > 0);
102 
103  /*
104  * Initialise hostname once so we don't call gethostname every time
105  */
106  if (*fqdn == '\0') {
107  int ret;
108  struct addrinfo hints, *info = NULL;
109 
110  if (gethostname(hostname, sizeof(hostname)) < 0) {
111  ERROR("Error getting hostname: %s", fr_syserror(errno));
112 
113  return NULL;
114  }
115 
116  memset(&hints, 0, sizeof hints);
117  hints.ai_family = AF_UNSPEC; /*either IPV4 or IPV6*/
118  hints.ai_socktype = SOCK_STREAM;
119  hints.ai_flags = AI_CANONNAME;
120 
121  if ((ret = getaddrinfo(hostname, "radius", &hints, &info)) != 0) {
122  ERROR("Error getting hostname: %s", gai_strerror(ret));
123  return NULL;
124  }
125 
126  strlcpy(fqdn, info->ai_canonname, sizeof(fqdn));
127 
128  freeaddrinfo(info);
129  }
130 
131  tmpl = talloc_zero(ctx, rs_stats_tmpl_t);
132  if (!tmpl) {
133  return NULL;
134  }
135 
136  tmpl->value_tmpl = talloc_zero_array(tmpl, rs_stats_value_tmpl_t, len);
137  if (!tmpl->value_tmpl) {
138  goto error;
139  }
140 
141  tmpl->stats = stats;
142 
143  value = talloc_zero(tmpl, lcc_value_list_t);
144  if (!value) {
145  goto error;
146  }
147  tmpl->value = value;
148 
149  value->interval = conf->stats.interval;
150  value->values_len = len;
151 
152  value->values_types = talloc_zero_array(value, int, len);
153  if (!value->values_types) {
154  goto error;
155  }
156 
157  value->values = talloc_zero_array(value, value_t, len);
158  if (!value->values) {
159  goto error;
160  }
161 
162  for (i = 0; i < (int) len; i++) {
163  assert(values[i].src);
164  assert(values[i].cb);
165 
166  tmpl->value_tmpl[i] = values[i];
167  switch (tmpl->value_tmpl[i].type) {
168  case LCC_TYPE_COUNTER:
169  tmpl->value_tmpl[i].dst = &value->values[i].counter;
170  break;
171 
172  case LCC_TYPE_GAUGE:
173  tmpl->value_tmpl[i].dst = &value->values[i].gauge;
174  break;
175 
176  case LCC_TYPE_DERIVE:
177  tmpl->value_tmpl[i].dst = &value->values[i].derive;
178  break;
179 
180  case LCC_TYPE_ABSOLUTE:
181  tmpl->value_tmpl[i].dst = &value->values[i].absolute;
182  break;
183 
184  default:
185  assert(0);
186  }
187  value->values_types[i] = tmpl->value_tmpl[i].type;
188  }
189 
190  /*
191  * These should be OK as is
192  */
193  strlcpy(value->identifier.host, fqdn, sizeof(value->identifier.host));
194 
195  /*
196  * Plugin is ASCII only and no '/'
197  */
198  fr_snprint(value->identifier.plugin, sizeof(value->identifier.plugin),
199  conf->stats.prefix, strlen(conf->stats.prefix), '\0');
200  for (p = value->identifier.plugin; *p; ++p) {
201  if ((*p == '-') || (*p == '/'))*p = '_';
202  }
203 
204  /*
205  * Plugin instance is ASCII only (assuming printable only) and no '/'
206  */
207  fr_snprint(value->identifier.plugin_instance, sizeof(value->identifier.plugin_instance),
208  plugin_instance, strlen(plugin_instance), '\0');
209  for (p = value->identifier.plugin_instance; *p; ++p) {
210  if ((*p == '-') || (*p == '/')) *p = '_';
211  }
212 
213  /*
214  * Type is ASCII only (assuming printable only) and no '/' or '-'
215  */
216  fr_snprint(value->identifier.type, sizeof(value->identifier.type),
217  type, strlen(type), '\0');
218  for (p = value->identifier.type; *p; ++p) {
219  if ((*p == '-') || (*p == '/')) *p = '_';
220  }
221 
222  fr_snprint(value->identifier.type_instance, sizeof(value->identifier.type_instance),
223  type_instance, strlen(type_instance), '\0');
224  for (p = value->identifier.type_instance; *p; ++p) {
225  if ((*p == '-') || (*p == '/')) *p = '_';
226  }
227 
228 
229  return tmpl;
230 
231 error:
232  talloc_free(tmpl);
233  return NULL;
234 }
235 
236 
237 /** Setup stats templates for latency
238  *
239  */
240 rs_stats_tmpl_t *rs_stats_collectd_init_latency(TALLOC_CTX *ctx, rs_stats_tmpl_t **out, rs_t *conf,
241  char const *type, rs_latency_t *stats, fr_radius_packet_code_t code)
242 {
243  rs_stats_tmpl_t **tmpl = out, *last;
244  char *p;
245  char buffer[LCC_NAME_LEN];
246  rs_stats_value_tmpl_t rtx[(RS_RETRANSMIT_MAX + 1) + 1 + 1]; // RTX bins + 0 bin + lost + NULL
247  int i;
248 
249  /* not static so were thread safe */
250  rs_stats_value_tmpl_t const _packet_count[] = {
251  { &stats->interval.received, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
252  { &stats->interval.linked, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
253  { &stats->interval.unlinked, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
254  { &stats->interval.reused, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
255  { NULL, 0, NULL, NULL }
256  };
257 
258  rs_stats_value_tmpl_t const _latency[] = {
259  { &stats->latency_smoothed, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
260  { &stats->interval.latency_average, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
261  { &stats->interval.latency_high, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
262  { &stats->interval.latency_low, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
263  { NULL, 0, NULL, NULL }
264  };
265 
266 #define INIT_STATS(_ti, _v) do {\
267  strlcpy(buffer, fr_radius_packet_name[code], sizeof(buffer)); \
268  for (p = buffer; *p; ++p) *p = tolower((uint8_t) *p);\
269  last = *tmpl = rs_stats_collectd_init(ctx, conf, type, _ti, buffer, stats, _v);\
270  if (!*tmpl) {\
271  TALLOC_FREE(*out);\
272  return NULL;\
273  }\
274  tmpl = &(*tmpl)->next;\
275  ctx = *tmpl;\
276  } while (0)
277 
278 
279  INIT_STATS("radius_count", _packet_count);
280  INIT_STATS("radius_latency", _latency);
281 
282  for (i = 0; i < (RS_RETRANSMIT_MAX + 1); i++) {
283  rtx[i].src = &stats->interval.rt[i];
284  rtx[i].type = LCC_TYPE_GAUGE;
285  rtx[i].cb = _copy_double_to_double;
286  rtx[i].dst = NULL;
287  }
288 
289  rtx[i].src = &stats->interval.lost;
290  rtx[i].type = LCC_TYPE_GAUGE;
291  rtx[i].cb = _copy_double_to_double;
292  rtx[i].dst = NULL;
293 
294  memset(&rtx[++i], 0, sizeof(rs_stats_value_tmpl_t));
295 
296  INIT_STATS("radius_rtx", rtx);
297 
298  return last;
299 }
300 
301 /** Refresh and send the stats to the collectd server
302  *
303  */
304 void rs_stats_collectd_do_stats(rs_t *conf, rs_stats_tmpl_t *tmpls, struct timeval *now)
305 {
306  rs_stats_tmpl_t *tmpl = tmpls;
307  char identifier[6 * LCC_NAME_LEN];
308  int i;
309 
310  while (tmpl) {
311  /*
312  * Refresh the value of whatever were sending
313  */
314  for (i = 0; i < (int) tmpl->value->values_len; i++) {
315  tmpl->value_tmpl[i].cb(conf, &tmpl->value_tmpl[i]);
316  }
317 
318  tmpl->value->time = now->tv_sec;
319 
320  lcc_identifier_to_string(conf->stats.handle, identifier, sizeof(identifier), &tmpl->value->identifier);
321 
322  if (lcc_putval(conf->stats.handle, tmpl->value) < 0) {
323  char const *error;
324 
325  error = lcc_strerror(conf->stats.handle);
326  ERROR("Failed PUTVAL \"%s\" interval=%i %" PRIu64 " : %s",
327  identifier,
328  (int) tmpl->value->interval,
329  (uint64_t) tmpl->value->time,
330  error ? error : "unknown error");
331  }
332 
333  tmpl = tmpl->next;
334  }
335 }
336 
337 /** Connect to a collectd server for stats output
338  *
339  * @param[in,out] conf radsniff configuration, we write the generated handle here.
340  * @return
341  * - 0 on success
342  * - -1 on failure.
343  */
344 int rs_stats_collectd_open(rs_t *conf)
345 {
346  assert(conf->stats.collectd);
347 
348  /*
349  * Tear down stale connections gracefully.
350  */
351  rs_stats_collectd_close(conf);
352 
353  /*
354  * There's no way to get the error from the connection handle
355  * because it's freed on failure, before lcc returns.
356  */
357  if (lcc_connect(conf->stats.collectd, &conf->stats.handle) < 0) {
358  ERROR("Failed opening connection to collectd: %s", fr_syserror(errno));
359  return -1;
360  }
361  DEBUG2("Connected to \"%s\"", conf->stats.collectd);
362 
363  assert(conf->stats.handle);
364  return 0;
365 }
366 
367 /** Close connection
368  *
369  * @param[in,out] conf radsniff configuration.
370  * @return
371  * - 0 on success.
372  * - -1 on failure.
373  */
374 int rs_stats_collectd_close(rs_t *conf)
375 {
376  int ret = 0;
377 
378  assert(conf->stats.collectd);
379 
380  if (conf->stats.handle) {
381  ret = lcc_disconnect(conf->stats.handle);
382  conf->stats.handle = NULL;
383  }
384 
385  return ret;
386 }
387 #endif
static int const char char buffer[256]
Definition: acutest.h:574
#define UNUSED
Definition: build.h:313
fr_radius_packet_code_t
RADIUS packet codes.
Definition: defs.h:31
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
Test enumeration values.
Definition: dict_test.h:92
int getaddrinfo(char const *hostname, char const *servname, struct addrinfo const *hints, struct addrinfo **res)
Definition: getaddrinfo.c:261
void freeaddrinfo(struct addrinfo *ai)
Definition: getaddrinfo.c:251
char const * gai_strerror(int ecode)
Definition: getaddrinfo.c:231
talloc_free(reap)
size_t fr_snprint(char *out, size_t outlen, char const *in, ssize_t inlen, char quote)
Escape any non printable or non-UTF8 characters in the input string.
Definition: print.c:229
static rc_stats_t stats
Definition: radclient-ng.c:72
uint64_t lost
Requests to which we received no response.
Definition: radclient.h:59
#define DEBUG2(fmt,...)
Definition: radclient.h:43
static rs_t * conf
Definition: radsniff.c:53
Structures and prototypes for the RADIUS sniffer.
struct rs::@1 stats
#define RS_RETRANSMIT_MAX
Maximum number of times we expect to see a packet retransmitted.
Definition: radsniff.h:44
Definition: radsniff.h:259
Stats for a single interval.
Definition: radsniff.h:112
static char const * hostname(char *buf, size_t buflen, uint32_t ipaddr)
Definition: radwho.c:133
fr_aka_sim_id_type_t type
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:34
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition: syserror.c:243
static size_t char ** out
Definition: value.h:984