From d0a4f282f3e36eb53cb0a50a64aa4597e52b7d42 Mon Sep 17 00:00:00 2001 From: Bryan Venteicher Date: Tue, 1 Jul 2014 00:51:29 -0500 Subject: [PATCH] Add 'netbw' display to systat --- usr.bin/systat/Makefile | 2 +- usr.bin/systat/cmdtab.c | 3 + usr.bin/systat/extern.h | 7 + usr.bin/systat/netbw.c | 476 ++++++++++++++++++++++++++++++++++++++++++++++++ usr.bin/systat/systat.1 | 4 + 5 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 usr.bin/systat/netbw.c diff --git a/usr.bin/systat/Makefile b/usr.bin/systat/Makefile index 1bb2da0..a17e4dd 100644 --- a/usr.bin/systat/Makefile +++ b/usr.bin/systat/Makefile @@ -7,7 +7,7 @@ PROG= systat SRCS= cmds.c cmdtab.c devs.c fetch.c iostat.c keyboard.c main.c \ netcmds.c netstat.c pigs.c swap.c icmp.c \ mode.c ip.c tcp.c \ - vmstat.c convtbl.c ifcmds.c ifstat.c + vmstat.c convtbl.c ifcmds.c ifstat.c netbw.c .if ${MK_INET6_SUPPORT} != "no" SRCS+= icmp6.c ip6.c diff --git a/usr.bin/systat/cmdtab.c b/usr.bin/systat/cmdtab.c index c9c9e7d..0e225ec 100644 --- a/usr.bin/systat/cmdtab.c +++ b/usr.bin/systat/cmdtab.c @@ -55,6 +55,9 @@ struct cmdtab cmdtab[] = { { "netstat", shownetstat, fetchnetstat, labelnetstat, initnetstat, opennetstat, closenetstat, cmdnetstat, 0, CF_LOADAV }, + { "netbw", shownetbw, fetchnetbw, labelnetbw, + initnetbw, opennetbw, closenetbw, NULL, + 0, 0 }, { "icmp", showicmp, fetchicmp, labelicmp, initicmp, openicmp, closeicmp, cmdmode, reseticmp, CF_LOADAV }, diff --git a/usr.bin/systat/extern.h b/usr.bin/systat/extern.h index 17fffc1..38d4084 100644 --- a/usr.bin/systat/extern.h +++ b/usr.bin/systat/extern.h @@ -76,6 +76,7 @@ void closeiostat(WINDOW *); void closeip(WINDOW *); void closeip6(WINDOW *); void closekre(WINDOW *); +void closenetbw(WINDOW *); void closenetstat(WINDOW *); void closepigs(WINDOW *); void closeswap(WINDOW *); @@ -83,6 +84,7 @@ void closetcp(WINDOW *); int cmdifstat(const char *, const char *); int cmdiostat(const char *, const char *); int cmdkre(const char *, const char *); +int cmdnetbw(const char *, const char *); int cmdnetstat(const char *, const char *); struct cmdtab *lookup(const char *); void command(const char *); @@ -98,6 +100,7 @@ void fetchip(void); void fetchip6(void); void fetchiostat(void); void fetchkre(void); +void fetchnetbw(void); void fetchnetstat(void); void fetchpigs(void); void fetchswap(void); @@ -111,6 +114,7 @@ int initip(void); int initip6(void); int initiostat(void); int initkre(void); +int initnetbw(void); int initnetstat(void); int initpigs(void); int initswap(void); @@ -124,6 +128,7 @@ void labelip(void); void labelip6(void); void labeliostat(void); void labelkre(void); +void labelnetbw(void); void labelnetstat(void); void labelpigs(void); void labels(void); @@ -139,6 +144,7 @@ WINDOW *openip(void); WINDOW *openip6(void); WINDOW *openiostat(void); WINDOW *openkre(void); +WINDOW *opennetbw(void); WINDOW *opennetstat(void); WINDOW *openpigs(void); WINDOW *openswap(void); @@ -156,6 +162,7 @@ void showip(void); void showip6(void); void showiostat(void); void showkre(void); +void shownetbw(void); void shownetstat(void); void showpigs(void); void showswap(void); diff --git a/usr.bin/systat/netbw.c b/usr.bin/systat/netbw.c new file mode 100644 index 0000000..785af5f --- /dev/null +++ b/usr.bin/systat/netbw.c @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2013 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#ifdef INET6 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "systat.h" +#include "extern.h" + +/* Reduce diff from DragonflyBSD. */ +union in_dependaddr { + struct in_addr_4in6 id46_addr; + struct in6_addr id6_addr; +}; +typedef int32_t tcp_seq_diff_t; +#define s6_addr16 __u6_addr.__u6_addr16 + +struct mytcpcb { + RB_ENTRY(mytcpcb) rb_node; + int seq; + struct xtcpcb xtcp; + struct xtcpcb last_xtcp; +}; + +static int +mytcpcb_cmp(struct mytcpcb *tcp1, struct mytcpcb *tcp2) +{ + int r; + + /* + * Low local or foreign port comes first (local has priority). + */ + if (ntohs(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie_lport) >= 1024 && + ntohs(tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie_lport) >= 1024) { + if (ntohs(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie_fport) < + ntohs(tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie_fport)) + return(-1); + if (ntohs(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie_fport) > + ntohs(tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie_fport)) + return(1); + } + + if (ntohs(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie_lport) < + ntohs(tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie_lport)) + return(-1); + if (ntohs(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie_lport) > + ntohs(tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie_lport)) + return(1); + if (ntohs(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie_fport) < + ntohs(tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie_fport)) + return(-1); + if (ntohs(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie_fport) > + ntohs(tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie_fport)) + return(1); + + /* + * Sort IPV4 vs IPV6 addresses + */ + if ((tcp1->xtcp.xt_inp.inp_vflag & (INP_IPV4|INP_IPV6)) < + (tcp2->xtcp.xt_inp.inp_vflag & (INP_IPV4|INP_IPV6))) + return(-1); + if ((tcp1->xtcp.xt_inp.inp_vflag & (INP_IPV4|INP_IPV6)) > + (tcp2->xtcp.xt_inp.inp_vflag & (INP_IPV4|INP_IPV6))) + return(1); + + /* + * Local and foreign addresses + */ + if (tcp1->xtcp.xt_inp.inp_vflag & INP_IPV4) { + if (ntohl(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie_laddr.s_addr) < + ntohl(tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie_laddr.s_addr)) + return(-1); + if (ntohl(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie_laddr.s_addr) > + ntohl(tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie_laddr.s_addr)) + return(1); + if (ntohl(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie_faddr.s_addr) < + ntohl(tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie_faddr.s_addr)) + return(-1); + if (ntohl(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie_faddr.s_addr) > + ntohl(tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie_faddr.s_addr)) + return(1); + } else if (tcp1->xtcp.xt_inp.inp_vflag & INP_IPV6) { + r = bcmp(&tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie6_faddr, + &tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie6_faddr, + sizeof(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie6_faddr)); + if (r) + return(r); + } else { + r = bcmp(&tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie6_faddr, + &tcp2->xtcp.xt_inp.inp_inc.inc_ie.ie6_faddr, + sizeof(tcp1->xtcp.xt_inp.inp_inc.inc_ie.ie6_faddr)); + if (r) + return(r); + } + return(0); +} + +struct mytcpcb_tree; +static RB_HEAD(mytcpcb_tree, mytcpcb); +RB_PROTOTYPE_STATIC(mytcpcb_tree, mytcpcb, rb_node, mytcpcb_cmp); +RB_GENERATE_STATIC(mytcpcb_tree, mytcpcb, rb_node, mytcpcb_cmp); + +static struct mytcpcb_tree mytcp_tree; +static struct timeval tv_curr; +static struct timeval tv_last; +static struct tcpstat tcp_curr; +static struct tcpstat tcp_last; +static int tcp_pcb_seq; + +static const char *numtok(double value); +static void netbwline(int row, struct mytcpcb *elm, double delta_time); +static const char *netaddrstr(u_char vflags, union in_dependaddr *depaddr, + u_int16_t port); +static void updatepcb(struct xtcpcb *xtcp); + +#define DELTARATE(field) \ + ((double)(tcp_curr.field - tcp_last.field) / delta_time) + +#define DELTAELM(field) \ + ((double)(tcp_seq_diff_t)(elm->xtcp.field - \ + elm->last_xtcp.field) / \ + delta_time) + +#define DELTAELMSCALE(field, scale) \ + ((double)((tcp_seq_diff_t)(elm->xtcp.field - \ + elm->last_xtcp.field) << scale) / \ + delta_time) + +WINDOW * +opennetbw(void) +{ + RB_INIT(&mytcp_tree); + return (subwin(stdscr, LINES-0-1, 0, 0, 0)); +} + +void +closenetbw(WINDOW *w) +{ + struct mytcpcb *mytcp; + + while ((mytcp = RB_ROOT(&mytcp_tree)) != NULL) { + RB_REMOVE(mytcpcb_tree, &mytcp_tree, mytcp); + free(mytcp); + } + + if (w != NULL) { + wclear(w); + wrefresh(w); + delwin(w); + } +} + +int +initnetbw(void) +{ + return(1); +} + +void +fetchnetbw(void) +{ + struct xinpgen *inpg; + struct xtcpcb *xtp; + char *cur, *end; + size_t len; + + /* + * Extract PCB list + */ + inpg = (struct xinpgen *)sysctl_dynread("net.inet.tcp.pcblist", &len); + if (inpg == NULL) + return; + /* + * We currently do no require a consistent pcb list. + * Try to be robust in case of struct size changes + */ + cur = ((char *)inpg) + inpg->xig_len; + /* There is also a trailing struct xinpgen */ + end = ((char *)inpg) + len - inpg->xig_len; + if (end <= cur) { + free(inpg); + return; + } + ++tcp_pcb_seq; + xtp = (struct xtcpcb *)cur; + while (cur + xtp->xt_len <= end) { + updatepcb(xtp); + cur += xtp->xt_len; + xtp = (struct xtcpcb *)cur; + } + free(inpg); + + /* + * General stats + */ + tcp_last = tcp_curr; + len = sizeof(tcp_curr); + if (sysctlbyname("net.inet.tcp.stats", &tcp_curr, &len, NULL, 0) < 0) + return; + tv_last = tv_curr; + gettimeofday(&tv_curr, NULL); +} + +void +labelnetbw(void) +{ + wmove(wnd, 0, 0); + wclrtobot(wnd); +#if 0 + mvwaddstr(wnd, 0, LADDR, "Local Address"); + mvwaddstr(wnd, 0, FADDR, "Foreign Address"); + mvwaddstr(wnd, 0, PROTO, "Proto"); + mvwaddstr(wnd, 0, RCVCC, "Recv-Q"); + mvwaddstr(wnd, 0, SNDCC, "Send-Q"); + mvwaddstr(wnd, 0, STATE, "(state)"); +#endif +} + +void +shownetbw(void) +{ + double delta_time; + struct mytcpcb *elm, *telm; + int row; + + delta_time = (double)(tv_curr.tv_sec - tv_last.tv_sec) - 1.0 + + (tv_curr.tv_usec + 1000000 - tv_last.tv_usec) / 1e6; + if (delta_time < 0.1) + return; + + mvwprintw(wnd, 0, 0, + "tcp accepts %s connects %s " + " rcv %s snd %s rexmit %s", + numtok(DELTARATE(tcps_accepts)), + numtok(DELTARATE(tcps_connects) - DELTARATE(tcps_accepts)), + numtok(DELTARATE(tcps_rcvbyte)), + numtok(DELTARATE(tcps_sndbyte)), + numtok(DELTARATE(tcps_sndrexmitbyte))); + + row = 2; + RB_FOREACH_SAFE(elm, mytcpcb_tree, &mytcp_tree, telm) { + if (elm->seq == tcp_pcb_seq && + (elm->xtcp.xt_socket.so_rcv.sb_cc || + elm->xtcp.xt_socket.so_snd.sb_cc || + DELTAELM(xt_tp.snd_max) || + DELTAELM(xt_tp.rcv_nxt) + )) { + if (row < LINES - 3) + netbwline(row, elm, delta_time); + ++row; + } else if (elm->seq != tcp_pcb_seq) { + RB_REMOVE(mytcpcb_tree, &mytcp_tree, elm); + free(elm); + } + } + wmove(wnd, row, 0); + wclrtobot(wnd); + mvwprintw(wnd, LINES-2, 0, + "Rate/sec, " + "R=rxpend T=txpend N=nodelay T=tstmp " + "S=sack X=winscale F=fastrec"); +} + +static void +netbwline(int row, struct mytcpcb *elm, double delta_time) +{ + mvwprintw(wnd, row, 0, + "%s %s " + /*"rxb %s txb %s "*/ + "rcv %s snd %s " + "[%c%c%c%c%c%c%c]", + netaddrstr( + elm->xtcp.xt_inp.inp_vflag, + (union in_dependaddr *)&elm->xtcp.xt_inp.inp_inc.inc_ie. + ie_dependladdr, + ntohs(elm->xtcp.xt_inp.inp_inc.inc_ie.ie_lport)), + netaddrstr( + elm->xtcp.xt_inp.inp_vflag, + (union in_dependaddr*)&elm->xtcp.xt_inp.inp_inc.inc_ie. + ie_dependfaddr, + ntohs(elm->xtcp.xt_inp.inp_inc.inc_ie.ie_fport)), + /* + numtok(elm->xtcp.xt_socket.so_rcv.sb_cc), + numtok(elm->xtcp.xt_socket.so_snd.sb_cc), + */ + numtok(DELTAELM(xt_tp.rcv_nxt)), + numtok(DELTAELM(xt_tp.snd_max)), + (elm->xtcp.xt_socket.so_rcv.sb_cc > 15000 ? + 'R' : ' '), + (elm->xtcp.xt_socket.so_snd.sb_cc > 15000 ? + 'T' : ' '), + ((elm->xtcp.xt_tp.t_flags & TF_NODELAY) ? + 'N' : ' '), + ((elm->xtcp.xt_tp.t_flags & TF_RCVD_TSTMP) ? + 'T' : ' '), + ((elm->xtcp.xt_tp.t_flags & TF_SACK_PERMIT) ? + 'S' : ' '), + ((elm->xtcp.xt_tp.t_flags & TF_RCVD_SCALE) ? + 'X' : ' '), + ((elm->xtcp.xt_tp.t_flags & TF_FASTRECOVERY) ? + 'F' : ' ') + ); + wclrtoeol(wnd); +} + +#if 0 +int +cmdnetbw(const char *cmd __unused, char *args __unused) +{ + fetchnetbw(); + shownetbw(); + refresh(); + + return (0); +} +#endif + +#define MAXINDEXES 8 + +static const char * +numtok(double value) +{ + static char buf[MAXINDEXES][32]; + static int nexti; + static const char *suffixes[] = { " ", "K", "M", "G", "T", NULL }; + int suffix = 0; + const char *fmt; + + while (value >= 1000.0 && suffixes[suffix+1]) { + value /= 1000.0; + ++suffix; + } + nexti = (nexti + 1) % MAXINDEXES; + if (value < 0.001) { + fmt = " "; + } else if (value < 1.0) { + fmt = "%5.3f%s"; + } else if (value < 10.0) { + fmt = "%5.3f%s"; + } else if (value < 100.0) { + fmt = "%5.2f%s"; + } else { + fmt = "%5.1f%s"; + } + snprintf(buf[nexti], sizeof(buf[nexti]), + fmt, value, suffixes[suffix]); + return (buf[nexti]); +} + +static const char * +netaddrstr(u_char vflags, union in_dependaddr *depaddr, u_int16_t port) +{ + static char buf[MAXINDEXES][64]; + static int nexta; + char bufip[64]; + + nexta = (nexta + 1) % MAXINDEXES; + + if (vflags & INP_IPV4) { + snprintf(bufip, sizeof(bufip), + "%d.%d.%d.%d", + (ntohl(depaddr->id46_addr.ia46_addr4.s_addr) >> 24) & + 255, + (ntohl(depaddr->id46_addr.ia46_addr4.s_addr) >> 16) & + 255, + (ntohl(depaddr->id46_addr.ia46_addr4.s_addr) >> 8) & + 255, + (ntohl(depaddr->id46_addr.ia46_addr4.s_addr) >> 0) & + 255); + snprintf(buf[nexta], sizeof(buf[nexta]), + "%15s:%-5d", bufip, port); + } else if (vflags & INP_IPV6) { + snprintf(bufip, sizeof(bufip), + "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x", + ntohs(depaddr->id6_addr.s6_addr16[0]), + ntohs(depaddr->id6_addr.s6_addr16[1]), + ntohs(depaddr->id6_addr.s6_addr16[2]), + ntohs(depaddr->id6_addr.s6_addr16[3]), + ntohs(depaddr->id6_addr.s6_addr16[4]), + ntohs(depaddr->id6_addr.s6_addr16[5]), + ntohs(depaddr->id6_addr.s6_addr16[6]), + ntohs(depaddr->id6_addr.s6_addr16[7])); + snprintf(buf[nexta], sizeof(buf[nexta]), + "%39s:%-5d", bufip, port); + } else { + snprintf(bufip, sizeof(bufip), ""); + snprintf(buf[nexta], sizeof(buf[nexta]), + "%15s:%-5d", bufip, port); + } + return (buf[nexta]); +} + +static void +updatepcb(struct xtcpcb *xtcp) +{ + struct mytcpcb dummy; + struct mytcpcb *elm; + + dummy.xtcp = *xtcp; + if ((elm = RB_FIND(mytcpcb_tree, &mytcp_tree, &dummy)) == NULL) { + elm = malloc(sizeof(*elm)); + bzero(elm, sizeof(*elm)); + elm->xtcp = *xtcp; + elm->last_xtcp = *xtcp; + RB_INSERT(mytcpcb_tree, &mytcp_tree, elm); + } else { + elm->last_xtcp = elm->xtcp; + elm->xtcp = *xtcp; + } + elm->seq = tcp_pcb_seq; +} diff --git a/usr.bin/systat/systat.1 b/usr.bin/systat/systat.1 index 5fc3257..0ab1738 100644 --- a/usr.bin/systat/systat.1 +++ b/usr.bin/systat/systat.1 @@ -94,6 +94,7 @@ to be one of: .Ic iostat , .Ic ip , .Ic ip6 , +.Ic netbw , .Ic netstat , .Ic pigs , .Ic swap , @@ -441,6 +442,9 @@ Display statistics averaged over the refresh interval (the default). .It Cm zero Reset running statistics to zero. .El +.It Ic netbw +Display aggregate and per-connection TCP receive and transmit rates. +Only active TCP connections are shown. .It Ic netstat Display, in the lower window, network connections. By default, -- 1.8.5.4