tcpconns plugin: Done complain when reading one of the files fails.
[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 && !HAVE_SYSCTLBYNAME
27 # error "No applicable input method."
28 #endif
29
30 #if KERNEL_LINUX
31 /* #endif KERNEL_LINUX */
32
33 #elif HAVE_SYSCTLBYNAME
34 # include <sys/socketvar.h>
35 # include <sys/sysctl.h>
36 # include <net/route.h>
37 # include <netinet/in.h>
38 # include <netinet/in_systm.h>
39 # include <netinet/ip.h>
40 # include <netinet/ip6.h>
41 # include <netinet/in_pcb.h>
42 # include <netinet/ip_var.h>
43 # include <netinet/tcp.h>
44 # include <netinet/tcpip.h>
45 # include <netinet/tcp_seq.h>
46 # include <netinet/tcp_var.h>
47 #endif /* HAVE_SYSCTLBYNAME */
48
49 #if KERNEL_LINUX
50 static const char *tcp_state[] =
51 {
52   "", /* 0 */
53   "ESTABLISHED",
54   "SYN_SENT",
55   "SYN_RECV",
56   "FIN_WAIT1",
57   "FIN_WAIT2",
58   "TIME_WAIT",
59   "CLOSED",
60   "CLOSE_WAIT",
61   "LAST_ACK",
62   "LISTEN", /* 10 */
63   "CLOSING"
64 };
65
66 # define TCP_STATE_LISTEN 10
67 # define TCP_STATE_MIN 1
68 # define TCP_STATE_MAX 11
69 /* #endif KERNEL_LINUX */
70
71 #elif HAVE_SYSCTLBYNAME
72 static const char *tcp_state[] =
73 {
74   "CLOSED",
75   "LISTEN",
76   "SYN_SENT",
77   "SYN_RECV",
78   "ESTABLISHED",
79   "CLOSE_WAIT",
80   "FIN_WAIT1",
81   "CLOSING",
82   "LAST_ACK",
83   "FIN_WAIT2",
84   "TIME_WAIT"
85 };
86
87 # define TCP_STATE_LISTEN 1
88 # define TCP_STATE_MIN 0
89 # define TCP_STATE_MAX 10
90 #endif /* HAVE_SYSCTLBYNAME */
91
92 #define PORT_COLLECT_LOCAL  0x01
93 #define PORT_COLLECT_REMOTE 0x02
94 #define PORT_IS_LISTENING   0x04
95
96 typedef struct port_entry_s
97 {
98   uint16_t port;
99   uint16_t flags;
100   uint32_t count_local[TCP_STATE_MAX + 1];
101   uint32_t count_remote[TCP_STATE_MAX + 1];
102   struct port_entry_s *next;
103 } port_entry_t;
104
105 static const char *config_keys[] =
106 {
107   "ListeningPorts",
108   "LocalPort",
109   "RemotePort"
110 };
111 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
112
113 static int port_collect_listening = 0;
114 static port_entry_t *port_list_head = NULL;
115
116 static void conn_submit_port_entry (port_entry_t *pe)
117 {
118   value_t values[1];
119   value_list_t vl = VALUE_LIST_INIT;
120   int i;
121
122   vl.values = values;
123   vl.values_len = 1;
124   vl.time = time (NULL);
125   strcpy (vl.host, hostname_g);
126   strcpy (vl.plugin, "tcpconns");
127
128   if (((port_collect_listening != 0) && (pe->flags & PORT_IS_LISTENING))
129       || (pe->flags & PORT_COLLECT_LOCAL))
130   {
131     snprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
132         "%hu-local", pe->port);
133     vl.plugin_instance[sizeof (vl.plugin_instance) - 1] = '\0';
134
135     for (i = 1; i <= TCP_STATE_MAX; i++)
136     {
137       vl.values[0].gauge = pe->count_local[i];
138
139       strncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance));
140       vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
141
142       plugin_dispatch_values ("tcp_connections", &vl);
143     }
144   }
145
146   if (pe->flags & PORT_COLLECT_REMOTE)
147   {
148     snprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
149         "%hu-remote", pe->port);
150     vl.plugin_instance[sizeof (vl.plugin_instance) - 1] = '\0';
151
152     for (i = 1; i <= TCP_STATE_MAX; i++)
153     {
154       vl.values[0].gauge = pe->count_remote[i];
155
156       strncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance));
157       vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
158
159       plugin_dispatch_values ("tcp_connections", &vl);
160     }
161   }
162 } /* void conn_submit */
163
164 static void conn_submit_all (void)
165 {
166   port_entry_t *pe;
167
168   for (pe = port_list_head; pe != NULL; pe = pe->next)
169     conn_submit_port_entry (pe);
170 } /* void conn_submit_all */
171
172 static port_entry_t *conn_get_port_entry (uint16_t port, int create)
173 {
174   port_entry_t *ret;
175
176   ret = port_list_head;
177   while (ret != NULL)
178   {
179     if (ret->port == port)
180       break;
181     ret = ret->next;
182   }
183
184   if ((ret == NULL) && (create != 0))
185   {
186     ret = (port_entry_t *) malloc (sizeof (port_entry_t));
187     if (ret == NULL)
188       return (NULL);
189     memset (ret, '\0', sizeof (port_entry_t));
190
191     ret->port = port;
192     ret->next = port_list_head;
193     port_list_head = ret;
194   }
195
196   return (ret);
197 } /* port_entry_t *conn_get_port_entry */
198
199 /* Removes ports that were added automatically due to the `ListeningPorts'
200  * setting but which are no longer listening. */
201 static void conn_reset_port_entry (void)
202 {
203   port_entry_t *prev = NULL;
204   port_entry_t *pe = port_list_head;
205
206   while (pe != NULL)
207   {
208     /* If this entry was created while reading the files (ant not when handling
209      * the configuration) remove it now. */
210     if ((pe->flags & (PORT_COLLECT_LOCAL
211             | PORT_COLLECT_REMOTE
212             | PORT_IS_LISTENING)) == 0)
213     {
214       port_entry_t *next = pe->next;
215
216       DEBUG ("tcpconns plugin: Removing temporary entry "
217           "for listening port %hu", pe->port);
218
219       if (prev == NULL)
220         port_list_head = next;
221       else
222         prev->next = next;
223
224       sfree (pe);
225       pe = next;
226
227       continue;
228     }
229
230     memset (pe->count_local, '\0', sizeof (pe->count_local));
231     memset (pe->count_remote, '\0', sizeof (pe->count_remote));
232     pe->flags &= ~PORT_IS_LISTENING;
233
234     pe = pe->next;
235   }
236 } /* void conn_reset_port_entry */
237
238 static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t state)
239 {
240   port_entry_t *pe = NULL;
241
242   if ((state > TCP_STATE_MAX)
243 #if TCP_STATE_MIN > 0
244       || (state < TCP_STATE_MIN)
245 #endif
246      )
247   {
248     NOTICE ("tcpconns plugin: Ignoring connection with unknown state 0x%02x.",
249         state);
250     return (-1);
251   }
252
253   /* Listening sockets */
254   if ((state == TCP_STATE_LISTEN) && (port_collect_listening != 0))
255   {
256     pe = conn_get_port_entry (port_local, 1 /* create */);
257     if (pe != NULL)
258       pe->flags |= PORT_IS_LISTENING;
259   }
260
261   DEBUG ("tcpconns plugin: Connection %hu <-> %hu (%s)",
262       port_local, port_remote, tcp_state[state]);
263
264   pe = conn_get_port_entry (port_local, 0 /* no create */);
265   if (pe != NULL)
266     pe->count_local[state]++;
267
268   pe = conn_get_port_entry (port_remote, 0 /* no create */);
269   if (pe != NULL)
270     pe->count_remote[state]++;
271
272   return (0);
273 } /* int conn_handle_ports */
274
275 #if KERNEL_LINUX
276 static int conn_handle_line (char *buffer)
277 {
278   char *fields[32];
279   int   fields_len;
280
281   char *endptr;
282
283   char *port_local_str;
284   char *port_remote_str;
285   uint16_t port_local;
286   uint16_t port_remote;
287
288   uint8_t state;
289
290   int buffer_len = strlen (buffer);
291
292   while ((buffer_len > 0) && (buffer[buffer_len - 1] < 32))
293     buffer[--buffer_len] = '\0';
294   if (buffer_len <= 0)
295     return (-1);
296
297   fields_len = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
298   if (fields_len < 12)
299   {
300     DEBUG ("tcpconns plugin: Got %i fields, expected at least 12.", fields_len);
301     return (-1);
302   }
303
304   port_local_str  = strchr (fields[1], ':');
305   port_remote_str = strchr (fields[2], ':');
306
307   if ((port_local_str == NULL) || (port_remote_str == NULL))
308     return (-1);
309   port_local_str++;
310   port_remote_str++;
311   if ((*port_local_str == '\0') || (*port_remote_str == '\0'))
312     return (-1);
313
314   endptr = NULL;
315   port_local = (uint16_t) strtol (port_local_str, &endptr, 16);
316   if ((endptr == NULL) || (*endptr != '\0'))
317     return (-1);
318
319   endptr = NULL;
320   port_remote = (uint16_t) strtol (port_remote_str, &endptr, 16);
321   if ((endptr == NULL) || (*endptr != '\0'))
322     return (-1);
323
324   endptr = NULL;
325   state = (uint8_t) strtol (fields[3], &endptr, 16);
326   if ((endptr == NULL) || (*endptr != '\0'))
327     return (-1);
328
329   return (conn_handle_ports (port_local, port_remote, state));
330 } /* int conn_handle_line */
331
332 static int conn_read_file (const char *file)
333 {
334   FILE *fh;
335   char buffer[1024];
336
337   fh = fopen (file, "r");
338   if (fh == NULL)
339     return (-1);
340
341   while (fgets (buffer, sizeof (buffer), fh) != NULL)
342   {
343     conn_handle_line (buffer);
344   } /* while (fgets) */
345
346   fclose (fh);
347
348   return (0);
349 } /* int conn_read_file */
350 /* #endif KERNEL_LINUX */
351
352 #elif HAVE_SYSCTLBYNAME
353 #endif /* HAVE_SYSCTLBYNAME */
354
355 static int conn_config (const char *key, const char *value)
356 {
357   if (strcasecmp (key, "ListeningPorts") == 0)
358   {
359     if ((strcasecmp (value, "Yes") == 0)
360         || (strcasecmp (value, "True") == 0)
361         || (strcasecmp (value, "On") == 0))
362       port_collect_listening = 1;
363     else
364       port_collect_listening = 0;
365   }
366   else if ((strcasecmp (key, "LocalPort") == 0)
367       || (strcasecmp (key, "RemotePort") == 0))
368   {
369       port_entry_t *pe;
370       int port = atoi (value);
371
372       if ((port < 1) || (port > 65535))
373       {
374         ERROR ("tcpconns plugin: Invalid port: %i", port);
375         return (1);
376       }
377
378       pe = conn_get_port_entry ((uint16_t) port, 1 /* create */);
379       if (pe == NULL)
380       {
381         ERROR ("tcpconns plugin: conn_get_port_entry failed.");
382         return (1);
383       }
384
385       if (strcasecmp (key, "LocalPort") == 0)
386         pe->flags |= PORT_COLLECT_LOCAL;
387       else
388         pe->flags |= PORT_COLLECT_REMOTE;
389   }
390   else
391   {
392     return (-1);
393   }
394
395   return (0);
396 } /* int conn_config */
397
398 #if KERNEL_LINUX
399 static int conn_init (void)
400 {
401   if (port_list_head == NULL)
402     port_collect_listening = 1;
403
404   return (0);
405 } /* int conn_init */
406
407 static int conn_read (void)
408 {
409   int errors_num = 0;
410
411   conn_reset_port_entry ();
412
413   if (conn_read_file ("/proc/net/tcp") != 0)
414     errors_num++;
415   if (conn_read_file ("/proc/net/tcp6") != 0)
416     errors_num++;
417
418   if (errors_num < 2)
419   {
420     conn_submit_all ();
421   }
422   else
423   {
424     ERROR ("tcpconns plugin: Neither /proc/net/tcp nor /proc/net/tcp6 "
425         "coult be read.");
426     return (-1);
427   }
428
429   return (0);
430 } /* int conn_read */
431 /* #endif KERNEL_LINUX */
432
433 #elif HAVE_SYSCTLBYNAME
434 static int conn_read (void)
435 {
436   int status;
437   char *buffer;
438   size_t buffer_len;;
439
440   struct xinpgen *in_orig;
441   struct xinpgen *in_ptr;
442
443   conn_reset_port_entry ();
444
445   buffer_len = 0;
446   status = sysctlbyname ("net.inet.tcp.pcblist", NULL, &buffer_len, 0, 0);
447   if (status < 0)
448   {
449     ERROR ("tcpconns plugin: sysctlbyname failed.");
450     return (-1);
451   }
452
453   buffer = (char *) malloc (buffer_len);
454   if (buffer == NULL)
455   {
456     ERROR ("tcpconns plugin: malloc failed.");
457     return (-1);
458   }
459
460   status = sysctlbyname ("net.inet.tcp.pcblist", buffer, &buffer_len, 0, 0);
461   if (status < 0)
462   {
463     ERROR ("tcpconns plugin: sysctlbyname failed.");
464     sfree (buffer);
465     return (-1);
466   }
467
468   if (buffer_len <= sizeof (struct xinpgen))
469   {
470     ERROR ("tcpconns plugin: (buffer_len <= sizeof (struct xinpgen))");
471     sfree (buffer);
472     return (-1);
473   }
474
475   in_orig = (struct xinpgen *) buffer;
476   for (in_ptr = (struct xinpgen *) (((char *) in_orig) + in_orig->xig_len);
477       in_ptr->xig_len > sizeof (struct xinpgen);
478       in_ptr = (struct xinpgen *) (((char *) in_ptr) + in_ptr->xig_len))
479   {
480     struct tcpcb *tp = &((struct xtcpcb *) in_ptr)->xt_tp;
481     struct inpcb *inp = &((struct xtcpcb *) in_ptr)->xt_inp;
482     struct xsocket *so = &((struct xtcpcb *) in_ptr)->xt_socket;
483
484     /* Ignore non-TCP sockets */
485     if (so->xso_protocol != IPPROTO_TCP)
486       continue;
487
488     /* Ignore PCBs which were freed during copyout. */
489     if (inp->inp_gencnt > in_orig->xig_gen)
490       continue;
491
492     if (((inp->inp_vflag & INP_IPV4) == 0)
493         && ((inp->inp_vflag & INP_IPV6) == 0))
494       continue;
495
496     conn_handle_ports (inp->inp_lport, inp->inp_fport, tp->t_state);
497   } /* for (in_ptr) */
498
499   in_orig = NULL;
500   in_ptr = NULL;
501   sfree (buffer);
502
503   conn_submit_all ();
504
505   return (0);
506 } /* int conn_read */
507 #endif /* HAVE_SYSCTLBYNAME */
508
509 void module_register (void)
510 {
511         plugin_register_config ("tcpconns", conn_config,
512                         config_keys, config_keys_num);
513 #if KERNEL_LINUX
514         plugin_register_init ("tcpconns", conn_init);
515 #endif
516         plugin_register_read ("tcpconns", conn_read);
517 } /* void module_register */
518
519 /*
520  * vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
521  */