Merge branch 'collectd-4.1'
[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   {
340     char errbuf[1024];
341     ERROR ("tcpconns plugin: fopen (%s) failed: %s",
342         file, sstrerror (errno, errbuf, sizeof (errbuf)));
343     return (-1);
344   }
345
346   while (fgets (buffer, sizeof (buffer), fh) != NULL)
347   {
348     conn_handle_line (buffer);
349   } /* while (fgets) */
350
351   fclose (fh);
352
353   return (0);
354 } /* int conn_read_file */
355 /* #endif KERNEL_LINUX */
356
357 #elif HAVE_SYSCTLBYNAME
358 #endif /* HAVE_SYSCTLBYNAME */
359
360 static int conn_config (const char *key, const char *value)
361 {
362   if (strcasecmp (key, "ListeningPorts") == 0)
363   {
364     if ((strcasecmp (value, "Yes") == 0)
365         || (strcasecmp (value, "True") == 0)
366         || (strcasecmp (value, "On") == 0))
367       port_collect_listening = 1;
368     else
369       port_collect_listening = 0;
370   }
371   else if ((strcasecmp (key, "LocalPort") == 0)
372       || (strcasecmp (key, "RemotePort") == 0))
373   {
374       port_entry_t *pe;
375       int port = atoi (value);
376
377       if ((port < 1) || (port > 65535))
378       {
379         ERROR ("tcpconns plugin: Invalid port: %i", port);
380         return (1);
381       }
382
383       pe = conn_get_port_entry ((uint16_t) port, 1 /* create */);
384       if (pe == NULL)
385       {
386         ERROR ("tcpconns plugin: conn_get_port_entry failed.");
387         return (1);
388       }
389
390       if (strcasecmp (key, "LocalPort") == 0)
391         pe->flags |= PORT_COLLECT_LOCAL;
392       else
393         pe->flags |= PORT_COLLECT_REMOTE;
394   }
395   else
396   {
397     return (-1);
398   }
399
400   return (0);
401 } /* int conn_config */
402
403 #if KERNEL_LINUX
404 static int conn_init (void)
405 {
406   if (port_list_head == NULL)
407     port_collect_listening = 1;
408
409   return (0);
410 } /* int conn_init */
411
412 static int conn_read (void)
413 {
414   conn_reset_port_entry ();
415
416   conn_read_file ("/proc/net/tcp");
417   conn_read_file ("/proc/net/tcp6");
418
419   conn_submit_all ();
420
421   return (0);
422 } /* int conn_read */
423 /* #endif KERNEL_LINUX */
424
425 #elif HAVE_SYSCTLBYNAME
426 static int conn_read (void)
427 {
428   int status;
429   char *buffer;
430   size_t buffer_len;;
431
432   struct xinpgen *in_orig;
433   struct xinpgen *in_ptr;
434
435   conn_reset_port_entry ();
436
437   buffer_len = 0;
438   status = sysctlbyname ("net.inet.tcp.pcblist", NULL, &buffer_len, 0, 0);
439   if (status < 0)
440   {
441     ERROR ("tcpconns plugin: sysctlbyname failed.");
442     return (-1);
443   }
444
445   buffer = (char *) malloc (buffer_len);
446   if (buffer == NULL)
447   {
448     ERROR ("tcpconns plugin: malloc failed.");
449     return (-1);
450   }
451
452   status = sysctlbyname ("net.inet.tcp.pcblist", buffer, &buffer_len, 0, 0);
453   if (status < 0)
454   {
455     ERROR ("tcpconns plugin: sysctlbyname failed.");
456     sfree (buffer);
457     return (-1);
458   }
459
460   if (buffer_len <= sizeof (struct xinpgen))
461   {
462     ERROR ("tcpconns plugin: (buffer_len <= sizeof (struct xinpgen))");
463     sfree (buffer);
464     return (-1);
465   }
466
467   in_orig = (struct xinpgen *) buffer;
468   for (in_ptr = (struct xinpgen *) (((char *) in_orig) + in_orig->xig_len);
469       in_ptr->xig_len > sizeof (struct xinpgen);
470       in_ptr = (struct xinpgen *) (((char *) in_ptr) + in_ptr->xig_len))
471   {
472     struct tcpcb *tp = &((struct xtcpcb *) in_ptr)->xt_tp;
473     struct inpcb *inp = &((struct xtcpcb *) in_ptr)->xt_inp;
474     struct xsocket *so = &((struct xtcpcb *) in_ptr)->xt_socket;
475
476     /* Ignore non-TCP sockets */
477     if (so->xso_protocol != IPPROTO_TCP)
478       continue;
479
480     /* Ignore PCBs which were freed during copyout. */
481     if (inp->inp_gencnt > in_orig->xig_gen)
482       continue;
483
484     if (((inp->inp_vflag & INP_IPV4) == 0)
485         && ((inp->inp_vflag & INP_IPV6) == 0))
486       continue;
487
488     conn_handle_ports (inp->inp_lport, inp->inp_fport, tp->t_state);
489   } /* for (in_ptr) */
490
491   in_orig = NULL;
492   in_ptr = NULL;
493   sfree (buffer);
494
495   conn_submit_all ();
496
497   return (0);
498 } /* int conn_read */
499 #endif /* HAVE_SYSCTLBYNAME */
500
501 void module_register (void)
502 {
503         plugin_register_config ("tcpconns", conn_config,
504                         config_keys, config_keys_num);
505 #if KERNEL_LINUX
506         plugin_register_init ("tcpconns", conn_init);
507 #endif
508         plugin_register_read ("tcpconns", conn_read);
509 } /* void module_register */
510
511 /*
512  * vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
513  */