2 * collectd - src/teamspeak2.c
3 * Copyright (C) 2008 Stefan Hacker
4 * Copyright (C) 2008 Florian Forster
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.
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.
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
20 * Stefan Hacker <d0t at dbclan dot de>
21 * Florian Forster <octo at collectd.org>
29 #include <arpa/inet.h>
31 #include <netinet/in.h>
32 #include <sys/types.h>
37 /* Default host and port */
38 #define DEFAULT_HOST "127.0.0.1"
39 #define DEFAULT_PORT "51234"
44 /* Server linked list structure */
45 typedef struct vserver_list_s {
47 struct vserver_list_s *next;
49 static vserver_list_t *server_list = NULL;
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;
59 static const char *config_keys[] = {"Host", "Port", "Server"};
60 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
65 static int tss2_add_vserver(int vserver_port) {
67 * Adds a new vserver to the linked list
69 vserver_list_t *entry;
71 /* Check port range */
72 if ((vserver_port <= 0) || (vserver_port > 65535)) {
73 ERROR("teamspeak2 plugin: VServer port is invalid: %i", vserver_port);
78 entry = calloc(1, sizeof(*entry));
80 ERROR("teamspeak2 plugin: calloc failed.");
85 entry->port = vserver_port;
88 if (server_list == NULL) {
89 /* Add the server as the first element */
94 /* Add the server to the end of the list */
96 while (prev->next != NULL)
101 INFO("teamspeak2 plugin: Registered new vserver: %i", vserver_port);
104 } /* int tss2_add_vserver */
106 static void tss2_submit_gauge(const char *plugin_instance, const char *type,
107 const char *type_instance, gauge_t value) {
109 * Submits a gauge value to the collectd daemon
111 value_list_t vl = VALUE_LIST_INIT;
113 vl.values = &(value_t){.gauge = value};
115 sstrncpy(vl.plugin, "teamspeak2", sizeof(vl.plugin));
117 if (plugin_instance != NULL)
118 sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
120 sstrncpy(vl.type, type, sizeof(vl.type));
122 if (type_instance != NULL)
123 sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
125 plugin_dispatch_values(&vl);
126 } /* void tss2_submit_gauge */
128 static void tss2_submit_io(const char *plugin_instance, const char *type,
129 derive_t rx, derive_t tx) {
131 * Submits the io rx/tx tuple to the collectd daemon
133 value_list_t vl = VALUE_LIST_INIT;
135 {.derive = rx}, {.derive = tx},
139 vl.values_len = STATIC_ARRAY_SIZE(values);
140 sstrncpy(vl.plugin, "teamspeak2", sizeof(vl.plugin));
142 if (plugin_instance != NULL)
143 sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
145 sstrncpy(vl.type, type, sizeof(vl.type));
147 plugin_dispatch_values(&vl);
148 } /* void tss2_submit_gauge */
150 static void tss2_close_socket(void) {
154 if (global_write_fh != NULL) {
155 fputs("quit\r\n", global_write_fh);
158 if (global_read_fh != NULL) {
159 fclose(global_read_fh);
160 global_read_fh = NULL;
163 if (global_write_fh != NULL) {
164 fclose(global_write_fh);
165 global_write_fh = NULL;
167 } /* void tss2_close_socket */
169 static int tss2_get_socket(FILE **ret_read_fh, FILE **ret_write_fh) {
171 * Returns connected file objects or establishes the connection
172 * if it's not already present
174 struct addrinfo *ai_head;
178 /* Check if we already got opened connections */
179 if ((global_read_fh != NULL) && (global_write_fh != NULL)) {
180 /* If so, use them */
181 if (ret_read_fh != NULL)
182 *ret_read_fh = global_read_fh;
183 if (ret_write_fh != NULL)
184 *ret_write_fh = global_write_fh;
188 /* Get all addrs for this hostname */
189 struct addrinfo ai_hints = {.ai_family = AF_UNSPEC,
190 .ai_flags = AI_ADDRCONFIG,
191 .ai_socktype = SOCK_STREAM};
193 status = getaddrinfo((config_host != NULL) ? config_host : DEFAULT_HOST,
194 (config_port != NULL) ? config_port : DEFAULT_PORT,
195 &ai_hints, &ai_head);
197 ERROR("teamspeak2 plugin: getaddrinfo failed: %s", gai_strerror(status));
201 /* Try all given hosts until we can connect to one */
202 for (struct addrinfo *ai_ptr = ai_head; ai_ptr != NULL;
203 ai_ptr = ai_ptr->ai_next) {
205 sd = socket(ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
207 WARNING("teamspeak2 plugin: socket failed: %s", STRERRNO);
212 status = connect(sd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
214 WARNING("teamspeak2 plugin: connect failed: %s", STRERRNO);
221 * Success, we can break. Don't need more than one connection
226 freeaddrinfo(ai_head);
228 /* Check if we really got connected */
232 /* Create file objects from sockets */
233 global_read_fh = fdopen(sd, "r");
234 if (global_read_fh == NULL) {
235 ERROR("teamspeak2 plugin: fdopen failed: %s", STRERRNO);
240 global_write_fh = fdopen(sd, "w");
241 if (global_write_fh == NULL) {
242 ERROR("teamspeak2 plugin: fdopen failed: %s", STRERRNO);
247 { /* Check that the server correctly identifies itself. */
251 buffer_ptr = fgets(buffer, sizeof(buffer), global_read_fh);
252 if (buffer_ptr == NULL) {
253 WARNING("teamspeak2 plugin: Unexpected EOF received "
254 "from remote host %s:%s.",
255 config_host ? config_host : DEFAULT_HOST,
256 config_port ? config_port : DEFAULT_PORT);
258 buffer[sizeof(buffer) - 1] = 0;
260 if (memcmp("[TS]\r\n", buffer, 6) != 0) {
261 ERROR("teamspeak2 plugin: Unexpected response when connecting "
262 "to server. Expected ``[TS]'', got ``%s''.",
267 DEBUG("teamspeak2 plugin: Server send correct banner, connected!");
270 /* Copy the new filehandles to the given pointers */
271 if (ret_read_fh != NULL)
272 *ret_read_fh = global_read_fh;
273 if (ret_write_fh != NULL)
274 *ret_write_fh = global_write_fh;
276 } /* int tss2_get_socket */
278 static int tss2_send_request(FILE *fh, const char *request) {
280 * This function puts a request to the server socket
284 status = fputs(request, fh);
286 ERROR("teamspeak2 plugin: fputs failed.");
293 } /* int tss2_send_request */
295 static int tss2_receive_line(FILE *fh, char *buffer, int buffer_size) {
297 * Receive a single line from the given file object
302 * fgets is blocking but much easier then doing anything else
303 * TODO: Non-blocking Version would be safer
305 temp = fgets(buffer, buffer_size, fh);
307 ERROR("teamspeak2 plugin: fgets failed: %s", STRERRNO);
312 buffer[buffer_size - 1] = 0;
314 } /* int tss2_receive_line */
316 static int tss2_select_vserver(FILE *read_fh, FILE *write_fh,
317 vserver_list_t *vserver) {
319 * Tell the server to select the given vserver
326 snprintf(command, sizeof(command), "sel %i\r\n", vserver->port);
328 status = tss2_send_request(write_fh, command);
330 ERROR("teamspeak2 plugin: tss2_send_request (%s) failed.", command);
335 status = tss2_receive_line(read_fh, response, sizeof(response));
337 ERROR("teamspeak2 plugin: tss2_receive_line failed.");
340 response[sizeof(response) - 1] = 0;
343 if ((strncasecmp("OK", response, 2) == 0) &&
344 ((response[2] == 0) || (response[2] == '\n') || (response[2] == '\r')))
347 ERROR("teamspeak2 plugin: Command ``%s'' failed. "
348 "Response received from server was: ``%s''.",
351 } /* int tss2_select_vserver */
353 static int tss2_vserver_gapl(FILE *read_fh, FILE *write_fh,
354 gauge_t *ret_value) {
356 * Reads the vserver's average packet loss and submits it to collectd.
357 * Be sure to run the tss2_read_vserver function before calling this so
358 * the vserver is selected correctly.
360 gauge_t packet_loss = NAN;
363 status = tss2_send_request(write_fh, "gapl\r\n");
365 ERROR("teamspeak2 plugin: tss2_send_request (gapl) failed.");
374 status = tss2_receive_line(read_fh, buffer, sizeof(buffer));
376 /* Set to NULL just to make sure no one uses these FHs anymore. */
379 ERROR("teamspeak2 plugin: tss2_receive_line failed.");
382 buffer[sizeof(buffer) - 1] = 0;
384 if (strncmp("average_packet_loss=", buffer,
385 strlen("average_packet_loss=")) == 0) {
386 /* Got average packet loss, now interpret it */
388 /* Replace , with . */
389 while (*value != 0) {
399 packet_loss = strtod(value, &endptr);
400 if (value == endptr) {
402 WARNING("teamspeak2 plugin: Could not read average package "
403 "loss from string: %s",
407 } else if (strncasecmp("OK", buffer, 2) == 0) {
409 } else if (strncasecmp("ERROR", buffer, 5) == 0) {
410 ERROR("teamspeak2 plugin: Server returned an error: %s", buffer);
413 WARNING("teamspeak2 plugin: Server returned unexpected string: %s",
418 *ret_value = packet_loss;
420 } /* int tss2_vserver_gapl */
422 static int tss2_read_vserver(vserver_list_t *vserver) {
424 * Poll information for the given vserver and submit it to collect.
425 * If vserver is NULL the global server information will be queried.
430 gauge_t channels = NAN;
431 gauge_t servers = NAN;
432 derive_t rx_octets = 0;
433 derive_t tx_octets = 0;
434 derive_t rx_packets = 0;
435 derive_t tx_packets = 0;
436 gauge_t packet_loss = NAN;
439 char plugin_instance[DATA_MAX_NAME_LEN] = {0};
444 /* Get the send/receive sockets */
445 status = tss2_get_socket(&read_fh, &write_fh);
447 ERROR("teamspeak2 plugin: tss2_get_socket failed.");
451 if (vserver == NULL) {
452 /* Request global information */
453 status = tss2_send_request(write_fh, "gi\r\n");
455 /* Request server information */
456 snprintf(plugin_instance, sizeof(plugin_instance), "vserver%i",
459 /* Select the server */
460 status = tss2_select_vserver(read_fh, write_fh, vserver);
464 status = tss2_send_request(write_fh, "si\r\n");
468 ERROR("teamspeak2 plugin: tss2_send_request failed.");
472 /* Loop until break */
479 /* Read one line of the server's answer */
480 status = tss2_receive_line(read_fh, buffer, sizeof(buffer));
482 /* Set to NULL just to make sure no one uses these FHs anymore. */
485 ERROR("teamspeak2 plugin: tss2_receive_line failed.");
489 if (strncasecmp("ERROR", buffer, 5) == 0) {
490 ERROR("teamspeak2 plugin: Server returned an error: %s", buffer);
492 } else if (strncasecmp("OK", buffer, 2) == 0) {
496 /* Split line into key and value */
497 key = strchr(buffer, '_');
499 DEBUG("teamspeak2 plugin: Cannot parse line: %s", buffer);
504 /* Evaluate assignment */
505 value = strchr(key, '=');
507 DEBUG("teamspeak2 plugin: Cannot parse line: %s", buffer);
513 /* Check for known key and save the given value */
514 /* global info: users_online,
515 * server info: currentusers. */
516 if ((strcmp("currentusers", key) == 0) ||
517 (strcmp("users_online", key) == 0)) {
518 users = strtod(value, &endptr);
522 /* global info: channels,
523 * server info: currentchannels. */
524 else if ((strcmp("currentchannels", key) == 0) ||
525 (strcmp("channels", key) == 0)) {
526 channels = strtod(value, &endptr);
531 else if (strcmp("servers", key) == 0) {
532 servers = strtod(value, &endptr);
535 } else if (strcmp("bytesreceived", key) == 0) {
536 rx_octets = strtoll(value, &endptr, 0);
539 } else if (strcmp("bytessend", key) == 0) {
540 tx_octets = strtoll(value, &endptr, 0);
543 } else if (strcmp("packetsreceived", key) == 0) {
544 rx_packets = strtoll(value, &endptr, 0);
547 } else if (strcmp("packetssend", key) == 0) {
548 tx_packets = strtoll(value, &endptr, 0);
551 } else if ((strncmp("allow_codec_", key, strlen("allow_codec_")) == 0) ||
552 (strncmp("bwinlast", key, strlen("bwinlast")) == 0) ||
553 (strncmp("bwoutlast", key, strlen("bwoutlast")) == 0) ||
554 (strncmp("webpost_", key, strlen("webpost_")) == 0) ||
555 (strcmp("adminemail", key) == 0) ||
556 (strcmp("clan_server", key) == 0) ||
557 (strcmp("countrynumber", key) == 0) ||
558 (strcmp("id", key) == 0) || (strcmp("ispname", key) == 0) ||
559 (strcmp("linkurl", key) == 0) ||
560 (strcmp("maxusers", key) == 0) || (strcmp("name", key) == 0) ||
561 (strcmp("password", key) == 0) ||
562 (strcmp("platform", key) == 0) ||
563 (strcmp("server_platform", key) == 0) ||
564 (strcmp("server_uptime", key) == 0) ||
565 (strcmp("server_version", key) == 0) ||
566 (strcmp("udpport", key) == 0) || (strcmp("uptime", key) == 0) ||
567 (strcmp("users_maximal", key) == 0) ||
568 (strcmp("welcomemessage", key) == 0))
571 INFO("teamspeak2 plugin: Unknown key-value-pair: "
572 "key = %s; value = %s;",
577 /* Collect vserver packet loss rates only if the loop above did not exit
579 if ((status == 0) && (vserver != NULL)) {
580 status = tss2_vserver_gapl(read_fh, write_fh, &packet_loss);
584 WARNING("teamspeak2 plugin: Reading package loss "
585 "for vserver %i failed.",
590 if ((valid & 0x01) == 0x01)
591 tss2_submit_gauge(plugin_instance, "users", NULL, users);
593 if ((valid & 0x06) == 0x06)
594 tss2_submit_io(plugin_instance, "io_octets", rx_octets, tx_octets);
596 if ((valid & 0x18) == 0x18)
597 tss2_submit_io(plugin_instance, "io_packets", rx_packets, tx_packets);
599 if ((valid & 0x20) == 0x20)
600 tss2_submit_gauge(plugin_instance, "percent", "packet_loss", packet_loss);
602 if ((valid & 0x40) == 0x40)
603 tss2_submit_gauge(plugin_instance, "gauge", "channels", channels);
605 if ((valid & 0x80) == 0x80)
606 tss2_submit_gauge(plugin_instance, "gauge", "servers", servers);
611 } /* int tss2_read_vserver */
613 static int tss2_config(const char *key, const char *value) {
615 * Interpret configuration values
617 if (strcasecmp("Host", key) == 0) {
620 temp = strdup(value);
622 ERROR("teamspeak2 plugin: strdup failed.");
627 } else if (strcasecmp("Port", key) == 0) {
630 temp = strdup(value);
632 ERROR("teamspeak2 plugin: strdup failed.");
637 } else if (strcasecmp("Server", key) == 0) {
638 /* Server variable found */
641 status = tss2_add_vserver(atoi(value));
645 /* Unknown variable found */
650 } /* int tss2_config */
652 static int tss2_read(void) {
654 * Poll function which collects global and vserver information
655 * and submits it to collectd
660 /* Handle global server variables */
661 status = tss2_read_vserver(NULL);
665 WARNING("teamspeak2 plugin: Reading global server variables failed.");
668 /* Handle vservers */
669 for (vserver_list_t *vserver = server_list; vserver != NULL;
670 vserver = vserver->next) {
671 status = tss2_read_vserver(vserver);
675 WARNING("teamspeak2 plugin: Reading statistics "
676 "for vserver %i failed.",
685 } /* int tss2_read */
687 static int tss2_shutdown(void) {
691 vserver_list_t *entry;
697 while (entry != NULL) {
698 vserver_list_t *next;
705 /* Get rid of the configuration */
710 } /* int tss2_shutdown */
712 void module_register(void) {
714 * Mandatory module_register function
716 plugin_register_config("teamspeak2", tss2_config, config_keys,
718 plugin_register_read("teamspeak2", tss2_read);
719 plugin_register_shutdown("teamspeak2", tss2_shutdown);
720 } /* void module_register */