1 /**
2 * collectd - src/libcollectdclient/network.c
3 * Copyright (C) 2005-2015 Florian Forster
4 * Copyright (C) 2010 Max Henkel
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *
24 * Authors:
25 * Florian Forster <octo at collectd.org>
26 * Max Henkel <henkel at gmx.at>
27 **/
29 #include "collectd.h"
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <unistd.h>
34 #include <string.h>
35 #include <errno.h>
36 #include <assert.h>
38 #include <sys/types.h>
39 #include <sys/socket.h>
40 #include <netdb.h>
42 #if HAVE_NETINET_IN_H
43 # include <netinet/in.h>
44 #endif
46 #if HAVE_NET_IF_H
47 # include <net/if.h>
48 #endif
50 #include "collectd/network.h"
51 #include "collectd/network_buffer.h"
53 /*
54 * Private data types
55 */
56 struct lcc_network_s
57 {
58 lcc_server_t *servers;
59 };
61 struct lcc_server_s
62 {
63 char *node;
64 char *service;
66 int ttl;
67 lcc_security_level_t security_level;
68 char *username;
69 char *password;
71 int fd;
72 struct sockaddr *sa;
73 socklen_t sa_len;
75 lcc_network_buffer_t *buffer;
77 lcc_server_t *next;
78 };
80 /*
81 * Private functions
82 */
83 static int server_close_socket (lcc_server_t *srv) /* {{{ */
84 {
85 if (srv == NULL)
86 return (EINVAL);
88 if (srv->fd < 0)
89 return (0);
91 close (srv->fd);
92 srv->fd = -1;
93 free (srv->sa);
94 srv->sa = NULL;
95 srv->sa_len = 0;
97 return (0);
98 } /* }}} int server_close_socket */
100 static void int_server_destroy (lcc_server_t *srv) /* {{{ */
101 {
102 lcc_server_t *next;
104 if (srv == NULL)
105 return;
107 server_close_socket (srv);
109 next = srv->next;
111 free (srv->node);
112 free (srv->service);
113 free (srv->username);
114 free (srv->password);
115 free (srv);
117 int_server_destroy (next);
118 } /* }}} void int_server_destroy */
120 static int server_open_socket (lcc_server_t *srv) /* {{{ */
121 {
122 struct addrinfo ai_hints = { 0 };
123 struct addrinfo *ai_list = NULL;
124 struct addrinfo *ai_ptr;
125 int status;
127 if (srv == NULL)
128 return (EINVAL);
130 if (srv->fd >= 0)
131 server_close_socket (srv);
133 #ifdef AI_ADDRCONFIG
134 ai_hints.ai_flags |= AI_ADDRCONFIG;
135 #endif
136 ai_hints.ai_family = AF_UNSPEC;
137 ai_hints.ai_socktype = SOCK_DGRAM;
139 status = getaddrinfo (srv->node, srv->service, &ai_hints, &ai_list);
140 if (status != 0)
141 return (status);
142 assert (ai_list != NULL);
144 for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
145 {
146 srv->fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
147 if (srv->fd < 0)
148 continue;
150 if (ai_ptr->ai_family == AF_INET)
151 {
153 struct sockaddr_in *addr = (struct sockaddr_in *) ai_ptr->ai_addr;
154 int optname;
156 if (IN_MULTICAST (ntohl (addr->sin_addr.s_addr)))
157 optname = IP_MULTICAST_TTL;
158 else
159 optname = IP_TTL;
161 setsockopt (srv->fd, IPPROTO_IP, optname,
162 &srv->ttl,
163 sizeof (srv->ttl));
164 }
165 else if (ai_ptr->ai_family == AF_INET6)
166 {
167 /* Useful example: http://gsyc.escet.urjc.es/~eva/IPv6-web/examples/mcast.html */
168 struct sockaddr_in6 *addr = (struct sockaddr_in6 *) ai_ptr->ai_addr;
169 int optname;
171 if (IN6_IS_ADDR_MULTICAST (&addr->sin6_addr))
172 optname = IPV6_MULTICAST_HOPS;
173 else
174 optname = IPV6_UNICAST_HOPS;
176 setsockopt (srv->fd, IPPROTO_IPV6, optname,
177 &srv->ttl,
178 sizeof (srv->ttl));
179 }
181 srv->sa = malloc (ai_ptr->ai_addrlen);
182 if (srv->sa == NULL)
183 {
184 close (srv->fd);
185 srv->fd = -1;
186 continue;
187 }
189 memcpy (srv->sa, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
190 srv->sa_len = ai_ptr->ai_addrlen;
191 break;
192 }
194 freeaddrinfo (ai_list);
196 if (srv->fd < 0)
197 return (-1);
198 return (0);
199 } /* }}} int server_open_socket */
201 static int server_send_buffer (lcc_server_t *srv) /* {{{ */
202 {
203 char buffer[LCC_NETWORK_BUFFER_SIZE_DEFAULT];
204 size_t buffer_size;
205 int status;
207 if (srv->fd < 0)
208 {
209 status = server_open_socket (srv);
210 if (status != 0)
211 return (status);
212 }
214 memset (buffer, 0, sizeof (buffer));
215 buffer_size = sizeof (buffer);
217 status = lcc_network_buffer_finalize (srv->buffer);
218 if (status != 0)
219 {
220 lcc_network_buffer_initialize (srv->buffer);
221 return (status);
222 }
224 status = lcc_network_buffer_get (srv->buffer, buffer, &buffer_size);
225 lcc_network_buffer_initialize (srv->buffer);
227 if (status != 0)
228 return (status);
230 if (buffer_size > sizeof (buffer))
231 buffer_size = sizeof (buffer);
233 while (42)
234 {
235 assert (srv->fd >= 0);
236 assert (srv->sa != NULL);
237 status = (int) sendto (srv->fd, buffer, buffer_size, /* flags = */ 0,
238 srv->sa, srv->sa_len);
239 if ((status < 0) && ((errno == EINTR) || (errno == EAGAIN)))
240 continue;
242 break;
243 }
245 if (status < 0)
246 return (status);
247 return (0);
248 } /* }}} int server_send_buffer */
250 static int server_value_add (lcc_server_t *srv, /* {{{ */
251 const lcc_value_list_t *vl)
252 {
253 int status;
255 status = lcc_network_buffer_add_value (srv->buffer, vl);
256 if (status == 0)
257 return (0);
259 server_send_buffer (srv);
260 return (lcc_network_buffer_add_value (srv->buffer, vl));
261 } /* }}} int server_value_add */
263 /*
264 * Public functions
265 */
266 lcc_network_t *lcc_network_create (void) /* {{{ */
267 {
268 lcc_network_t *net;
270 net = malloc (sizeof (*net));
271 if (net == NULL)
272 return (NULL);
273 memset (net, 0, sizeof (*net));
275 net->servers = NULL;
277 return (net);
278 } /* }}} lcc_network_t *lcc_network_create */
280 void lcc_network_destroy (lcc_network_t *net) /* {{{ */
281 {
282 if (net == NULL)
283 return;
284 int_server_destroy (net->servers);
285 free (net);
286 } /* }}} void lcc_network_destroy */
288 lcc_server_t *lcc_server_create (lcc_network_t *net, /* {{{ */
289 const char *node, const char *service)
290 {
291 lcc_server_t *srv;
293 if ((net == NULL) || (node == NULL))
294 return (NULL);
295 if (service == NULL)
296 service = NET_DEFAULT_PORT;
298 srv = malloc (sizeof (*srv));
299 if (srv == NULL)
300 return (NULL);
301 memset (srv, 0, sizeof (*srv));
303 srv->fd = -1;
304 srv->security_level = NONE;
305 srv->username = NULL;
306 srv->password = NULL;
307 srv->next = NULL;
309 srv->node = strdup (node);
310 if (srv->node == NULL)
311 {
312 free (srv);
313 return (NULL);
314 }
316 srv->service = strdup (service);
317 if (srv->service == NULL)
318 {
319 free (srv->node);
320 free (srv);
321 return (NULL);
322 }
324 srv->buffer = lcc_network_buffer_create (/* size = */ 0);
325 if (srv->buffer == NULL)
326 {
327 free (srv->service);
328 free (srv->node);
329 free (srv);
330 return (NULL);
331 }
333 if (net->servers == NULL)
334 {
335 net->servers = srv;
336 }
337 else
338 {
339 lcc_server_t *last = net->servers;
341 while (last->next != NULL)
342 last = last->next;
344 last->next = srv;
345 }
347 return (srv);
348 } /* }}} lcc_server_t *lcc_server_create */
350 int lcc_server_destroy (lcc_network_t *net, lcc_server_t *srv) /* {{{ */
351 {
352 if ((net == NULL) || (srv == NULL))
353 return (EINVAL);
355 if (net->servers == srv)
356 {
357 net->servers = srv->next;
358 srv->next = NULL;
359 }
360 else
361 {
362 lcc_server_t *prev = net->servers;
364 while ((prev != NULL) && (prev->next != srv))
365 prev = prev->next;
367 if (prev == NULL)
368 return (ENOENT);
370 prev->next = srv->next;
371 srv->next = NULL;
372 }
374 int_server_destroy (srv);
376 return (0);
377 } /* }}} int lcc_server_destroy */
379 int lcc_server_set_ttl (lcc_server_t *srv, uint8_t ttl) /* {{{ */
380 {
381 if (srv == NULL)
382 return (EINVAL);
384 srv->ttl = (int) ttl;
386 return (0);
387 } /* }}} int lcc_server_set_ttl */
389 int lcc_server_set_interface (lcc_server_t *srv, char const *interface) /* {{{ */
390 {
391 unsigned int if_index;
392 int status;
394 if ((srv == NULL) || (interface == NULL))
395 return (EINVAL);
397 if_index = if_nametoindex (interface);
398 if (if_index == 0)
399 return (ENOENT);
401 /* IPv4 multicast */
402 if (srv->sa->sa_family == AF_INET)
403 {
404 struct sockaddr_in *addr = (struct sockaddr_in *) srv->sa;
406 if (IN_MULTICAST (ntohl (addr->sin_addr.s_addr)))
407 {
408 #if HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
409 /* If possible, use the "ip_mreqn" structure which has
410 * an "interface index" member. Using the interface
411 * index is preferred here, because of its similarity
412 * to the way IPv6 handles this. Unfortunately, it
413 * appears not to be portable. */
414 struct ip_mreqn mreq;
416 memset (&mreq, 0, sizeof (mreq));
417 mreq.imr_multiaddr.s_addr = addr->sin_addr.s_addr;
418 mreq.imr_address.s_addr = ntohl (INADDR_ANY);
419 mreq.imr_ifindex = (int) if_index;
420 #else
421 struct ip_mreq mreq;
423 memset (&mreq, 0, sizeof (mreq));
424 mreq.imr_multiaddr.s_addr = addr->sin_addr.s_addr;
425 mreq.imr_interface.s_addr = ntohl (INADDR_ANY);
426 #endif
428 status = setsockopt (srv->fd, IPPROTO_IP, IP_MULTICAST_IF,
429 &mreq, sizeof (mreq));
430 if (status != 0)
431 return (status);
433 return (0);
434 }
435 }
437 /* IPv6 multicast */
438 if (srv->sa->sa_family == AF_INET6)
439 {
440 struct sockaddr_in6 *addr = (struct sockaddr_in6 *) srv->sa;
442 if (IN6_IS_ADDR_MULTICAST (&addr->sin6_addr))
443 {
444 status = setsockopt (srv->fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
445 &if_index, sizeof (if_index));
446 if (status != 0)
447 return (status);
449 return (0);
450 }
451 }
453 /* else: Not a multicast interface. */
454 #if defined(SO_BINDTODEVICE)
455 status = setsockopt (srv->fd, SOL_SOCKET, SO_BINDTODEVICE, interface,
456 (socklen_t) (strlen (interface) + 1));
457 if (status != 0)
458 return (-1);
459 #endif
461 return (0);
462 } /* }}} int lcc_server_set_interface */
464 int lcc_server_set_security_level (lcc_server_t *srv, /* {{{ */
465 lcc_security_level_t level,
466 const char *username, const char *password)
467 {
468 return (lcc_network_buffer_set_security_level (srv->buffer,
469 level, username, password));
470 } /* }}} int lcc_server_set_security_level */
472 int lcc_network_values_send (lcc_network_t *net, /* {{{ */
473 const lcc_value_list_t *vl)
474 {
475 lcc_server_t *srv;
477 if ((net == NULL) || (vl == NULL))
478 return (EINVAL);
480 for (srv = net->servers; srv != NULL; srv = srv->next)
481 server_value_add (srv, vl);
483 return (0);
484 } /* }}} int lcc_network_values_send */
486 /* vim: set sw=2 sts=2 et fdm=marker : */