X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Ftcpconns.c;h=f576b68f31e00ae120f923ce4ba1d808528022ae;hb=51a4e62d7d0e73d8d5822efaef1e3218b5ad0373;hp=b31ebd8510a6e5909a8617579beee957d461f248;hpb=cac681c56b06231b18fc02ffd71d4afd13973050;p=collectd.git diff --git a/src/tcpconns.c b/src/tcpconns.c index b31ebd85..6351c7b6 100644 --- a/src/tcpconns.c +++ b/src/tcpconns.c @@ -1,6 +1,7 @@ /** * collectd - src/tcpconns.c - * Copyright (C) 2007 Florian octo Forster + * Copyright (C) 2007,2008 Florian octo Forster + * Copyright (C) 2008 Michael Stapelberg * * 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 @@ -17,10 +18,12 @@ * * Author: * Florian octo Forster + * Michael Stapelberg **/ /** - * Code within `__OpenBSD__' blocks is provided under the following license: + * Code within `HAVE_LIBKVM_NLIST' blocks is provided under the following + * license: * * $collectd: parts of tcpconns.c, 2008/08/08 03:48:30 Michael Stapelberg $ * $OpenBSD: inet.c,v 1.100 2007/06/19 05:28:30 ray Exp $ @@ -58,11 +61,24 @@ #include "common.h" #include "plugin.h" -#if !KERNEL_LINUX && !HAVE_SYSCTLBYNAME && !__OpenBSD__ +#if defined(__OpenBSD__) || defined(__NetBSD__) +#undef HAVE_SYSCTLBYNAME /* force HAVE_LIBKVM_NLIST path */ +#endif + +#if !KERNEL_LINUX && !HAVE_SYSCTLBYNAME && !HAVE_LIBKVM_NLIST && !KERNEL_AIX # error "No applicable input method." #endif #if KERNEL_LINUX +# include +/* sys/socket.h is necessary to compile when using netlink on older systems. */ +# include +# include +#if HAVE_LINUX_INET_DIAG_H +# include +#endif +# include +# include /* #endif KERNEL_LINUX */ #elif HAVE_SYSCTLBYNAME @@ -94,24 +110,42 @@ # include /* #endif HAVE_SYSCTLBYNAME */ -#elif __OpenBSD__ +/* This is for OpenBSD and NetBSD. */ +#elif HAVE_LIBKVM_NLIST # include # include # include # include # include # include +# include # include # include # include # include # include # include -# include +# if !defined(HAVE_BSD_NLIST_H) || !HAVE_BSD_NLIST_H +# include +# else /* HAVE_BSD_NLIST_H */ +# include +# endif # include -#endif /* __OpenBSD__ */ +/* #endif HAVE_LIBKVM_NLIST */ + +#elif KERNEL_AIX +# include +# include +#endif /* KERNEL_AIX */ #if KERNEL_LINUX +#if HAVE_STRUCT_LINUX_INET_DIAG_REQ +struct nlreq { + struct nlmsghdr nlh; + struct inet_diag_req r; +}; +#endif + static const char *tcp_state[] = { "", /* 0 */ @@ -154,118 +188,72 @@ static const char *tcp_state[] = # define TCP_STATE_MAX 10 /* #endif HAVE_SYSCTLBYNAME */ -#elif __OpenBSD__ +#elif HAVE_LIBKVM_NLIST static const char *tcp_state[] = { "CLOSED", "LISTEN", "SYN_SENT", - "SYN_RCVD", + "SYN_RECV", "ESTABLISHED", "CLOSE_WAIT", - "FIN_WAIT_1", + "FIN_WAIT1", "CLOSING", "LAST_ACK", - "FIN_WAIT_2", + "FIN_WAIT2", "TIME_WAIT" }; static kvm_t *kvmd; +static u_long inpcbtable_off = 0; +struct inpcbtable *inpcbtable_ptr = NULL; -static struct nlist nl[] = { -#define N_MBSTAT 0 - { "_mbstat" }, -#define N_IPSTAT 1 - { "_ipstat" }, -#define N_TCBTABLE 2 - { "_tcbtable" }, -#define N_TCPSTAT 3 - { "_tcpstat" }, -#define N_UDBTABLE 4 - { "_udbtable" }, -#define N_UDPSTAT 5 - { "_udpstat" }, -#define N_IFNET 6 - { "_ifnet" }, -#define N_ICMPSTAT 7 - { "_icmpstat" }, -#define N_RTSTAT 8 - { "_rtstat" }, -#define N_UNIXSW 9 - { "_unixsw" }, -#define N_RTREE 10 - { "_rt_tables"}, -#define N_FILE 11 - { "_file" }, -#define N_IGMPSTAT 12 - { "_igmpstat" }, -#define N_MRTPROTO 13 - { "_ip_mrtproto" }, -#define N_MRTSTAT 14 - { "_mrtstat" }, -#define N_MFCHASHTBL 15 - { "_mfchashtbl" }, -#define N_MFCHASH 16 - { "_mfchash" }, - { "_viftable" }, -#define N_AHSTAT 18 - { "_ahstat"}, -#define N_ESPSTAT 19 - { "_espstat"}, -#define N_IP4STAT 20 - { "_ipipstat"}, -#define N_DDPSTAT 21 - { "_ddpstat"}, -#define N_DDPCB 22 - { "_ddpcb"}, -#define N_ETHERIPSTAT 23 - { "_etheripstat"}, -#define N_IP6STAT 24 - { "_ip6stat" }, -#define N_ICMP6STAT 25 - { "_icmp6stat" }, -#define N_PIM6STAT 26 - { "_pim6stat" }, -#define N_MRT6PROTO 27 - { "_ip6_mrtproto" }, -#define N_MRT6STAT 28 - { "_mrt6stat" }, -#define N_MF6CTABLE 29 - { "_mf6ctable" }, -#define N_MIF6TABLE 30 - { "_mif6table" }, -#define N_MBPOOL 31 - { "_mbpool" }, -#define N_MCLPOOL 32 - { "_mclpool" }, -#define N_IPCOMPSTAT 33 - { "_ipcompstat" }, -#define N_RIP6STAT 34 - { "_rip6stat" }, -#define N_CARPSTAT 35 - { "_carpstats" }, -#define N_RAWIPTABLE 36 - { "_rawcbtable" }, -#define N_RAWIP6TABLE 37 - { "_rawin6pcbtable" }, -#define N_PFSYNCSTAT 38 - { "_pfsyncstats" }, -#define N_PIMSTAT 39 - { "_pimstat" }, -#define N_AF2RTAFIDX 40 - { "_af2rtafidx" }, -#define N_RTBLIDMAX 41 - { "_rtbl_id_max" }, -#define N_RTMASK 42 - { "_mask_rnhead" }, - - { "" } +# define TCP_STATE_LISTEN 1 +# define TCP_STATE_MIN 1 +# define TCP_STATE_MAX 10 +/* #endif HAVE_LIBKVM_NLIST */ + +#elif KERNEL_AIX +static const char *tcp_state[] = +{ + "CLOSED", + "LISTEN", + "SYN_SENT", + "SYN_RCVD", + "ESTABLISHED", + "CLOSE_WAIT", + "FIN_WAIT_1", + "CLOSING", + "LAST_ACK", + "FIN_WAIT_2", + "TIME_WAIT" }; # define TCP_STATE_LISTEN 1 -# define TCP_STATE_MIN 1 +# define TCP_STATE_MIN 0 # define TCP_STATE_MAX 10 -#endif /* __OpenBSD__ */ + +struct netinfo_conn { + uint32_t unknow1[2]; + uint16_t dstport; + uint16_t unknow2; + struct in6_addr dstaddr; + uint16_t srcport; + uint16_t unknow3; + struct in6_addr srcaddr; + uint32_t unknow4[36]; + uint16_t tcp_state; + uint16_t unknow5[7]; +}; + +struct netinfo_header { + unsigned int proto; + unsigned int size; +}; + +# define NETINFO_TCP 3 +extern int netinfo (int proto, void *data, int *size, int n); +#endif /* KERNEL_AIX */ #define PORT_COLLECT_LOCAL 0x01 #define PORT_COLLECT_REMOTE 0x02 @@ -291,6 +279,22 @@ static int config_keys_num = STATIC_ARRAY_SIZE (config_keys); static int port_collect_listening = 0; static port_entry_t *port_list_head = NULL; +#if KERNEL_LINUX +#if HAVE_STRUCT_LINUX_INET_DIAG_REQ +/* This depends on linux inet_diag_req because if this structure is missing, + * sequence_number is useless and we get a compilation warning. + */ +static uint32_t sequence_number = 0; +#endif + +enum +{ + SRC_DUNNO, + SRC_NETLINK, + SRC_PROC +} linux_source = SRC_DUNNO; +#endif + static void conn_submit_port_entry (port_entry_t *pe) { value_t values[1]; @@ -299,7 +303,6 @@ static void conn_submit_port_entry (port_entry_t *pe) vl.values = values; vl.values_len = 1; - vl.time = time (NULL); sstrncpy (vl.host, hostname_g, sizeof (vl.host)); sstrncpy (vl.plugin, "tcpconns", sizeof (vl.plugin)); sstrncpy (vl.type, "tcp_connections", sizeof (vl.type)); @@ -308,7 +311,7 @@ static void conn_submit_port_entry (port_entry_t *pe) || (pe->flags & PORT_COLLECT_LOCAL)) { ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance), - "%hu-local", pe->port); + "%"PRIu16"-local", pe->port); for (i = 1; i <= TCP_STATE_MAX; i++) { @@ -323,7 +326,7 @@ static void conn_submit_port_entry (port_entry_t *pe) if (pe->flags & PORT_COLLECT_REMOTE) { ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance), - "%hu-remote", pe->port); + "%"PRIu16"-remote", pe->port); for (i = 1; i <= TCP_STATE_MAX; i++) { @@ -389,7 +392,7 @@ static void conn_reset_port_entry (void) port_entry_t *next = pe->next; DEBUG ("tcpconns plugin: Removing temporary entry " - "for listening port %hu", pe->port); + "for listening port %"PRIu16, pe->port); if (prev == NULL) port_list_head = next; @@ -420,8 +423,8 @@ static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t #endif ) { - NOTICE ("tcpconns plugin: Ignoring connection with unknown state 0x%02x.", - state); + NOTICE ("tcpconns plugin: Ignoring connection with " + "unknown state 0x%02"PRIx8".", state); return (-1); } @@ -433,7 +436,7 @@ static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t pe->flags |= PORT_IS_LISTENING; } - DEBUG ("tcpconns plugin: Connection %hu <-> %hu (%s)", + DEBUG ("tcpconns plugin: Connection %"PRIu16" <-> %"PRIu16" (%s)", port_local, port_remote, tcp_state[state]); pe = conn_get_port_entry (port_local, 0 /* no create */); @@ -448,6 +451,144 @@ static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t } /* int conn_handle_ports */ #if KERNEL_LINUX +/* Returns zero on success, less than zero on socket error and greater than + * zero on other errors. */ +static int conn_read_netlink (void) +{ +#if HAVE_STRUCT_LINUX_INET_DIAG_REQ + int fd; + struct sockaddr_nl nladdr; + struct nlreq req; + struct msghdr msg; + struct iovec iov; + struct inet_diag_msg *r; + char buf[8192]; + + /* If this fails, it's likely a permission problem. We'll fall back to + * reading this information from files below. */ + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG); + if (fd < 0) + { + ERROR ("tcpconns plugin: conn_read_netlink: socket(AF_NETLINK, SOCK_RAW, " + "NETLINK_INET_DIAG) failed: %s", + sstrerror (errno, buf, sizeof (buf))); + return (-1); + } + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = TCPDIAG_GETSOCK; + /* NLM_F_ROOT: return the complete table instead of a single entry. + * NLM_F_MATCH: return all entries matching criteria (not implemented) + * NLM_F_REQUEST: must be set on all request messages */ + req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + /* The sequence_number is used to track our messages. Since netlink is not + * reliable, we don't want to end up with a corrupt or incomplete old + * message in case the system is/was out of memory. */ + req.nlh.nlmsg_seq = ++sequence_number; + req.r.idiag_family = AF_INET; + req.r.idiag_states = 0xfff; + req.r.idiag_ext = 0; + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = &req; + iov.iov_len = sizeof(req); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (void*)&nladdr; + msg.msg_namelen = sizeof(nladdr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + if (sendmsg (fd, &msg, 0) < 0) + { + ERROR ("tcpconns plugin: conn_read_netlink: sendmsg(2) failed: %s", + sstrerror (errno, buf, sizeof (buf))); + close (fd); + return (-1); + } + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + while (1) + { + int status; + struct nlmsghdr *h; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (void*)&nladdr; + msg.msg_namelen = sizeof(nladdr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + status = recvmsg(fd, (void *) &msg, /* flags = */ 0); + if (status < 0) + { + if ((errno == EINTR) || (errno == EAGAIN)) + continue; + + ERROR ("tcpconns plugin: conn_read_netlink: recvmsg(2) failed: %s", + sstrerror (errno, buf, sizeof (buf))); + close (fd); + return (-1); + } + else if (status == 0) + { + close (fd); + DEBUG ("tcpconns plugin: conn_read_netlink: Unexpected zero-sized " + "reply from netlink socket."); + return (0); + } + + h = (struct nlmsghdr*)buf; + while (NLMSG_OK(h, status)) + { + if (h->nlmsg_seq != sequence_number) + { + h = NLMSG_NEXT(h, status); + continue; + } + + if (h->nlmsg_type == NLMSG_DONE) + { + close (fd); + return (0); + } + else if (h->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *msg_error; + + msg_error = NLMSG_DATA(h); + WARNING ("tcpconns plugin: conn_read_netlink: Received error %i.", + msg_error->error); + + close (fd); + return (1); + } + + r = NLMSG_DATA(h); + + /* This code does not (need to) distinguish between IPv4 and IPv6. */ + conn_handle_ports (ntohs(r->id.idiag_sport), + ntohs(r->id.idiag_dport), + r->idiag_state); + + h = NLMSG_NEXT(h, status); + } /* while (NLMSG_OK) */ + } /* while (1) */ + + /* Not reached because the while() loop above handles the exit condition. */ + return (0); +#else + return (1); +#endif /* HAVE_STRUCT_LINUX_INET_DIAG_REQ */ +} /* int conn_read_netlink */ + static int conn_handle_line (char *buffer) { char *fields[32]; @@ -525,15 +666,16 @@ static int conn_read_file (const char *file) /* #endif KERNEL_LINUX */ #elif HAVE_SYSCTLBYNAME -#endif /* HAVE_SYSCTLBYNAME */ +/* #endif HAVE_SYSCTLBYNAME */ + +#elif HAVE_LIBKVM_NLIST +#endif /* HAVE_LIBKVM_NLIST */ 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)) + if (IS_TRUE (value)) port_collect_listening = 1; else port_collect_listening = 0; @@ -581,25 +723,54 @@ static int conn_init (void) static int conn_read (void) { - int errors_num = 0; + int status; conn_reset_port_entry (); - if (conn_read_file ("/proc/net/tcp") != 0) - errors_num++; - if (conn_read_file ("/proc/net/tcp6") != 0) - errors_num++; - - if (errors_num < 2) + if (linux_source == SRC_NETLINK) { - conn_submit_all (); + status = conn_read_netlink (); } - else + else if (linux_source == SRC_PROC) { - ERROR ("tcpconns plugin: Neither /proc/net/tcp nor /proc/net/tcp6 " - "coult be read."); - return (-1); + int errors_num = 0; + + if (conn_read_file ("/proc/net/tcp") != 0) + errors_num++; + if (conn_read_file ("/proc/net/tcp6") != 0) + errors_num++; + + if (errors_num < 2) + status = 0; + else + status = ENOENT; } + else /* if (linux_source == SRC_DUNNO) */ + { + /* Try to use netlink for getting this data, it is _much_ faster on systems + * with a large amount of connections. */ + status = conn_read_netlink (); + if (status == 0) + { + INFO ("tcpconns plugin: Reading from netlink succeeded. " + "Will use the netlink method from now on."); + linux_source = SRC_NETLINK; + } + else + { + INFO ("tcpconns plugin: Reading from netlink failed. " + "Will read from /proc from now on."); + linux_source = SRC_PROC; + + /* return success here to avoid the "plugin failed" message. */ + return (0); + } + } + + if (status == 0) + conn_submit_all (); + else + return (status); return (0); } /* int conn_read */ @@ -668,7 +839,8 @@ static int conn_read (void) && ((inp->inp_vflag & INP_IPV6) == 0)) continue; - conn_handle_ports (inp->inp_lport, inp->inp_fport, tp->t_state); + conn_handle_ports (ntohs (inp->inp_lport), ntohs (inp->inp_fport), + tp->t_state); } /* for (in_ptr) */ in_orig = NULL; @@ -681,62 +853,170 @@ static int conn_read (void) } /* int conn_read */ /* #endif HAVE_SYSCTLBYNAME */ -#elif __OpenBSD__ -static int kread(u_long addr, void *buf, int size) +#elif HAVE_LIBKVM_NLIST +static int kread (u_long addr, void *buf, int size) { - if (kvm_read(kvmd, addr, buf, size) != size) + int status; + + status = kvm_read (kvmd, addr, buf, size); + if (status != size) { - ERROR ("tcpconns plugin: %s\n", kvm_geterr(kvmd)); + ERROR ("tcpconns plugin: kvm_read failed (got %i, expected %i): %s\n", + status, size, kvm_geterr (kvmd)); return (-1); } return (0); -} +} /* int kread */ static int conn_init (void) { char buf[_POSIX2_LINE_MAX]; - if ((kvmd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, buf)) == NULL) + struct nlist nl[] = + { +#define N_TCBTABLE 0 + { "_tcbtable" }, + { "" } + }; + int status; + + kvmd = kvm_openfiles (NULL, NULL, NULL, O_RDONLY, buf); + if (kvmd == NULL) { - ERROR("tcpconns plugin: %s", buf); + ERROR ("tcpconns plugin: kvm_openfiles failed: %s", buf); return (-1); } - if (kvm_nlist(kvmd, nl) < 0 || nl[0].n_type == 0) + + status = kvm_nlist (kvmd, nl); + if (status < 0) + { + ERROR ("tcpconns plugin: kvm_nlist failed with status %i.", status); + return (-1); + } + + if (nl[N_TCBTABLE].n_type == 0) { - ERROR("tcpconns plugin: No namelist."); + ERROR ("tcpconns plugin: Error looking up kernel's namelist: " + "N_TCBTABLE is invalid."); return (-1); } + + inpcbtable_off = (u_long) nl[N_TCBTABLE].n_value; + inpcbtable_ptr = (struct inpcbtable *) nl[N_TCBTABLE].n_value; + return (0); -} +} /* int conn_init */ static int conn_read (void) { - u_long off = nl[2].n_value; struct inpcbtable table; - struct inpcb *head, *next, *prev; + struct inpcb *head; + struct inpcb *next; struct inpcb inpcb; struct tcpcb tcpcb; + int status; conn_reset_port_entry (); - kread(off, &table, sizeof(table)); - prev = head = (struct inpcb *)&CIRCLEQ_FIRST(&((struct inpcbtable *)off)->inpt_queue); - next = CIRCLEQ_FIRST(&table.inpt_queue); + /* Read the pcbtable from the kernel */ + status = kread (inpcbtable_off, &table, sizeof (table)); + if (status != 0) + return (-1); + + /* Get the `head' pcb */ + head = (struct inpcb *) &(inpcbtable_ptr->inpt_queue); + /* Get the first pcb */ + next = (struct inpcb *)CIRCLEQ_FIRST (&table.inpt_queue); - while (next != head) { - kread((u_long)next, &inpcb, sizeof(inpcb)); - prev = next; - next = CIRCLEQ_NEXT(&inpcb, inp_queue); - if (inet_lnaof(inpcb.inp_laddr) == INADDR_ANY) + while (next != head) + { + /* Read the pcb pointed to by `next' into `inpcb' */ + kread ((u_long) next, &inpcb, sizeof (inpcb)); + + /* Advance `next' */ + next = (struct inpcb *)CIRCLEQ_NEXT (&inpcb, inp_queue); + + /* Ignore sockets, that are not connected. */ +#ifdef __NetBSD__ + if (inpcb.inp_af == AF_INET6) + continue; /* XXX see netbsd/src/usr.bin/netstat/inet6.c */ +#else + if (!(inpcb.inp_flags & INP_IPV6) + && (inet_lnaof(inpcb.inp_laddr) == INADDR_ANY)) + continue; + if ((inpcb.inp_flags & INP_IPV6) + && IN6_IS_ADDR_UNSPECIFIED (&inpcb.inp_laddr6)) continue; - kread((u_long)inpcb.inp_ppcb, &tcpcb, sizeof(tcpcb)); +#endif + + kread ((u_long) inpcb.inp_ppcb, &tcpcb, sizeof (tcpcb)); conn_handle_ports (ntohs(inpcb.inp_lport), ntohs(inpcb.inp_fport), tcpcb.t_state); + } /* while (next != head) */ + + conn_submit_all (); + + return (0); +} +/* #endif HAVE_LIBKVM_NLIST */ + +#elif KERNEL_AIX + +static int conn_read (void) +{ + int size; + int i; + int nconn; + void *data; + struct netinfo_header *header; + struct netinfo_conn *conn; + + conn_reset_port_entry (); + + size = netinfo(NETINFO_TCP, 0, 0, 0); + if (size < 0) + { + ERROR ("tcpconns plugin: netinfo failed return: %i", size); + return (-1); + } + + if (size == 0) + return (0); + + if ((size - sizeof (struct netinfo_header)) % sizeof (struct netinfo_conn)) + { + ERROR ("tcpconns plugin: invalid buffer size"); + return (-1); + } + + data = malloc(size); + if (data == NULL) + { + ERROR ("tcpconns plugin: malloc failed"); + return (-1); } + if (netinfo(NETINFO_TCP, data, &size, 0) < 0) + { + ERROR ("tcpconns plugin: netinfo failed"); + free(data); + return (-1); + } + + header = (struct netinfo_header *)data; + nconn = header->size; + conn = (struct netinfo_conn *)(data + sizeof(struct netinfo_header)); + + for (i=0; i < nconn; conn++, i++) + { + conn_handle_ports (conn->srcport, conn->dstport, conn->tcp_state); + } + + free(data); + conn_submit_all (); return (0); } -#endif /* __OpenBSD__ */ +#endif /* KERNEL_AIX */ void module_register (void) { @@ -746,12 +1026,14 @@ void module_register (void) plugin_register_init ("tcpconns", conn_init); #elif HAVE_SYSCTLBYNAME /* no initialization */ -#elif __OpenBSD__ +#elif HAVE_LIBKVM_NLIST plugin_register_init ("tcpconns", conn_init); +#elif KERNEL_AIX + /* no initialization */ #endif plugin_register_read ("tcpconns", conn_read); } /* void module_register */ /* - * vim: set shiftwidth=2 softtabstop=2 tabstop=8 : + * vim: set shiftwidth=2 softtabstop=2 tabstop=8 fdm=marker : */