890e07f697c5c7c00b79a5f9dfe896a13e2c7d54
[collectd.git] / src / tcpconns.c
1 /**
2  * collectd - src/tcpconns.c
3  * Copyright (C) 2007,2008  Florian octo Forster
4  * Copyright (C) 2008       Michael Stapelberg
5  *
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.
9  *
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.
14  *
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
18  *
19  * Author:
20  *   Florian octo Forster <octo at verplant.org>
21  *   Michael Stapelberg <michael+git at stapelberg.de>
22  **/
23
24 /**
25  * Code within `HAVE_LIBKVM_NLIST' blocks is provided under the following
26  * license:
27  *
28  * $collectd: parts of tcpconns.c, 2008/08/08 03:48:30 Michael Stapelberg $
29  * $OpenBSD: inet.c,v 1.100 2007/06/19 05:28:30 ray Exp $
30  * $NetBSD: inet.c,v 1.14 1995/10/03 21:42:37 thorpej Exp $
31  *
32  * Copyright (c) 1983, 1988, 1993
33  *      The Regents of the University of California.  All rights reserved.
34  *
35  * Redistribution and use in source and binary forms, with or without
36  * modification, are permitted provided that the following conditions
37  * are met:
38  * 1. Redistributions of source code must retain the above copyright
39  *    notice, this list of conditions and the following disclaimer.
40  * 2. Redistributions in binary form must reproduce the above copyright
41  *    notice, this list of conditions and the following disclaimer in the
42  *    documentation and/or other materials provided with the distribution.
43  * 3. Neither the name of the University nor the names of its contributors
44  *    may be used to endorse or promote products derived from this software
45  *    without specific prior written permission.
46  *
47  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
48  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
49  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
50  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
51  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
52  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
53  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
54  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
55  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
56  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
57  * SUCH DAMAGE.
58  */
59
60 #include "collectd.h"
61 #include "common.h"
62 #include "plugin.h"
63
64 #if defined(__OpenBSD__) || defined(__NetBSD__)
65 #undef HAVE_SYSCTLBYNAME /* force HAVE_LIBKVM_NLIST path */
66 #endif
67
68 #if !KERNEL_LINUX && !HAVE_SYSCTLBYNAME && !HAVE_LIBKVM_NLIST && !KERNEL_AIX
69 # error "No applicable input method."
70 #endif
71
72 #if KERNEL_LINUX
73 # include <asm/types.h>
74 /* sys/socket.h is necessary to compile when using netlink on older systems. */
75 # include <sys/socket.h>
76 # include <linux/netlink.h>
77 # include <linux/inet_diag.h>
78 # include <sys/socket.h>
79 # include <arpa/inet.h>
80 /* #endif KERNEL_LINUX */
81
82 #elif HAVE_SYSCTLBYNAME
83 # include <sys/socketvar.h>
84 # include <sys/sysctl.h>
85
86 /* Some includes needed for compiling on FreeBSD */
87 #include <sys/time.h>
88 #if HAVE_SYS_TYPES_H
89 # include <sys/types.h>
90 #endif
91 #if HAVE_SYS_SOCKET_H
92 # include <sys/socket.h>
93 #endif
94 #if HAVE_NET_IF_H
95 # include <net/if.h>
96 #endif
97
98 # include <net/route.h>
99 # include <netinet/in.h>
100 # include <netinet/in_systm.h>
101 # include <netinet/ip.h>
102 # include <netinet/ip6.h>
103 # include <netinet/in_pcb.h>
104 # include <netinet/ip_var.h>
105 # include <netinet/tcp.h>
106 # include <netinet/tcpip.h>
107 # include <netinet/tcp_seq.h>
108 # include <netinet/tcp_var.h>
109 /* #endif HAVE_SYSCTLBYNAME */
110
111 /* This is for OpenBSD and NetBSD. */
112 #elif HAVE_LIBKVM_NLIST
113 # include <sys/queue.h>
114 # include <sys/socket.h>
115 # include <net/route.h>
116 # include <netinet/in.h>
117 # include <netinet/in_systm.h>
118 # include <netinet/ip.h>
119 # include <netinet/ip_var.h>
120 # include <netinet/in_pcb.h>
121 # include <netinet/tcp.h>
122 # include <netinet/tcp_timer.h>
123 # include <netinet/tcp_var.h>
124 # include <netdb.h>
125 # include <arpa/inet.h>
126 # if !defined(HAVE_BSD_NLIST_H) || !HAVE_BSD_NLIST_H
127 #  include <nlist.h>
128 # else /* HAVE_BSD_NLIST_H */
129 #  include <bsd/nlist.h>
130 # endif
131 # include <kvm.h>
132 /* #endif HAVE_LIBKVM_NLIST */
133
134 #elif KERNEL_AIX
135 # include <arpa/inet.h>
136 # include <sys/socketvar.h>
137 #endif /* KERNEL_AIX */
138
139 #if KERNEL_LINUX
140 struct nlreq {
141   struct nlmsghdr nlh;
142   struct inet_diag_req r;
143 };
144
145 static const char *tcp_state[] =
146 {
147   "", /* 0 */
148   "ESTABLISHED",
149   "SYN_SENT",
150   "SYN_RECV",
151   "FIN_WAIT1",
152   "FIN_WAIT2",
153   "TIME_WAIT",
154   "CLOSED",
155   "CLOSE_WAIT",
156   "LAST_ACK",
157   "LISTEN", /* 10 */
158   "CLOSING"
159 };
160
161 # define TCP_STATE_LISTEN 10
162 # define TCP_STATE_MIN 1
163 # define TCP_STATE_MAX 11
164 /* #endif KERNEL_LINUX */
165
166 #elif HAVE_SYSCTLBYNAME
167 static const char *tcp_state[] =
168 {
169   "CLOSED",
170   "LISTEN",
171   "SYN_SENT",
172   "SYN_RECV",
173   "ESTABLISHED",
174   "CLOSE_WAIT",
175   "FIN_WAIT1",
176   "CLOSING",
177   "LAST_ACK",
178   "FIN_WAIT2",
179   "TIME_WAIT"
180 };
181
182 # define TCP_STATE_LISTEN 1
183 # define TCP_STATE_MIN 0
184 # define TCP_STATE_MAX 10
185 /* #endif HAVE_SYSCTLBYNAME */
186
187 #elif HAVE_LIBKVM_NLIST
188 static const char *tcp_state[] =
189 {
190   "CLOSED",
191   "LISTEN",
192   "SYN_SENT",
193   "SYN_RECV",
194   "ESTABLISHED",
195   "CLOSE_WAIT",
196   "FIN_WAIT1",
197   "CLOSING",
198   "LAST_ACK",
199   "FIN_WAIT2",
200   "TIME_WAIT"
201 };
202
203 static kvm_t *kvmd;
204 static u_long      inpcbtable_off = 0;
205 struct inpcbtable *inpcbtable_ptr = NULL;
206
207 # define TCP_STATE_LISTEN 1
208 # define TCP_STATE_MIN 1
209 # define TCP_STATE_MAX 10
210 /* #endif HAVE_LIBKVM_NLIST */
211
212 #elif KERNEL_AIX
213 static const char *tcp_state[] =
214 {
215   "CLOSED",
216   "LISTEN",
217   "SYN_SENT",
218   "SYN_RECV",
219   "ESTABLISHED",
220   "CLOSE_WAIT",
221   "FIN_WAIT1",
222   "CLOSING",
223   "LAST_ACK",
224   "FIN_WAIT2",
225   "TIME_WAIT"
226 };
227
228 # define TCP_STATE_LISTEN 1
229 # define TCP_STATE_MIN 0
230 # define TCP_STATE_MAX 10
231
232 struct netinfo_conn {
233   uint32_t unknow1[2];
234   uint16_t dstport;
235   uint16_t unknow2;
236   struct in6_addr dstaddr;
237   uint16_t srcport;
238   uint16_t unknow3;
239   struct in6_addr srcaddr;
240   uint32_t unknow4[36];
241   uint16_t tcp_state;
242   uint16_t unknow5[7];
243 };
244
245 struct netinfo_header {
246   unsigned int proto;
247   unsigned int size;
248 };
249
250 # define NETINFO_TCP 3
251 extern int netinfo (int proto, void *data, int *size,  int n);
252 #endif /* KERNEL_AIX */
253
254 #define PORT_COLLECT_LOCAL  0x01
255 #define PORT_COLLECT_REMOTE 0x02
256 #define PORT_IS_LISTENING   0x04
257
258 typedef struct port_entry_s
259 {
260   uint16_t port;
261   uint16_t flags;
262   uint32_t count_local[TCP_STATE_MAX + 1];
263   uint32_t count_remote[TCP_STATE_MAX + 1];
264   struct port_entry_s *next;
265 } port_entry_t;
266
267 static const char *config_keys[] =
268 {
269   "ListeningPorts",
270   "LocalPort",
271   "RemotePort"
272 };
273 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
274
275 static int port_collect_listening = 0;
276 static port_entry_t *port_list_head = NULL;
277
278 #if KERNEL_LINUX
279 static uint32_t sequence_number = 0;
280
281 enum
282 {
283   SRC_DUNNO,
284   SRC_NETLINK,
285   SRC_PROC
286 } linux_source = SRC_DUNNO;
287 #endif
288
289 static void conn_submit_port_entry (port_entry_t *pe)
290 {
291   value_t values[1];
292   value_list_t vl = VALUE_LIST_INIT;
293   int i;
294
295   vl.values = values;
296   vl.values_len = 1;
297   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
298   sstrncpy (vl.plugin, "tcpconns", sizeof (vl.plugin));
299   sstrncpy (vl.type, "tcp_connections", sizeof (vl.type));
300
301   if (((port_collect_listening != 0) && (pe->flags & PORT_IS_LISTENING))
302       || (pe->flags & PORT_COLLECT_LOCAL))
303   {
304     ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
305         "%"PRIu16"-local", pe->port);
306
307     for (i = 1; i <= TCP_STATE_MAX; i++)
308     {
309       vl.values[0].gauge = pe->count_local[i];
310
311       sstrncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance));
312
313       plugin_dispatch_values (&vl);
314     }
315   }
316
317   if (pe->flags & PORT_COLLECT_REMOTE)
318   {
319     ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
320         "%"PRIu16"-remote", pe->port);
321
322     for (i = 1; i <= TCP_STATE_MAX; i++)
323     {
324       vl.values[0].gauge = pe->count_remote[i];
325
326       sstrncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance));
327
328       plugin_dispatch_values (&vl);
329     }
330   }
331 } /* void conn_submit */
332
333 static void conn_submit_all (void)
334 {
335   port_entry_t *pe;
336
337   for (pe = port_list_head; pe != NULL; pe = pe->next)
338     conn_submit_port_entry (pe);
339 } /* void conn_submit_all */
340
341 static port_entry_t *conn_get_port_entry (uint16_t port, int create)
342 {
343   port_entry_t *ret;
344
345   ret = port_list_head;
346   while (ret != NULL)
347   {
348     if (ret->port == port)
349       break;
350     ret = ret->next;
351   }
352
353   if ((ret == NULL) && (create != 0))
354   {
355     ret = (port_entry_t *) malloc (sizeof (port_entry_t));
356     if (ret == NULL)
357       return (NULL);
358     memset (ret, '\0', sizeof (port_entry_t));
359
360     ret->port = port;
361     ret->next = port_list_head;
362     port_list_head = ret;
363   }
364
365   return (ret);
366 } /* port_entry_t *conn_get_port_entry */
367
368 /* Removes ports that were added automatically due to the `ListeningPorts'
369  * setting but which are no longer listening. */
370 static void conn_reset_port_entry (void)
371 {
372   port_entry_t *prev = NULL;
373   port_entry_t *pe = port_list_head;
374
375   while (pe != NULL)
376   {
377     /* If this entry was created while reading the files (ant not when handling
378      * the configuration) remove it now. */
379     if ((pe->flags & (PORT_COLLECT_LOCAL
380             | PORT_COLLECT_REMOTE
381             | PORT_IS_LISTENING)) == 0)
382     {
383       port_entry_t *next = pe->next;
384
385       DEBUG ("tcpconns plugin: Removing temporary entry "
386           "for listening port %"PRIu16, pe->port);
387
388       if (prev == NULL)
389         port_list_head = next;
390       else
391         prev->next = next;
392
393       sfree (pe);
394       pe = next;
395
396       continue;
397     }
398
399     memset (pe->count_local, '\0', sizeof (pe->count_local));
400     memset (pe->count_remote, '\0', sizeof (pe->count_remote));
401     pe->flags &= ~PORT_IS_LISTENING;
402
403     pe = pe->next;
404   }
405 } /* void conn_reset_port_entry */
406
407 static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t state)
408 {
409   port_entry_t *pe = NULL;
410
411   if ((state > TCP_STATE_MAX)
412 #if TCP_STATE_MIN > 0
413       || (state < TCP_STATE_MIN)
414 #endif
415      )
416   {
417     NOTICE ("tcpconns plugin: Ignoring connection with "
418         "unknown state 0x%02"PRIx8".", state);
419     return (-1);
420   }
421
422   /* Listening sockets */
423   if ((state == TCP_STATE_LISTEN) && (port_collect_listening != 0))
424   {
425     pe = conn_get_port_entry (port_local, 1 /* create */);
426     if (pe != NULL)
427       pe->flags |= PORT_IS_LISTENING;
428   }
429
430   DEBUG ("tcpconns plugin: Connection %"PRIu16" <-> %"PRIu16" (%s)",
431       port_local, port_remote, tcp_state[state]);
432
433   pe = conn_get_port_entry (port_local, 0 /* no create */);
434   if (pe != NULL)
435     pe->count_local[state]++;
436
437   pe = conn_get_port_entry (port_remote, 0 /* no create */);
438   if (pe != NULL)
439     pe->count_remote[state]++;
440
441   return (0);
442 } /* int conn_handle_ports */
443
444 #if KERNEL_LINUX
445 /* Returns zero on success, less than zero on socket error and greater than
446  * zero on other errors. */
447 static int conn_read_netlink (void)
448 {
449   int fd;
450   struct sockaddr_nl nladdr;
451   struct nlreq req;
452   struct msghdr msg;
453   struct iovec iov;
454   struct inet_diag_msg *r;
455   char buf[8192];
456
457   /* If this fails, it's likely a permission problem. We'll fall back to
458    * reading this information from files below. */
459   fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG);
460   if (fd < 0)
461   {
462     ERROR ("tcpconns plugin: conn_read_netlink: socket(AF_NETLINK, SOCK_RAW, "
463         "NETLINK_INET_DIAG) failed: %s",
464         sstrerror (errno, buf, sizeof (buf)));
465     return (-1);
466   }
467
468   memset(&nladdr, 0, sizeof(nladdr));
469   nladdr.nl_family = AF_NETLINK;
470
471   memset(&req, 0, sizeof(req));
472   req.nlh.nlmsg_len = sizeof(req);
473   req.nlh.nlmsg_type = TCPDIAG_GETSOCK;
474   /* NLM_F_ROOT: return the complete table instead of a single entry.
475    * NLM_F_MATCH: return all entries matching criteria (not implemented)
476    * NLM_F_REQUEST: must be set on all request messages */
477   req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
478   req.nlh.nlmsg_pid = 0;
479   /* The sequence_number is used to track our messages. Since netlink is not
480    * reliable, we don't want to end up with a corrupt or incomplete old
481    * message in case the system is/was out of memory. */
482   req.nlh.nlmsg_seq = ++sequence_number;
483   req.r.idiag_family = AF_INET;
484   req.r.idiag_states = 0xfff;
485   req.r.idiag_ext = 0;
486
487   memset(&iov, 0, sizeof(iov));
488   iov.iov_base = &req;
489   iov.iov_len = sizeof(req);
490
491   memset(&msg, 0, sizeof(msg));
492   msg.msg_name = (void*)&nladdr;
493   msg.msg_namelen = sizeof(nladdr);
494   msg.msg_iov = &iov;
495   msg.msg_iovlen = 1;
496
497   if (sendmsg (fd, &msg, 0) < 0)
498   {
499     ERROR ("tcpconns plugin: conn_read_netlink: sendmsg(2) failed: %s",
500         sstrerror (errno, buf, sizeof (buf)));
501     close (fd);
502     return (-1);
503   }
504
505   iov.iov_base = buf;
506   iov.iov_len = sizeof(buf);
507
508   while (1)
509   {
510     int status;
511     struct nlmsghdr *h;
512
513     memset(&msg, 0, sizeof(msg));
514     msg.msg_name = (void*)&nladdr;
515     msg.msg_namelen = sizeof(nladdr);
516     msg.msg_iov = &iov;
517     msg.msg_iovlen = 1;
518
519     status = recvmsg(fd, (void *) &msg, /* flags = */ 0);
520     if (status < 0)
521     {
522       if ((errno == EINTR) || (errno == EAGAIN))
523         continue;
524
525       ERROR ("tcpconns plugin: conn_read_netlink: recvmsg(2) failed: %s",
526           sstrerror (errno, buf, sizeof (buf)));
527       close (fd);
528       return (-1);
529     }
530     else if (status == 0)
531     {
532       close (fd);
533       DEBUG ("tcpconns plugin: conn_read_netlink: Unexpected zero-sized "
534           "reply from netlink socket.");
535       return (0);
536     }
537
538     h = (struct nlmsghdr*)buf;
539     while (NLMSG_OK(h, status))
540     {
541       if (h->nlmsg_seq != sequence_number)
542       {
543         h = NLMSG_NEXT(h, status);
544         continue;
545       }
546
547       if (h->nlmsg_type == NLMSG_DONE)
548       {
549         close (fd);
550         return (0);
551       }
552       else if (h->nlmsg_type == NLMSG_ERROR)
553       {
554         struct nlmsgerr *msg_error;
555
556         msg_error = NLMSG_DATA(h);
557         WARNING ("tcpconns plugin: conn_read_netlink: Received error %i.",
558             msg_error->error);
559
560         close (fd);
561         return (1);
562       }
563
564       r = NLMSG_DATA(h);
565
566       /* This code does not (need to) distinguish between IPv4 and IPv6. */
567       conn_handle_ports (ntohs(r->id.idiag_sport),
568           ntohs(r->id.idiag_dport),
569           r->idiag_state);
570
571       h = NLMSG_NEXT(h, status);
572     } /* while (NLMSG_OK) */
573   } /* while (1) */
574
575   /* Not reached because the while() loop above handles the exit condition. */
576   return (0);
577 } /* int conn_read_netlink */
578
579 static int conn_handle_line (char *buffer)
580 {
581   char *fields[32];
582   int   fields_len;
583
584   char *endptr;
585
586   char *port_local_str;
587   char *port_remote_str;
588   uint16_t port_local;
589   uint16_t port_remote;
590
591   uint8_t state;
592
593   int buffer_len = strlen (buffer);
594
595   while ((buffer_len > 0) && (buffer[buffer_len - 1] < 32))
596     buffer[--buffer_len] = '\0';
597   if (buffer_len <= 0)
598     return (-1);
599
600   fields_len = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
601   if (fields_len < 12)
602   {
603     DEBUG ("tcpconns plugin: Got %i fields, expected at least 12.", fields_len);
604     return (-1);
605   }
606
607   port_local_str  = strchr (fields[1], ':');
608   port_remote_str = strchr (fields[2], ':');
609
610   if ((port_local_str == NULL) || (port_remote_str == NULL))
611     return (-1);
612   port_local_str++;
613   port_remote_str++;
614   if ((*port_local_str == '\0') || (*port_remote_str == '\0'))
615     return (-1);
616
617   endptr = NULL;
618   port_local = (uint16_t) strtol (port_local_str, &endptr, 16);
619   if ((endptr == NULL) || (*endptr != '\0'))
620     return (-1);
621
622   endptr = NULL;
623   port_remote = (uint16_t) strtol (port_remote_str, &endptr, 16);
624   if ((endptr == NULL) || (*endptr != '\0'))
625     return (-1);
626
627   endptr = NULL;
628   state = (uint8_t) strtol (fields[3], &endptr, 16);
629   if ((endptr == NULL) || (*endptr != '\0'))
630     return (-1);
631
632   return (conn_handle_ports (port_local, port_remote, state));
633 } /* int conn_handle_line */
634
635 static int conn_read_file (const char *file)
636 {
637   FILE *fh;
638   char buffer[1024];
639
640   fh = fopen (file, "r");
641   if (fh == NULL)
642     return (-1);
643
644   while (fgets (buffer, sizeof (buffer), fh) != NULL)
645   {
646     conn_handle_line (buffer);
647   } /* while (fgets) */
648
649   fclose (fh);
650
651   return (0);
652 } /* int conn_read_file */
653 /* #endif KERNEL_LINUX */
654
655 #elif HAVE_SYSCTLBYNAME
656 /* #endif HAVE_SYSCTLBYNAME */
657
658 #elif HAVE_LIBKVM_NLIST
659 #endif /* HAVE_LIBKVM_NLIST */
660
661 static int conn_config (const char *key, const char *value)
662 {
663   if (strcasecmp (key, "ListeningPorts") == 0)
664   {
665     if (IS_TRUE (value))
666       port_collect_listening = 1;
667     else
668       port_collect_listening = 0;
669   }
670   else if ((strcasecmp (key, "LocalPort") == 0)
671       || (strcasecmp (key, "RemotePort") == 0))
672   {
673       port_entry_t *pe;
674       int port = atoi (value);
675
676       if ((port < 1) || (port > 65535))
677       {
678         ERROR ("tcpconns plugin: Invalid port: %i", port);
679         return (1);
680       }
681
682       pe = conn_get_port_entry ((uint16_t) port, 1 /* create */);
683       if (pe == NULL)
684       {
685         ERROR ("tcpconns plugin: conn_get_port_entry failed.");
686         return (1);
687       }
688
689       if (strcasecmp (key, "LocalPort") == 0)
690         pe->flags |= PORT_COLLECT_LOCAL;
691       else
692         pe->flags |= PORT_COLLECT_REMOTE;
693   }
694   else
695   {
696     return (-1);
697   }
698
699   return (0);
700 } /* int conn_config */
701
702 #if KERNEL_LINUX
703 static int conn_init (void)
704 {
705   if (port_list_head == NULL)
706     port_collect_listening = 1;
707
708   return (0);
709 } /* int conn_init */
710
711 static int conn_read (void)
712 {
713   int status;
714
715   conn_reset_port_entry ();
716
717   if (linux_source == SRC_NETLINK)
718   {
719     status = conn_read_netlink ();
720   }
721   else if (linux_source == SRC_PROC)
722   {
723     int errors_num = 0;
724
725     if (conn_read_file ("/proc/net/tcp") != 0)
726       errors_num++;
727     if (conn_read_file ("/proc/net/tcp6") != 0)
728       errors_num++;
729
730     if (errors_num < 2)
731       status = 0;
732     else
733       status = ENOENT;
734   }
735   else /* if (linux_source == SRC_DUNNO) */
736   {
737     /* Try to use netlink for getting this data, it is _much_ faster on systems
738      * with a large amount of connections. */
739     status = conn_read_netlink ();
740     if (status == 0)
741     {
742       INFO ("tcpconns plugin: Reading from netlink succeeded. "
743           "Will use the netlink method from now on.");
744       linux_source = SRC_NETLINK;
745     }
746     else
747     {
748       INFO ("tcpconns plugin: Reading from netlink failed. "
749           "Will read from /proc from now on.");
750       linux_source = SRC_PROC;
751
752       /* return success here to avoid the "plugin failed" message. */
753       return (0);
754     }
755   }
756
757   if (status == 0)
758     conn_submit_all ();
759   else
760     return (status);
761
762   return (0);
763 } /* int conn_read */
764 /* #endif KERNEL_LINUX */
765
766 #elif HAVE_SYSCTLBYNAME
767 static int conn_read (void)
768 {
769   int status;
770   char *buffer;
771   size_t buffer_len;;
772
773   struct xinpgen *in_orig;
774   struct xinpgen *in_ptr;
775
776   conn_reset_port_entry ();
777
778   buffer_len = 0;
779   status = sysctlbyname ("net.inet.tcp.pcblist", NULL, &buffer_len, 0, 0);
780   if (status < 0)
781   {
782     ERROR ("tcpconns plugin: sysctlbyname failed.");
783     return (-1);
784   }
785
786   buffer = (char *) malloc (buffer_len);
787   if (buffer == NULL)
788   {
789     ERROR ("tcpconns plugin: malloc failed.");
790     return (-1);
791   }
792
793   status = sysctlbyname ("net.inet.tcp.pcblist", buffer, &buffer_len, 0, 0);
794   if (status < 0)
795   {
796     ERROR ("tcpconns plugin: sysctlbyname failed.");
797     sfree (buffer);
798     return (-1);
799   }
800
801   if (buffer_len <= sizeof (struct xinpgen))
802   {
803     ERROR ("tcpconns plugin: (buffer_len <= sizeof (struct xinpgen))");
804     sfree (buffer);
805     return (-1);
806   }
807
808   in_orig = (struct xinpgen *) buffer;
809   for (in_ptr = (struct xinpgen *) (((char *) in_orig) + in_orig->xig_len);
810       in_ptr->xig_len > sizeof (struct xinpgen);
811       in_ptr = (struct xinpgen *) (((char *) in_ptr) + in_ptr->xig_len))
812   {
813     struct tcpcb *tp = &((struct xtcpcb *) in_ptr)->xt_tp;
814     struct inpcb *inp = &((struct xtcpcb *) in_ptr)->xt_inp;
815     struct xsocket *so = &((struct xtcpcb *) in_ptr)->xt_socket;
816
817     /* Ignore non-TCP sockets */
818     if (so->xso_protocol != IPPROTO_TCP)
819       continue;
820
821     /* Ignore PCBs which were freed during copyout. */
822     if (inp->inp_gencnt > in_orig->xig_gen)
823       continue;
824
825     if (((inp->inp_vflag & INP_IPV4) == 0)
826         && ((inp->inp_vflag & INP_IPV6) == 0))
827       continue;
828
829     conn_handle_ports (ntohs (inp->inp_lport), ntohs (inp->inp_fport),
830         tp->t_state);
831   } /* for (in_ptr) */
832
833   in_orig = NULL;
834   in_ptr = NULL;
835   sfree (buffer);
836
837   conn_submit_all ();
838
839   return (0);
840 } /* int conn_read */
841 /* #endif HAVE_SYSCTLBYNAME */
842
843 #elif HAVE_LIBKVM_NLIST
844 static int kread (u_long addr, void *buf, int size)
845 {
846   int status;
847
848   status = kvm_read (kvmd, addr, buf, size);
849   if (status != size)
850   {
851     ERROR ("tcpconns plugin: kvm_read failed (got %i, expected %i): %s\n",
852         status, size, kvm_geterr (kvmd));
853     return (-1);
854   }
855   return (0);
856 } /* int kread */
857
858 static int conn_init (void)
859 {
860   char buf[_POSIX2_LINE_MAX];
861   struct nlist nl[] =
862   {
863 #define N_TCBTABLE 0
864     { "_tcbtable" },
865     { "" }
866   };
867   int status;
868
869   kvmd = kvm_openfiles (NULL, NULL, NULL, O_RDONLY, buf);
870   if (kvmd == NULL)
871   {
872     ERROR ("tcpconns plugin: kvm_openfiles failed: %s", buf);
873     return (-1);
874   }
875
876   status = kvm_nlist (kvmd, nl);
877   if (status < 0)
878   {
879     ERROR ("tcpconns plugin: kvm_nlist failed with status %i.", status);
880     return (-1);
881   }
882
883   if (nl[N_TCBTABLE].n_type == 0)
884   {
885     ERROR ("tcpconns plugin: Error looking up kernel's namelist: "
886         "N_TCBTABLE is invalid.");
887     return (-1);
888   }
889
890   inpcbtable_off = (u_long) nl[N_TCBTABLE].n_value;
891   inpcbtable_ptr = (struct inpcbtable *) nl[N_TCBTABLE].n_value;
892
893   return (0);
894 } /* int conn_init */
895
896 static int conn_read (void)
897 {
898   struct inpcbtable table;
899   struct inpcb *head;
900   struct inpcb *next;
901   struct inpcb inpcb;
902   struct tcpcb tcpcb;
903   int status;
904
905   conn_reset_port_entry ();
906
907   /* Read the pcbtable from the kernel */
908   status = kread (inpcbtable_off, &table, sizeof (table));
909   if (status != 0)
910     return (-1);
911
912   /* Get the `head' pcb */
913   head = (struct inpcb *) &(inpcbtable_ptr->inpt_queue);
914   /* Get the first pcb */
915   next = (struct inpcb *)CIRCLEQ_FIRST (&table.inpt_queue);
916
917   while (next != head)
918   {
919     /* Read the pcb pointed to by `next' into `inpcb' */
920     kread ((u_long) next, &inpcb, sizeof (inpcb));
921
922     /* Advance `next' */
923     next = (struct inpcb *)CIRCLEQ_NEXT (&inpcb, inp_queue);
924
925     /* Ignore sockets, that are not connected. */
926 #ifdef __NetBSD__
927     if (inpcb.inp_af == AF_INET6)
928       continue; /* XXX see netbsd/src/usr.bin/netstat/inet6.c */
929 #else
930     if (!(inpcb.inp_flags & INP_IPV6)
931         && (inet_lnaof(inpcb.inp_laddr) == INADDR_ANY))
932       continue;
933     if ((inpcb.inp_flags & INP_IPV6)
934         && IN6_IS_ADDR_UNSPECIFIED (&inpcb.inp_laddr6))
935       continue;
936 #endif
937
938     kread ((u_long) inpcb.inp_ppcb, &tcpcb, sizeof (tcpcb));
939     conn_handle_ports (ntohs(inpcb.inp_lport), ntohs(inpcb.inp_fport), tcpcb.t_state);
940   } /* while (next != head) */
941
942   conn_submit_all ();
943
944   return (0);
945 }
946 /* #endif HAVE_LIBKVM_NLIST */
947
948 #elif KERNEL_AIX
949
950 static int conn_read (void)
951 {
952   int size;
953   int i;
954   int nconn;
955   void *data;
956   struct netinfo_header *header;
957   struct netinfo_conn *conn;
958
959   conn_reset_port_entry ();
960
961   size = netinfo(NETINFO_TCP, 0, 0, 0);
962   if (size < 0)
963   {
964     ERROR ("tcpconns plugin: netinfo failed return: %i", size);
965     return (-1);
966   }
967
968   if (size == 0)
969     return (0);
970
971   if ((size - sizeof (struct netinfo_header)) % sizeof (struct netinfo_conn))
972   {
973     ERROR ("tcpconns plugin: invalid buffer size");
974     return (-1);
975   }
976
977   data = malloc(size);
978   if (data == NULL)
979   {
980     ERROR ("tcpconns plugin: malloc failed");
981     return (-1);
982   }
983
984   if (netinfo(NETINFO_TCP, data, &size, 0) < 0)
985   {
986     ERROR ("tcpconns plugin: netinfo failed");
987     free(data);
988     return (-1);
989   }
990
991   header = (struct netinfo_header *)data;
992   nconn = header->size;
993   conn = (struct netinfo_conn *)(data + sizeof(struct netinfo_header));
994
995   for (i=0; i < nconn; conn++, i++)
996   {
997     conn_handle_ports (conn->srcport, conn->dstport, conn->tcp_state);
998   }
999
1000   free(data);
1001
1002   conn_submit_all ();
1003
1004   return (0);
1005 }
1006 #endif /* KERNEL_AIX */
1007
1008 void module_register (void)
1009 {
1010         plugin_register_config ("tcpconns", conn_config,
1011                         config_keys, config_keys_num);
1012 #if KERNEL_LINUX
1013         plugin_register_init ("tcpconns", conn_init);
1014 #elif HAVE_SYSCTLBYNAME
1015         /* no initialization */
1016 #elif HAVE_LIBKVM_NLIST
1017         plugin_register_init ("tcpconns", conn_init);
1018 #elif KERNEL_AIX
1019         /* no initialization */
1020 #endif
1021         plugin_register_read ("tcpconns", conn_read);
1022 } /* void module_register */
1023
1024 /*
1025  * vim: set shiftwidth=2 softtabstop=2 tabstop=8 fdm=marker :
1026  */