1 /**
2 * collectd - src/teamspeak2.c
3 * Copyright (C) 2008 Stefan Hacker
4 * Copyright (C) 2008 Florian Forster
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; only version 2 of the License is applicable.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Authors:
20 * Stefan Hacker <d0t at dbclan dot de>
21 * Florian Forster <octo at collectd.org>
22 **/
24 #include "collectd.h"
25 #include "common.h"
26 #include "plugin.h"
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 #include <sys/types.h>
31 #include <netdb.h>
33 /*
34 * Defines
35 */
36 /* Default host and port */
37 #define DEFAULT_HOST "127.0.0.1"
38 #define DEFAULT_PORT "51234"
40 /*
41 * Variables
42 */
43 /* Server linked list structure */
44 typedef struct vserver_list_s
45 {
46 int port;
47 struct vserver_list_s *next;
48 } vserver_list_t;
49 static vserver_list_t *server_list = NULL;
51 /* Host data */
52 static char *config_host = NULL;
53 static char *config_port = NULL;
55 static FILE *global_read_fh = NULL;
56 static FILE *global_write_fh = NULL;
58 /* Config data */
59 static const char *config_keys[] =
60 {
61 "Host",
62 "Port",
63 "Server"
64 };
65 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
67 /*
68 * Functions
69 */
70 static int tss2_add_vserver (int vserver_port)
71 {
72 /*
73 * Adds a new vserver to the linked list
74 */
75 vserver_list_t *entry;
77 /* Check port range */
78 if ((vserver_port <= 0) || (vserver_port > 65535))
79 {
80 ERROR ("teamspeak2 plugin: VServer port is invalid: %i",
81 vserver_port);
82 return (-1);
83 }
85 /* Allocate memory */
86 entry = calloc (1, sizeof (*entry));
87 if (entry == NULL)
88 {
89 ERROR ("teamspeak2 plugin: calloc failed.");
90 return (-1);
91 }
93 /* Save data */
94 entry->port = vserver_port;
96 /* Insert to list */
97 if(server_list == NULL) {
98 /* Add the server as the first element */
99 server_list = entry;
100 }
101 else {
102 vserver_list_t *prev;
104 /* Add the server to the end of the list */
105 prev = server_list;
106 while (prev->next != NULL)
107 prev = prev->next;
108 prev->next = entry;
109 }
111 INFO ("teamspeak2 plugin: Registered new vserver: %i", vserver_port);
113 return (0);
114 } /* int tss2_add_vserver */
116 static void tss2_submit_gauge (const char *plugin_instance,
117 const char *type, const char *type_instance,
118 gauge_t value)
119 {
120 /*
121 * Submits a gauge value to the collectd daemon
122 */
123 value_t values[1];
124 value_list_t vl = VALUE_LIST_INIT;
126 values[0].gauge = value;
128 vl.values = values;
129 vl.values_len = 1;
130 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
131 sstrncpy (vl.plugin, "teamspeak2", sizeof (vl.plugin));
133 if (plugin_instance != NULL)
134 sstrncpy (vl.plugin_instance, plugin_instance,
135 sizeof (vl.plugin_instance));
137 sstrncpy (vl.type, type, sizeof (vl.type));
139 if (type_instance != NULL)
140 sstrncpy (vl.type_instance, type_instance,
141 sizeof (vl.type_instance));
143 plugin_dispatch_values (&vl);
144 } /* void tss2_submit_gauge */
146 static void tss2_submit_io (const char *plugin_instance, const char *type,
147 derive_t rx, derive_t tx)
148 {
149 /*
150 * Submits the io rx/tx tuple to the collectd daemon
151 */
152 value_t values[2];
153 value_list_t vl = VALUE_LIST_INIT;
155 values[0].derive = rx;
156 values[1].derive = tx;
158 vl.values = values;
159 vl.values_len = 2;
160 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
161 sstrncpy (vl.plugin, "teamspeak2", sizeof (vl.plugin));
163 if (plugin_instance != NULL)
164 sstrncpy (vl.plugin_instance, plugin_instance,
165 sizeof (vl.plugin_instance));
167 sstrncpy (vl.type, type, sizeof (vl.type));
169 plugin_dispatch_values (&vl);
170 } /* void tss2_submit_gauge */
172 static void tss2_close_socket (void)
173 {
174 /*
175 * Closes all sockets
176 */
177 if (global_write_fh != NULL)
178 {
179 fputs ("quit\r\n", global_write_fh);
180 }
182 if (global_read_fh != NULL)
183 {
184 fclose (global_read_fh);
185 global_read_fh = NULL;
186 }
188 if (global_write_fh != NULL)
189 {
190 fclose (global_write_fh);
191 global_write_fh = NULL;
192 }
193 } /* void tss2_close_socket */
195 static int tss2_get_socket (FILE **ret_read_fh, FILE **ret_write_fh)
196 {
197 /*
198 * Returns connected file objects or establishes the connection
199 * if it's not already present
200 */
201 struct addrinfo ai_hints;
202 struct addrinfo *ai_head;
203 struct addrinfo *ai_ptr;
204 int sd = -1;
205 int status;
207 /* Check if we already got opened connections */
208 if ((global_read_fh != NULL) && (global_write_fh != NULL))
209 {
210 /* If so, use them */
211 if (ret_read_fh != NULL)
212 *ret_read_fh = global_read_fh;
213 if (ret_write_fh != NULL)
214 *ret_write_fh = global_write_fh;
215 return (0);
216 }
218 /* Get all addrs for this hostname */
219 memset (&ai_hints, 0, sizeof (ai_hints));
220 #ifdef AI_ADDRCONFIG
221 ai_hints.ai_flags |= AI_ADDRCONFIG;
222 #endif
223 ai_hints.ai_family = AF_UNSPEC;
224 ai_hints.ai_socktype = SOCK_STREAM;
226 status = getaddrinfo ((config_host != NULL) ? config_host : DEFAULT_HOST,
227 (config_port != NULL) ? config_port : DEFAULT_PORT,
228 &ai_hints,
229 &ai_head);
230 if (status != 0)
231 {
232 ERROR ("teamspeak2 plugin: getaddrinfo failed: %s",
233 gai_strerror (status));
234 return (-1);
235 }
237 /* Try all given hosts until we can connect to one */
238 for (ai_ptr = ai_head; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
239 {
240 /* Create socket */
241 sd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
242 ai_ptr->ai_protocol);
243 if (sd < 0)
244 {
245 char errbuf[1024];
246 WARNING ("teamspeak2 plugin: socket failed: %s",
247 sstrerror (errno, errbuf, sizeof (errbuf)));
248 continue;
249 }
251 /* Try to connect */
252 status = connect (sd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
253 if (status != 0)
254 {
255 char errbuf[1024];
256 WARNING ("teamspeak2 plugin: connect failed: %s",
257 sstrerror (errno, errbuf, sizeof (errbuf)));
258 close (sd);
259 sd = -1;
260 continue;
261 }
263 /*
264 * Success, we can break. Don't need more than one connection
265 */
266 break;
267 } /* for (ai_ptr) */
269 freeaddrinfo (ai_head);
271 /* Check if we really got connected */
272 if (sd < 0)
273 return (-1);
275 /* Create file objects from sockets */
276 global_read_fh = fdopen (sd, "r");
277 if (global_read_fh == NULL)
278 {
279 char errbuf[1024];
280 ERROR ("teamspeak2 plugin: fdopen failed: %s",
281 sstrerror (errno, errbuf, sizeof (errbuf)));
282 close (sd);
283 return (-1);
284 }
286 global_write_fh = fdopen (sd, "w");
287 if (global_write_fh == NULL)
288 {
289 char errbuf[1024];
290 ERROR ("teamspeak2 plugin: fdopen failed: %s",
291 sstrerror (errno, errbuf, sizeof (errbuf)));
292 tss2_close_socket ();
293 return (-1);
294 }
296 { /* Check that the server correctly identifies itself. */
297 char buffer[4096];
298 char *buffer_ptr;
300 buffer_ptr = fgets (buffer, sizeof (buffer), global_read_fh);
301 if (buffer_ptr == NULL)
302 {
303 WARNING ("teamspeak2 plugin: Unexpected EOF received "
304 "from remote host %s:%s.",
305 config_host ? config_host : DEFAULT_HOST,
306 config_port ? config_port : DEFAULT_PORT);
307 }
308 buffer[sizeof (buffer) - 1] = 0;
310 if (memcmp ("[TS]\r\n", buffer, 6) != 0)
311 {
312 ERROR ("teamspeak2 plugin: Unexpected response when connecting "
313 "to server. Expected ``[TS]'', got ``%s''.",
314 buffer);
315 tss2_close_socket ();
316 return (-1);
317 }
318 DEBUG ("teamspeak2 plugin: Server send correct banner, connected!");
319 }
321 /* Copy the new filehandles to the given pointers */
322 if (ret_read_fh != NULL)
323 *ret_read_fh = global_read_fh;
324 if (ret_write_fh != NULL)
325 *ret_write_fh = global_write_fh;
326 return (0);
327 } /* int tss2_get_socket */
329 static int tss2_send_request (FILE *fh, const char *request)
330 {
331 /*
332 * This function puts a request to the server socket
333 */
334 int status;
336 status = fputs (request, fh);
337 if (status < 0)
338 {
339 ERROR ("teamspeak2 plugin: fputs failed.");
340 tss2_close_socket ();
341 return (-1);
342 }
343 fflush (fh);
345 return (0);
346 } /* int tss2_send_request */
348 static int tss2_receive_line (FILE *fh, char *buffer, int buffer_size)
349 {
350 /*
351 * Receive a single line from the given file object
352 */
353 char *temp;
355 /*
356 * fgets is blocking but much easier then doing anything else
357 * TODO: Non-blocking Version would be safer
358 */
359 temp = fgets (buffer, buffer_size, fh);
360 if (temp == NULL)
361 {
362 char errbuf[1024];
363 ERROR ("teamspeak2 plugin: fgets failed: %s",
364 sstrerror (errno, errbuf, sizeof(errbuf)));
365 tss2_close_socket ();
366 return (-1);
367 }
369 buffer[buffer_size - 1] = 0;
370 return (0);
371 } /* int tss2_receive_line */
373 static int tss2_select_vserver (FILE *read_fh, FILE *write_fh, vserver_list_t *vserver)
374 {
375 /*
376 * Tell the server to select the given vserver
377 */
378 char command[128];
379 char response[128];
380 int status;
382 /* Send request */
383 ssnprintf (command, sizeof (command), "sel %i\r\n", vserver->port);
385 status = tss2_send_request (write_fh, command);
386 if (status != 0)
387 {
388 ERROR ("teamspeak2 plugin: tss2_send_request (%s) failed.", command);
389 return (-1);
390 }
392 /* Get answer */
393 status = tss2_receive_line (read_fh, response, sizeof (response));
394 if (status != 0)
395 {
396 ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
397 return (-1);
398 }
399 response[sizeof (response) - 1] = 0;
401 /* Check answer */
402 if ((strncasecmp ("OK", response, 2) == 0)
403 && ((response[2] == 0)
404 || (response[2] == '\n')
405 || (response[2] == '\r')))
406 return (0);
408 ERROR ("teamspeak2 plugin: Command ``%s'' failed. "
409 "Response received from server was: ``%s''.",
410 command, response);
411 return (-1);
412 } /* int tss2_select_vserver */
414 static int tss2_vserver_gapl (FILE *read_fh, FILE *write_fh,
415 gauge_t *ret_value)
416 {
417 /*
418 * Reads the vserver's average packet loss and submits it to collectd.
419 * Be sure to run the tss2_read_vserver function before calling this so
420 * the vserver is selected correctly.
421 */
422 gauge_t packet_loss = NAN;
423 int status;
425 status = tss2_send_request (write_fh, "gapl\r\n");
426 if (status != 0)
427 {
428 ERROR("teamspeak2 plugin: tss2_send_request (gapl) failed.");
429 return (-1);
430 }
432 while (42)
433 {
434 char buffer[4096];
435 char *value;
436 char *endptr = NULL;
438 status = tss2_receive_line (read_fh, buffer, sizeof (buffer));
439 if (status != 0)
440 {
441 /* Set to NULL just to make sure no one uses these FHs anymore. */
442 read_fh = NULL;
443 write_fh = NULL;
444 ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
445 return (-1);
446 }
447 buffer[sizeof (buffer) - 1] = 0;
449 if (strncmp ("average_packet_loss=", buffer,
450 strlen ("average_packet_loss=")) == 0)
451 {
452 /* Got average packet loss, now interpret it */
453 value = &buffer[20];
454 /* Replace , with . */
455 while (*value != 0)
456 {
457 if (*value == ',')
458 {
459 *value = '.';
460 break;
461 }
462 value++;
463 }
465 value = &buffer[20];
467 packet_loss = strtod (value, &endptr);
468 if (value == endptr)
469 {
470 /* Failed */
471 WARNING ("teamspeak2 plugin: Could not read average package "
472 "loss from string: %s", buffer);
473 continue;
474 }
475 }
476 else if (strncasecmp ("OK", buffer, 2) == 0)
477 {
478 break;
479 }
480 else if (strncasecmp ("ERROR", buffer, 5) == 0)
481 {
482 ERROR ("teamspeak2 plugin: Server returned an error: %s", buffer);
483 return (-1);
484 }
485 else
486 {
487 WARNING ("teamspeak2 plugin: Server returned unexpected string: %s",
488 buffer);
489 }
490 }
492 *ret_value = packet_loss;
493 return (0);
494 } /* int tss2_vserver_gapl */
496 static int tss2_read_vserver (vserver_list_t *vserver)
497 {
498 /*
499 * Poll information for the given vserver and submit it to collect.
500 * If vserver is NULL the global server information will be queried.
501 */
502 int status;
504 gauge_t users = NAN;
505 gauge_t channels = NAN;
506 gauge_t servers = NAN;
507 derive_t rx_octets = 0;
508 derive_t tx_octets = 0;
509 derive_t rx_packets = 0;
510 derive_t tx_packets = 0;
511 gauge_t packet_loss = NAN;
512 int valid = 0;
514 char plugin_instance[DATA_MAX_NAME_LEN];
516 FILE *read_fh;
517 FILE *write_fh;
519 /* Get the send/receive sockets */
520 status = tss2_get_socket (&read_fh, &write_fh);
521 if (status != 0)
522 {
523 ERROR ("teamspeak2 plugin: tss2_get_socket failed.");
524 return (-1);
525 }
527 if (vserver == NULL)
528 {
529 /* Request global information */
530 memset (plugin_instance, 0, sizeof (plugin_instance));
532 status = tss2_send_request (write_fh, "gi\r\n");
533 }
534 else
535 {
536 /* Request server information */
537 ssnprintf (plugin_instance, sizeof (plugin_instance), "vserver%i",
538 vserver->port);
540 /* Select the server */
541 status = tss2_select_vserver (read_fh, write_fh, vserver);
542 if (status != 0)
543 return (status);
545 status = tss2_send_request (write_fh, "si\r\n");
546 }
548 if (status != 0)
549 {
550 ERROR ("teamspeak2 plugin: tss2_send_request failed.");
551 return (-1);
552 }
554 /* Loop until break */
555 while (42)
556 {
557 char buffer[4096];
558 char *key;
559 char *value;
560 char *endptr = NULL;
562 /* Read one line of the server's answer */
563 status = tss2_receive_line (read_fh, buffer, sizeof (buffer));
564 if (status != 0)
565 {
566 /* Set to NULL just to make sure no one uses these FHs anymore. */
567 read_fh = NULL;
568 write_fh = NULL;
569 ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
570 break;
571 }
573 if (strncasecmp ("ERROR", buffer, 5) == 0)
574 {
575 ERROR ("teamspeak2 plugin: Server returned an error: %s",
576 buffer);
577 break;
578 }
579 else if (strncasecmp ("OK", buffer, 2) == 0)
580 {
581 break;
582 }
584 /* Split line into key and value */
585 key = strchr (buffer, '_');
586 if (key == NULL)
587 {
588 DEBUG ("teamspeak2 plugin: Cannot parse line: %s", buffer);
589 continue;
590 }
591 key++;
593 /* Evaluate assignment */
594 value = strchr (key, '=');
595 if (value == NULL)
596 {
597 DEBUG ("teamspeak2 plugin: Cannot parse line: %s", buffer);
598 continue;
599 }
600 *value = 0;
601 value++;
603 /* Check for known key and save the given value */
604 /* global info: users_online,
605 * server info: currentusers. */
606 if ((strcmp ("currentusers", key) == 0)
607 || (strcmp ("users_online", key) == 0))
608 {
609 users = strtod (value, &endptr);
610 if (value != endptr)
611 valid |= 0x01;
612 }
613 /* global info: channels,
614 * server info: currentchannels. */
615 else if ((strcmp ("currentchannels", key) == 0)
616 || (strcmp ("channels", key) == 0))
617 {
618 channels = strtod (value, &endptr);
619 if (value != endptr)
620 valid |= 0x40;
621 }
622 /* global only */
623 else if (strcmp ("servers", key) == 0)
624 {
625 servers = strtod (value, &endptr);
626 if (value != endptr)
627 valid |= 0x80;
628 }
629 else if (strcmp ("bytesreceived", key) == 0)
630 {
631 rx_octets = strtoll (value, &endptr, 0);
632 if (value != endptr)
633 valid |= 0x02;
634 }
635 else if (strcmp ("bytessend", key) == 0)
636 {
637 tx_octets = strtoll (value, &endptr, 0);
638 if (value != endptr)
639 valid |= 0x04;
640 }
641 else if (strcmp ("packetsreceived", key) == 0)
642 {
643 rx_packets = strtoll (value, &endptr, 0);
644 if (value != endptr)
645 valid |= 0x08;
646 }
647 else if (strcmp ("packetssend", key) == 0)
648 {
649 tx_packets = strtoll (value, &endptr, 0);
650 if (value != endptr)
651 valid |= 0x10;
652 }
653 else if ((strncmp ("allow_codec_", key, strlen ("allow_codec_")) == 0)
654 || (strncmp ("bwinlast", key, strlen ("bwinlast")) == 0)
655 || (strncmp ("bwoutlast", key, strlen ("bwoutlast")) == 0)
656 || (strncmp ("webpost_", key, strlen ("webpost_")) == 0)
657 || (strcmp ("adminemail", key) == 0)
658 || (strcmp ("clan_server", key) == 0)
659 || (strcmp ("countrynumber", key) == 0)
660 || (strcmp ("id", key) == 0)
661 || (strcmp ("ispname", key) == 0)
662 || (strcmp ("linkurl", key) == 0)
663 || (strcmp ("maxusers", key) == 0)
664 || (strcmp ("name", key) == 0)
665 || (strcmp ("password", key) == 0)
666 || (strcmp ("platform", key) == 0)
667 || (strcmp ("server_platform", key) == 0)
668 || (strcmp ("server_uptime", key) == 0)
669 || (strcmp ("server_version", key) == 0)
670 || (strcmp ("udpport", key) == 0)
671 || (strcmp ("uptime", key) == 0)
672 || (strcmp ("users_maximal", key) == 0)
673 || (strcmp ("welcomemessage", key) == 0))
674 /* ignore */;
675 else
676 {
677 INFO ("teamspeak2 plugin: Unknown key-value-pair: "
678 "key = %s; value = %s;", key, value);
679 }
680 } /* while (42) */
682 /* Collect vserver packet loss rates only if the loop above did not exit
683 * with an error. */
684 if ((status == 0) && (vserver != NULL))
685 {
686 status = tss2_vserver_gapl (read_fh, write_fh, &packet_loss);
687 if (status == 0)
688 {
689 valid |= 0x20;
690 }
691 else
692 {
693 WARNING ("teamspeak2 plugin: Reading package loss "
694 "for vserver %i failed.", vserver->port);
695 }
696 }
698 if ((valid & 0x01) == 0x01)
699 tss2_submit_gauge (plugin_instance, "users", NULL, users);
701 if ((valid & 0x06) == 0x06)
702 tss2_submit_io (plugin_instance, "io_octets", rx_octets, tx_octets);
704 if ((valid & 0x18) == 0x18)
705 tss2_submit_io (plugin_instance, "io_packets", rx_packets, tx_packets);
707 if ((valid & 0x20) == 0x20)
708 tss2_submit_gauge (plugin_instance, "percent", "packet_loss", packet_loss);
710 if ((valid & 0x40) == 0x40)
711 tss2_submit_gauge (plugin_instance, "gauge", "channels", channels);
713 if ((valid & 0x80) == 0x80)
714 tss2_submit_gauge (plugin_instance, "gauge", "servers", servers);
716 if (valid == 0)
717 return (-1);
718 return (0);
719 } /* int tss2_read_vserver */
721 static int tss2_config (const char *key, const char *value)
722 {
723 /*
724 * Interpret configuration values
725 */
726 if (strcasecmp ("Host", key) == 0)
727 {
728 char *temp;
730 temp = strdup (value);
731 if (temp == NULL)
732 {
733 ERROR("teamspeak2 plugin: strdup failed.");
734 return (1);
735 }
736 sfree (config_host);
737 config_host = temp;
738 }
739 else if (strcasecmp ("Port", key) == 0)
740 {
741 char *temp;
743 temp = strdup (value);
744 if (temp == NULL)
745 {
746 ERROR("teamspeak2 plugin: strdup failed.");
747 return (1);
748 }
749 sfree (config_port);
750 config_port = temp;
751 }
752 else if (strcasecmp ("Server", key) == 0)
753 {
754 /* Server variable found */
755 int status;
757 status = tss2_add_vserver (atoi (value));
758 if (status != 0)
759 return (1);
760 }
761 else
762 {
763 /* Unknown variable found */
764 return (-1);
765 }
767 return 0;
768 } /* int tss2_config */
770 static int tss2_read (void)
771 {
772 /*
773 * Poll function which collects global and vserver information
774 * and submits it to collectd
775 */
776 vserver_list_t *vserver;
777 int success = 0;
778 int status;
780 /* Handle global server variables */
781 status = tss2_read_vserver (NULL);
782 if (status == 0)
783 {
784 success++;
785 }
786 else
787 {
788 WARNING ("teamspeak2 plugin: Reading global server variables failed.");
789 }
791 /* Handle vservers */
792 for (vserver = server_list; vserver != NULL; vserver = vserver->next)
793 {
794 status = tss2_read_vserver (vserver);
795 if (status == 0)
796 {
797 success++;
798 }
799 else
800 {
801 WARNING ("teamspeak2 plugin: Reading statistics "
802 "for vserver %i failed.", vserver->port);
803 continue;
804 }
805 }
807 if (success == 0)
808 return (-1);
809 return (0);
810 } /* int tss2_read */
812 static int tss2_shutdown(void)
813 {
814 /*
815 * Shutdown handler
816 */
817 vserver_list_t *entry;
819 tss2_close_socket ();
821 entry = server_list;
822 server_list = NULL;
823 while (entry != NULL)
824 {
825 vserver_list_t *next;
827 next = entry->next;
828 sfree (entry);
829 entry = next;
830 }
832 /* Get rid of the configuration */
833 sfree (config_host);
834 sfree (config_port);
836 return (0);
837 } /* int tss2_shutdown */
839 void module_register(void)
840 {
841 /*
842 * Mandatory module_register function
843 */
844 plugin_register_config ("teamspeak2", tss2_config,
845 config_keys, config_keys_num);
846 plugin_register_read ("teamspeak2", tss2_read);
847 plugin_register_shutdown ("teamspeak2", tss2_shutdown);
848 } /* void module_register */
850 /* vim: set sw=4 ts=4 : */