From 03b7ec004938ad0838e16f0eaef343a468140d40 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Tue, 9 Oct 2007 14:40:33 +0200 Subject: [PATCH] tcpconns plugin: Added a new plugin that counts the number of TCP connections to specific ports. --- README | 3 + configure.in | 4 + src/Makefile.am | 8 + src/collectd.conf.pod | 38 +++++ src/tcpconns.c | 346 ++++++++++++++++++++++++++++++++++++++++++ src/types.db | 1 + 6 files changed, 400 insertions(+) create mode 100644 src/tcpconns.c diff --git a/README b/README index 57ae4550..d87c960f 100644 --- a/README +++ b/README @@ -145,6 +145,9 @@ Features - tape Bytes and operations read and written on tape devices. Solaris only. + - tcpconns + Number of TCP connections to specific local and remote ports. + - users Users currently logged in. diff --git a/configure.in b/configure.in index d904920d..b0968737 100644 --- a/configure.in +++ b/configure.in @@ -1683,6 +1683,7 @@ plugin_processes="no" plugin_serial="no" plugin_swap="no" plugin_tape="no" +plugin_tcpconns="no" plugin_users="no" plugin_vserver="no" plugin_wireless="no" @@ -1703,6 +1704,7 @@ then plugin_processes="yes" plugin_serial="yes" plugin_swap="yes" + plugin_tcpconns="yes" plugin_vserver="yes" plugin_wireless="yes" fi @@ -1836,6 +1838,7 @@ AC_PLUGIN([snmp], [$with_libnetsnmp], [SNMP querying plugin]) AC_PLUGIN([swap], [$plugin_swap], [Swap usage statistics]) AC_PLUGIN([syslog], [$have_syslog], [Syslog logging plugin]) AC_PLUGIN([tape], [$plugin_tape], [Tape drive statistics]) +AC_PLUGIN([tcpconns], [$plugin_tcpconns], [TCP connection statistics]) AC_PLUGIN([unixsock], [yes], [Unixsock communication plugin]) AC_PLUGIN([users], [$plugin_users], [User statistics]) AC_PLUGIN([vserver], [$plugin_vserver], [Linux VServer statistics]) @@ -1951,6 +1954,7 @@ Configuration: swap . . . . . . . $enable_swap syslog . . . . . . $enable_syslog tape . . . . . . . $enable_tape + tcpconns . . . . . $enable_tcpconns unixsock . . . . . $enable_unixsock users . . . . . . . $enable_users vserver . . . . . . $enable_vserver diff --git a/src/Makefile.am b/src/Makefile.am index 98185804..e5578ade 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -520,6 +520,14 @@ collectd_LDADD += "-dlopen" tape.la collectd_DEPENDENCIES += tape.la endif +if BUILD_PLUGIN_TCPCONNS +pkglib_LTLIBRARIES += tcpconns.la +tcpconns_la_SOURCES = tcpconns.c +tcpconns_la_LDFLAGS = -module -avoid-version +collectd_LDADD += "-dlopen" tcpconns.la +collectd_DEPENDENCIES += tcpconns.la +endif + if BUILD_PLUGIN_UNIXSOCK pkglib_LTLIBRARIES += unixsock.la unixsock_la_SOURCES = unixsock.c utils_cmd_putval.h utils_cmd_putval.c diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 4dcbcd1b..b6503bb2 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -771,6 +771,44 @@ syslog-daemon. =back +=head2 Plugin C + +The C counts the number of currently established TCP +connections based on the local port and/or the remote port. Since there may be +a lot of connections the default if to count all connections with a local port, +for which a listening socket is opened. You can use the following options to +fine-tune the ports you are interested in: + +=over 4 + +=item B I|I + +If this option is set to I, statistics for all local ports for which a +listening socket exists are collected. The default depends on B and +B (see below): If no port at all is specifically selected, the +default is to collect listening ports. If specific ports (no matter if local or +remote ports) are selected, this option defaults to I, i.Ee. only +the selected ports will be collected unless this option is set to I +specifically. + +=item B I + +Count the connections to a specific local port. This can be used to see how +many connections are handeled by a specific daemon, e.Eg. the mailserver. +You have to specify the port in numeric form, so for the mailserver example +you'd need to set B<25>. + +=item B I + +Count the connections to a specific remote port. This is usefull to see how +much a remote service is used. This is most useful if you want to know how many +connections a local service has opened to remote services, e.Eg. how many +connections a mail server or news server has to other mail or news servers, or +how many connections a web proxy holds to web servers. You have to give the +port in numeric form. + +=back + =head2 Plugin C =over 4 diff --git a/src/tcpconns.c b/src/tcpconns.c new file mode 100644 index 00000000..7d6e7997 --- /dev/null +++ b/src/tcpconns.c @@ -0,0 +1,346 @@ +/** + * collectd - src/tcpconns.c + * Copyright (C) 2007 Florian octo Forster + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: + * Florian octo Forster + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" + +#if !KERNEL_LINUX +# error "No applicable input method." +#endif + +#define PORT_COLLECT_LOCAL 0x01 +#define PORT_COLLECT_REMOTE 0x02 +#define PORT_IS_LISTENING 0x04 + +typedef struct port_entry_s +{ + uint16_t port; + uint16_t flags; + uint32_t count_local; + uint32_t count_remote; + struct port_entry_s *next; +} port_entry_t; + +static const char *config_keys[] = +{ + "ListeningPorts", + "LocalPort", + "RemotePort" +}; +static int config_keys_num = STATIC_ARRAY_SIZE (config_keys); + +static int port_collect_listening = 0; +static port_entry_t *port_list_head = NULL; + +static void conn_submit_port_entry (port_entry_t *pe) +{ + value_t values[1]; + value_list_t vl = VALUE_LIST_INIT; + + vl.values = values; + vl.values_len = 1; + vl.time = time (NULL); + strcpy (vl.host, hostname_g); + strcpy (vl.plugin, "tcpconns"); + snprintf (vl.type_instance, sizeof (vl.type_instance), "%hu", pe->port); + vl.type_instance[sizeof (vl.type_instance) - 1] = '\0'; + + if (((port_collect_listening != 0) && (pe->flags & PORT_IS_LISTENING)) + || (pe->flags & PORT_COLLECT_LOCAL)) + { + values[0].gauge = pe->count_local; + strcpy (vl.plugin_instance, "local"); + plugin_dispatch_values ("tcp_connections", &vl); + } + if (pe->flags & PORT_COLLECT_REMOTE) + { + values[0].gauge = pe->count_remote; + strcpy (vl.plugin_instance, "remote"); + plugin_dispatch_values ("tcp_connections", &vl); + } +} /* void conn_submit */ + +static void conn_submit_all (void) +{ + port_entry_t *pe; + + for (pe = port_list_head; pe != NULL; pe = pe->next) + conn_submit_port_entry (pe); +} /* void conn_submit_all */ + +static port_entry_t *conn_get_port_entry (uint16_t port, int create) +{ + port_entry_t *ret; + + ret = port_list_head; + while (ret != NULL) + { + if (ret->port == port) + break; + ret = ret->next; + } + + if ((ret == NULL) && (create != 0)) + { + ret = (port_entry_t *) malloc (sizeof (port_entry_t)); + if (ret == NULL) + return (NULL); + memset (ret, '\0', sizeof (port_entry_t)); + + ret->port = port; + ret->next = port_list_head; + port_list_head = ret; + } + + return (ret); +} /* port_entry_t *conn_get_port_entry */ + +static void conn_reset_port_entry (void) +{ + port_entry_t *prev = NULL; + port_entry_t *pe = port_list_head; + + while (pe != NULL) + { + /* If this entry was created while reading the files (ant not when handling + * the configuration) remove it now. */ + if ((pe->flags & (PORT_COLLECT_LOCAL | PORT_COLLECT_REMOTE)) == 0) + { + port_entry_t *next = pe->next; + + DEBUG ("tcpconns plugin: Removing temporary entry " + "for listening port %hu", pe->port); + + if (prev == NULL) + port_list_head = next; + else + prev->next = next; + + sfree (pe); + pe = next; + + continue; + } + + pe->count_local = 0; + pe->count_remote = 0; + + pe->flags &= ~PORT_IS_LISTENING; + + pe = pe->next; + } +} /* void conn_reset_port_entry */ + +static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t state) +{ + /* Listening sockets */ + if ((state == 0x0a) && (port_collect_listening != 0)) + { + port_entry_t *pe; + + DEBUG ("tcpconns plugin: Adding listening port %hu", port_local); + + pe = conn_get_port_entry (port_local, 1 /* create */); + if (pe != NULL) + { + pe->count_local++; + pe->flags |= PORT_IS_LISTENING; + } + } + /* Established connections */ + else if (state == 0x01) + { + port_entry_t *pe; + + DEBUG ("tcpconns plugin: Established connection %hu <-> %hu", + port_local, port_remote); + + pe = conn_get_port_entry (port_local, 0 /* no create */); + if ((pe != NULL) && (pe->flags & PORT_COLLECT_LOCAL)) + pe->count_local++; + + pe = conn_get_port_entry (port_remote, 0 /* no create */); + if ((pe != NULL) && (pe->flags & PORT_COLLECT_REMOTE)) + pe->count_remote++; + } + else + { + DEBUG ("tcpconns plugin: Ignoring unknown state 0x%x", state); + } + + return (0); +} /* int conn_handle_ports */ + +static int conn_handle_line (char *buffer) +{ + char *fields[32]; + int fields_len; + + char *endptr; + + char *port_local_str; + char *port_remote_str; + uint16_t port_local; + uint16_t port_remote; + + uint8_t state; + + int buffer_len = strlen (buffer); + + while ((buffer_len > 0) && (buffer[buffer_len - 1] < 32)) + buffer[--buffer_len] = '\0'; + if (buffer_len <= 0) + return (-1); + + fields_len = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields)); + if (fields_len < 12) + { + DEBUG ("tcpconns plugin: Got %i fields, expected at least 12.", fields_len); + return (-1); + } + + port_local_str = strchr (fields[1], ':'); + port_remote_str = strchr (fields[2], ':'); + + if ((port_local_str == NULL) || (port_remote_str == NULL)) + return (-1); + port_local_str++; + port_remote_str++; + if ((*port_local_str == '\0') || (*port_remote_str == '\0')) + return (-1); + + endptr = NULL; + port_local = (uint16_t) strtol (port_local_str, &endptr, 16); + if ((endptr == NULL) || (*endptr != '\0')) + return (-1); + + endptr = NULL; + port_remote = (uint16_t) strtol (port_remote_str, &endptr, 16); + if ((endptr == NULL) || (*endptr != '\0')) + return (-1); + + endptr = NULL; + state = (uint8_t) strtol (fields[3], &endptr, 16); + if ((endptr == NULL) || (*endptr != '\0')) + return (-1); + + return (conn_handle_ports (port_local, port_remote, state)); +} /* int conn_handle_line */ + +static int conn_read_file (const char *file) +{ + FILE *fh; + char buffer[1024]; + + fh = fopen (file, "r"); + if (fh == NULL) + { + char errbuf[1024]; + ERROR ("tcpconns plugin: fopen (%s) failed: %s", + file, sstrerror (errno, errbuf, sizeof (errbuf))); + return (-1); + } + + while (fgets (buffer, sizeof (buffer), fh) != NULL) + { + conn_handle_line (buffer); + } /* while (fgets) */ + + fclose (fh); + + return (0); +} /* int conn_read_file */ + +static int conn_config (const char *key, const char *value) +{ + if (strcasecmp (key, "ListeningPorts") == 0) + { + if ((strcasecmp (value, "Yes") == 0) + || (strcasecmp (value, "True") == 0) + || (strcasecmp (value, "On") == 0)) + port_collect_listening = 1; + else + port_collect_listening = 0; + } + else if ((strcasecmp (key, "LocalPort") == 0) + || (strcasecmp (key, "RemotePort") == 0)) + { + port_entry_t *pe; + int port = atoi (value); + + if ((port < 1) || (port > 65535)) + { + ERROR ("tcpconns plugin: Invalid port: %i", port); + return (1); + } + + pe = conn_get_port_entry ((uint16_t) port, 1 /* create */); + if (pe == NULL) + { + ERROR ("tcpconns plugin: conn_get_port_entry failed."); + return (1); + } + + if (strcasecmp (key, "LocalPort") == 0) + pe->flags |= PORT_COLLECT_LOCAL; + else + pe->flags |= PORT_COLLECT_REMOTE; + } + else + { + return (-1); + } + + return (0); +} /* int conn_config */ + +static int conn_init (void) +{ + if (port_list_head == NULL) + port_collect_listening = 1; + + return (0); +} /* int conn_init */ + +static int conn_read (void) +{ + conn_reset_port_entry (); + + conn_read_file ("/proc/net/tcp"); + conn_read_file ("/proc/net/tcp6"); + + conn_submit_all (); + + return (0); +} /* int conn_read */ + +void module_register (void) +{ + plugin_register_config ("tcpconns", conn_config, + config_keys, config_keys_num); + plugin_register_init ("tcpconns", conn_init); + plugin_register_read ("tcpconns", conn_read); +} /* void module_register */ + +/* + * vim: set shiftwidth=2 softtabstop=2 tabstop=8 : + */ diff --git a/src/types.db b/src/types.db index 884dfcd4..7973cd44 100644 --- a/src/types.db +++ b/src/types.db @@ -56,6 +56,7 @@ ps_pagefaults minflt:COUNTER:0:9223372036854775807, majflt:COUNTER:0:9223372036 ps_rss value:GAUGE:0:9223372036854775807 ps_state value:GAUGE:0:65535 spam_score value:GAUGE:U:U +tcp_connections value:GAUGE:0:4294967295 temperature value:GAUGE:-273.15:U time_dispersion seconds:GAUGE:-1000000:1000000 time_offset seconds:GAUGE:-1000000:1000000 -- 2.30.2