tcpconns plugin: Changed the plugin to collect all TCP states, nut just `established'.
[collectd.git] / src / tcpconns.c
1 /**
2  * collectd - src/tcpconns.c
3  * Copyright (C) 2007  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Author:
19  *   Florian octo Forster <octo at verplant.org>
20  **/
21
22 #include "collectd.h"
23 #include "common.h"
24 #include "plugin.h"
25
26 #if !KERNEL_LINUX
27 # error "No applicable input method."
28 #endif
29
30 #define TCP_STATE_LISTEN 10
31 #define TCP_STATE_MAX 11
32
33 #define PORT_COLLECT_LOCAL  0x01
34 #define PORT_COLLECT_REMOTE 0x02
35 #define PORT_IS_LISTENING   0x04
36
37 typedef struct port_entry_s
38 {
39   uint16_t port;
40   uint16_t flags;
41   uint32_t count_local[TCP_STATE_MAX + 1];
42   uint32_t count_remote[TCP_STATE_MAX + 1];
43   struct port_entry_s *next;
44 } port_entry_t;
45
46 static const char *config_keys[] =
47 {
48   "ListeningPorts",
49   "LocalPort",
50   "RemotePort"
51 };
52 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
53
54 static const char *tcp_state[] =
55 {
56   "", /* 0 */
57   "ESTABLISHED",
58   "SYN_SENT",
59   "SYN_RECV",
60   "FIN_WAIT1",
61   "FIN_WAIT2",
62   "TIME_WAIT",
63   "CLOSE",
64   "CLOSE_WAIT",
65   "LAST_ACK",
66   "LISTEN", /* 10 */
67   "CLOSING"
68 };
69
70 static int port_collect_listening = 0;
71 static port_entry_t *port_list_head = NULL;
72
73 static void conn_submit_port_entry (port_entry_t *pe)
74 {
75   value_t values[1];
76   value_list_t vl = VALUE_LIST_INIT;
77   int i;
78
79   vl.values = values;
80   vl.values_len = 1;
81   vl.time = time (NULL);
82   strcpy (vl.host, hostname_g);
83   strcpy (vl.plugin, "tcpconns");
84
85   if (((port_collect_listening != 0) && (pe->flags & PORT_IS_LISTENING))
86       || (pe->flags & PORT_COLLECT_LOCAL))
87   {
88     snprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
89         "%hu-local", pe->port);
90     vl.plugin_instance[sizeof (vl.plugin_instance) - 1] = '\0';
91
92     for (i = 1; i <= TCP_STATE_MAX; i++)
93     {
94       vl.values[0].gauge = pe->count_local[i];
95
96       strncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance));
97       vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
98
99       plugin_dispatch_values ("tcp_connections", &vl);
100     }
101   }
102
103   if (pe->flags & PORT_COLLECT_REMOTE)
104   {
105     snprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
106         "%hu-remote", pe->port);
107     vl.plugin_instance[sizeof (vl.plugin_instance) - 1] = '\0';
108
109     for (i = 1; i <= TCP_STATE_MAX; i++)
110     {
111       vl.values[0].gauge = pe->count_remote[i];
112
113       strncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance));
114       vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
115
116       plugin_dispatch_values ("tcp_connections", &vl);
117     }
118   }
119 } /* void conn_submit */
120
121 static void conn_submit_all (void)
122 {
123   port_entry_t *pe;
124
125   for (pe = port_list_head; pe != NULL; pe = pe->next)
126     conn_submit_port_entry (pe);
127 } /* void conn_submit_all */
128
129 static port_entry_t *conn_get_port_entry (uint16_t port, int create)
130 {
131   port_entry_t *ret;
132
133   ret = port_list_head;
134   while (ret != NULL)
135   {
136     if (ret->port == port)
137       break;
138     ret = ret->next;
139   }
140
141   if ((ret == NULL) && (create != 0))
142   {
143     ret = (port_entry_t *) malloc (sizeof (port_entry_t));
144     if (ret == NULL)
145       return (NULL);
146     memset (ret, '\0', sizeof (port_entry_t));
147
148     ret->port = port;
149     ret->next = port_list_head;
150     port_list_head = ret;
151   }
152
153   return (ret);
154 } /* port_entry_t *conn_get_port_entry */
155
156 /* Removes ports that were added automatically due to the `ListeningPorts'
157  * setting but which are no longer listening. */
158 static void conn_reset_port_entry (void)
159 {
160   port_entry_t *prev = NULL;
161   port_entry_t *pe = port_list_head;
162
163   while (pe != NULL)
164   {
165     /* If this entry was created while reading the files (ant not when handling
166      * the configuration) remove it now. */
167     if ((pe->flags & (PORT_COLLECT_LOCAL
168             | PORT_COLLECT_REMOTE
169             | PORT_IS_LISTENING)) == 0)
170     {
171       port_entry_t *next = pe->next;
172
173       DEBUG ("tcpconns plugin: Removing temporary entry "
174           "for listening port %hu", pe->port);
175
176       if (prev == NULL)
177         port_list_head = next;
178       else
179         prev->next = next;
180
181       sfree (pe);
182       pe = next;
183
184       continue;
185     }
186
187     memset (pe->count_local, '\0', sizeof (pe->count_local));
188     memset (pe->count_remote, '\0', sizeof (pe->count_remote));
189     pe->flags &= ~PORT_IS_LISTENING;
190
191     pe = pe->next;
192   }
193 } /* void conn_reset_port_entry */
194
195 static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t state)
196 {
197   port_entry_t *pe = NULL;
198
199   if ((state == 0) || (state > TCP_STATE_MAX))
200   {
201     NOTICE ("tcpconns plugin: Ignoring connection with unknown state 0x%02x.",
202         state);
203     return (-1);
204   }
205
206   /* Listening sockets */
207   if ((state == TCP_STATE_LISTEN) && (port_collect_listening != 0))
208   {
209     pe = conn_get_port_entry (port_local, 1 /* create */);
210     if (pe != NULL)
211       pe->flags |= PORT_IS_LISTENING;
212   }
213
214   DEBUG ("tcpconns plugin: Connection %hu <-> %hu (%s)",
215       port_local, port_remote, tcp_state[state]);
216
217   pe = conn_get_port_entry (port_local, 0 /* no create */);
218   if (pe != NULL)
219     pe->count_local[state]++;
220
221   pe = conn_get_port_entry (port_remote, 0 /* no create */);
222   if (pe != NULL)
223     pe->count_remote[state]++;
224
225   return (0);
226 } /* int conn_handle_ports */
227
228 static int conn_handle_line (char *buffer)
229 {
230   char *fields[32];
231   int   fields_len;
232
233   char *endptr;
234
235   char *port_local_str;
236   char *port_remote_str;
237   uint16_t port_local;
238   uint16_t port_remote;
239
240   uint8_t state;
241
242   int buffer_len = strlen (buffer);
243
244   while ((buffer_len > 0) && (buffer[buffer_len - 1] < 32))
245     buffer[--buffer_len] = '\0';
246   if (buffer_len <= 0)
247     return (-1);
248
249   fields_len = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
250   if (fields_len < 12)
251   {
252     DEBUG ("tcpconns plugin: Got %i fields, expected at least 12.", fields_len);
253     return (-1);
254   }
255
256   port_local_str  = strchr (fields[1], ':');
257   port_remote_str = strchr (fields[2], ':');
258
259   if ((port_local_str == NULL) || (port_remote_str == NULL))
260     return (-1);
261   port_local_str++;
262   port_remote_str++;
263   if ((*port_local_str == '\0') || (*port_remote_str == '\0'))
264     return (-1);
265
266   endptr = NULL;
267   port_local = (uint16_t) strtol (port_local_str, &endptr, 16);
268   if ((endptr == NULL) || (*endptr != '\0'))
269     return (-1);
270
271   endptr = NULL;
272   port_remote = (uint16_t) strtol (port_remote_str, &endptr, 16);
273   if ((endptr == NULL) || (*endptr != '\0'))
274     return (-1);
275
276   endptr = NULL;
277   state = (uint8_t) strtol (fields[3], &endptr, 16);
278   if ((endptr == NULL) || (*endptr != '\0'))
279     return (-1);
280
281   return (conn_handle_ports (port_local, port_remote, state));
282 } /* int conn_handle_line */
283
284 static int conn_read_file (const char *file)
285 {
286   FILE *fh;
287   char buffer[1024];
288
289   fh = fopen (file, "r");
290   if (fh == NULL)
291   {
292     char errbuf[1024];
293     ERROR ("tcpconns plugin: fopen (%s) failed: %s",
294         file, sstrerror (errno, errbuf, sizeof (errbuf)));
295     return (-1);
296   }
297
298   while (fgets (buffer, sizeof (buffer), fh) != NULL)
299   {
300     conn_handle_line (buffer);
301   } /* while (fgets) */
302
303   fclose (fh);
304
305   return (0);
306 } /* int conn_read_file */
307
308 static int conn_config (const char *key, const char *value)
309 {
310   if (strcasecmp (key, "ListeningPorts") == 0)
311   {
312     if ((strcasecmp (value, "Yes") == 0)
313         || (strcasecmp (value, "True") == 0)
314         || (strcasecmp (value, "On") == 0))
315       port_collect_listening = 1;
316     else
317       port_collect_listening = 0;
318   }
319   else if ((strcasecmp (key, "LocalPort") == 0)
320       || (strcasecmp (key, "RemotePort") == 0))
321   {
322       port_entry_t *pe;
323       int port = atoi (value);
324
325       if ((port < 1) || (port > 65535))
326       {
327         ERROR ("tcpconns plugin: Invalid port: %i", port);
328         return (1);
329       }
330
331       pe = conn_get_port_entry ((uint16_t) port, 1 /* create */);
332       if (pe == NULL)
333       {
334         ERROR ("tcpconns plugin: conn_get_port_entry failed.");
335         return (1);
336       }
337
338       if (strcasecmp (key, "LocalPort") == 0)
339         pe->flags |= PORT_COLLECT_LOCAL;
340       else
341         pe->flags |= PORT_COLLECT_REMOTE;
342   }
343   else
344   {
345     return (-1);
346   }
347
348   return (0);
349 } /* int conn_config */
350
351 static int conn_init (void)
352 {
353   if (port_list_head == NULL)
354     port_collect_listening = 1;
355
356   return (0);
357 } /* int conn_init */
358
359 static int conn_read (void)
360 {
361   conn_reset_port_entry ();
362
363   conn_read_file ("/proc/net/tcp");
364   conn_read_file ("/proc/net/tcp6");
365
366   conn_submit_all ();
367
368   return (0);
369 } /* int conn_read */
370
371 void module_register (void)
372 {
373         plugin_register_config ("tcpconns", conn_config,
374                         config_keys, config_keys_num);
375         plugin_register_init ("tcpconns", conn_init);
376         plugin_register_read ("tcpconns", conn_read);
377 } /* void module_register */
378
379 /*
380  * vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
381  */