X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Ftcpconns.c;h=3f96345dac8ac1c9142d3cbba2f560d230682724;hb=db2d9257b8aac65b47bb1466aaa66b8a62345f1c;hp=6a7e32d72007e216ff739bd491e6e34a6abefe11;hpb=95b08a826445423b00297d0db8cfb9676b5f676d;p=collectd.git diff --git a/src/tcpconns.c b/src/tcpconns.c index 6a7e32d7..3f96345d 100644 --- a/src/tcpconns.c +++ b/src/tcpconns.c @@ -70,6 +70,13 @@ #endif #if KERNEL_LINUX +# include +/* sys/socket.h is necessary to compile when using netlink on older systems. */ +# include +# include +# include +# include +# include /* #endif KERNEL_LINUX */ #elif HAVE_SYSCTLBYNAME @@ -116,7 +123,11 @@ # include # include # include -# include +# if !defined(HAVE_BSD_NLIST_H) || !HAVE_BSD_NLIST_H +# include +# else /* HAVE_BSD_NLIST_H */ +# include +# endif # include /* #endif HAVE_LIBKVM_NLIST */ @@ -126,6 +137,11 @@ #endif /* KERNEL_AIX */ #if KERNEL_LINUX +struct nlreq { + struct nlmsghdr nlh; + struct inet_diag_req r; +}; + static const char *tcp_state[] = { "", /* 0 */ @@ -257,7 +273,29 @@ static const char *config_keys[] = static int config_keys_num = STATIC_ARRAY_SIZE (config_keys); static int port_collect_listening = 0; +static int port_collect_total = 1; static port_entry_t *port_list_head = NULL; +static uint32_t count_total[TCP_STATE_MAX + 1]; + +#if KERNEL_LINUX +static uint32_t sequence_number = 0; + +enum +{ + SRC_DUNNO, + SRC_NETLINK, + SRC_PROC +} linux_source = SRC_DUNNO; +#endif + +static void connt_prepare_vl (value_list_t *vl, value_t *values) +{ + vl->values = values; + vl->values_len = 1; + sstrncpy (vl->host, hostname_g, sizeof (vl->host)); + sstrncpy (vl->plugin, "tcpconns", sizeof (vl->plugin)); + sstrncpy (vl->type, "tcp_connections", sizeof (vl->type)); +} static void conn_submit_port_entry (port_entry_t *pe) { @@ -265,11 +303,7 @@ static void conn_submit_port_entry (port_entry_t *pe) value_list_t vl = VALUE_LIST_INIT; int i; - vl.values = values; - vl.values_len = 1; - sstrncpy (vl.host, hostname_g, sizeof (vl.host)); - sstrncpy (vl.plugin, "tcpconns", sizeof (vl.plugin)); - sstrncpy (vl.type, "tcp_connections", sizeof (vl.type)); + connt_prepare_vl (&vl, values); if (((port_collect_listening != 0) && (pe->flags & PORT_IS_LISTENING)) || (pe->flags & PORT_COLLECT_LOCAL)) @@ -303,10 +337,33 @@ static void conn_submit_port_entry (port_entry_t *pe) } } /* void conn_submit */ +static void conn_submit_port_total (void) +{ + value_t values[1]; + value_list_t vl = VALUE_LIST_INIT; + int i; + + connt_prepare_vl (&vl, values); + + sstrncpy (vl.plugin, "all", sizeof (vl.plugin)); + + for (i = 1; i <= TCP_STATE_MAX; i++) + { + vl.values[0].gauge = count_total[i]; + + sstrncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance)); + + plugin_dispatch_values (&vl); + } +} + static void conn_submit_all (void) { port_entry_t *pe; + if (port_collect_total) + conn_submit_port_total (); + for (pe = port_list_head; pe != NULL; pe = pe->next) conn_submit_port_entry (pe); } /* void conn_submit_all */ @@ -345,6 +402,8 @@ static void conn_reset_port_entry (void) port_entry_t *prev = NULL; port_entry_t *pe = port_list_head; + memset (&count_total, '\0', sizeof(count_total)); + while (pe != NULL) { /* If this entry was created while reading the files (ant not when handling @@ -392,6 +451,8 @@ static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t return (-1); } + count_total[state]++; + /* Listening sockets */ if ((state == TCP_STATE_LISTEN) && (port_collect_listening != 0)) { @@ -415,6 +476,140 @@ 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) +{ + 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); +} /* int conn_read_netlink */ + static int conn_handle_line (char *buffer) { char *fields[32]; @@ -549,25 +744,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 */