Add -M option to nc(1), which makes it print the TCP connection
statistics obtained with stats(3) in JSON format to standard error. Reviewed by: allanjude, thj, cem (earlier version) Tested by: thj MFC after: 2 weeks Relnotes: yes Sponsored by: Klara Inc. Differential Revision: https://reviews.freebsd.org/D21324
This commit is contained in:
parent
cd6bd6c451
commit
b2fad95038
@ -27,7 +27,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" $FreeBSD$
|
.\" $FreeBSD$
|
||||||
.\"
|
.\"
|
||||||
.Dd September 26, 2015
|
.Dd August 20, 2019
|
||||||
.Dt NC 1
|
.Dt NC 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@ -36,7 +36,7 @@
|
|||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm nc
|
.Nm nc
|
||||||
.Bk -words
|
.Bk -words
|
||||||
.Op Fl 46DdEFhklNnrStUuvz
|
.Op Fl 46DdEFhklMNnrStUuvz
|
||||||
.Op Fl e Ar IPsec_policy
|
.Op Fl e Ar IPsec_policy
|
||||||
.Op Fl I Ar length
|
.Op Fl I Ar length
|
||||||
.Op Fl i Ar interval
|
.Op Fl i Ar interval
|
||||||
@ -170,6 +170,12 @@ options.
|
|||||||
Additionally, any timeouts specified with the
|
Additionally, any timeouts specified with the
|
||||||
.Fl w
|
.Fl w
|
||||||
option are ignored.
|
option are ignored.
|
||||||
|
.It Fl M
|
||||||
|
Collect per-connection TCP statistics using the
|
||||||
|
.Xr stats 3
|
||||||
|
framework and print them in JSON format to
|
||||||
|
.Xr stderr 4
|
||||||
|
after the connection is closed.
|
||||||
.It Fl N
|
.It Fl N
|
||||||
.Xr shutdown 2
|
.Xr shutdown 2
|
||||||
the network socket after EOF on the input.
|
the network socket after EOF on the input.
|
||||||
|
@ -33,10 +33,16 @@
|
|||||||
* *Hobbit* <hobbit@avian.org>.
|
* *Hobbit* <hobbit@avian.org>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/arb.h>
|
||||||
#include <sys/limits.h>
|
#include <sys/limits.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/sbuf.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/sysctl.h>
|
#include <sys/sysctl.h>
|
||||||
|
#include <sys/qmath.h>
|
||||||
|
#include <sys/stats.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <sys/uio.h>
|
#include <sys/uio.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
@ -50,7 +56,6 @@
|
|||||||
#include <arpa/telnet.h>
|
#include <arpa/telnet.h>
|
||||||
|
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
#include <errno.h>
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
@ -58,7 +63,6 @@
|
|||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@ -85,6 +89,7 @@ int Fflag; /* fdpass sock to stdout */
|
|||||||
unsigned int iflag; /* Interval Flag */
|
unsigned int iflag; /* Interval Flag */
|
||||||
int kflag; /* More than one connect */
|
int kflag; /* More than one connect */
|
||||||
int lflag; /* Bind to local port */
|
int lflag; /* Bind to local port */
|
||||||
|
int FreeBSD_Mflag; /* Measure using stats(3) */
|
||||||
int Nflag; /* shutdown() network socket */
|
int Nflag; /* shutdown() network socket */
|
||||||
int nflag; /* Don't do name look up */
|
int nflag; /* Don't do name look up */
|
||||||
int FreeBSD_Oflag; /* Do not use TCP options */
|
int FreeBSD_Oflag; /* Do not use TCP options */
|
||||||
@ -123,6 +128,8 @@ int udptest(int);
|
|||||||
int unix_bind(char *);
|
int unix_bind(char *);
|
||||||
int unix_connect(char *);
|
int unix_connect(char *);
|
||||||
int unix_listen(char *);
|
int unix_listen(char *);
|
||||||
|
void FreeBSD_stats_setup(int);
|
||||||
|
void FreeBSD_stats_print(int);
|
||||||
void set_common_sockopts(int, int);
|
void set_common_sockopts(int, int);
|
||||||
int map_tos(char *, int *);
|
int map_tos(char *, int *);
|
||||||
void report_connect(const struct sockaddr *, socklen_t);
|
void report_connect(const struct sockaddr *, socklen_t);
|
||||||
@ -167,7 +174,7 @@ main(int argc, char *argv[])
|
|||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
while ((ch = getopt_long(argc, argv,
|
while ((ch = getopt_long(argc, argv,
|
||||||
"46DdEe:FhI:i:klNnoO:P:p:rSs:tT:UuV:vw:X:x:z",
|
"46DdEe:FhI:i:klMNnoO:P:p:rSs:tT:UuV:vw:X:x:z",
|
||||||
longopts, NULL)) != -1) {
|
longopts, NULL)) != -1) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case '4':
|
case '4':
|
||||||
@ -224,6 +231,13 @@ main(int argc, char *argv[])
|
|||||||
case 'l':
|
case 'l':
|
||||||
lflag = 1;
|
lflag = 1;
|
||||||
break;
|
break;
|
||||||
|
case 'M':
|
||||||
|
#ifndef WITH_STATS
|
||||||
|
errx(1, "-M requires stats(3) support");
|
||||||
|
#else
|
||||||
|
FreeBSD_Mflag = 1;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
case 'N':
|
case 'N':
|
||||||
Nflag = 1;
|
Nflag = 1;
|
||||||
break;
|
break;
|
||||||
@ -451,6 +465,8 @@ main(int argc, char *argv[])
|
|||||||
if (vflag)
|
if (vflag)
|
||||||
report_connect((struct sockaddr *)&cliaddr, len);
|
report_connect((struct sockaddr *)&cliaddr, len);
|
||||||
|
|
||||||
|
if (FreeBSD_Mflag)
|
||||||
|
FreeBSD_stats_setup(connfd);
|
||||||
readwrite(connfd);
|
readwrite(connfd);
|
||||||
close(connfd);
|
close(connfd);
|
||||||
}
|
}
|
||||||
@ -801,6 +817,7 @@ readwrite(int net_fd)
|
|||||||
unsigned char stdinbuf[BUFSIZE];
|
unsigned char stdinbuf[BUFSIZE];
|
||||||
size_t stdinbufpos = 0;
|
size_t stdinbufpos = 0;
|
||||||
int n, num_fds;
|
int n, num_fds;
|
||||||
|
int stats_printed = 0;
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
|
|
||||||
/* don't read from stdin if requested */
|
/* don't read from stdin if requested */
|
||||||
@ -827,17 +844,23 @@ readwrite(int net_fd)
|
|||||||
/* both inputs are gone, buffers are empty, we are done */
|
/* both inputs are gone, buffers are empty, we are done */
|
||||||
if (pfd[POLL_STDIN].fd == -1 && pfd[POLL_NETIN].fd == -1
|
if (pfd[POLL_STDIN].fd == -1 && pfd[POLL_NETIN].fd == -1
|
||||||
&& stdinbufpos == 0 && netinbufpos == 0) {
|
&& stdinbufpos == 0 && netinbufpos == 0) {
|
||||||
|
if (FreeBSD_Mflag && !stats_printed)
|
||||||
|
FreeBSD_stats_print(net_fd);
|
||||||
close(net_fd);
|
close(net_fd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* both outputs are gone, we can't continue */
|
/* both outputs are gone, we can't continue */
|
||||||
if (pfd[POLL_NETOUT].fd == -1 && pfd[POLL_STDOUT].fd == -1) {
|
if (pfd[POLL_NETOUT].fd == -1 && pfd[POLL_STDOUT].fd == -1) {
|
||||||
|
if (FreeBSD_Mflag && !stats_printed)
|
||||||
|
FreeBSD_stats_print(net_fd);
|
||||||
close(net_fd);
|
close(net_fd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* listen and net in gone, queues empty, done */
|
/* listen and net in gone, queues empty, done */
|
||||||
if (lflag && pfd[POLL_NETIN].fd == -1
|
if (lflag && pfd[POLL_NETIN].fd == -1
|
||||||
&& stdinbufpos == 0 && netinbufpos == 0) {
|
&& stdinbufpos == 0 && netinbufpos == 0) {
|
||||||
|
if (FreeBSD_Mflag && !stats_printed)
|
||||||
|
FreeBSD_stats_print(net_fd);
|
||||||
close(net_fd);
|
close(net_fd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -858,8 +881,11 @@ readwrite(int net_fd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* timeout happened */
|
/* timeout happened */
|
||||||
if (num_fds == 0)
|
if (num_fds == 0) {
|
||||||
|
if (FreeBSD_Mflag)
|
||||||
|
FreeBSD_stats_print(net_fd);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* treat socket error conditions */
|
/* treat socket error conditions */
|
||||||
for (n = 0; n < 4; n++) {
|
for (n = 0; n < 4; n++) {
|
||||||
@ -961,8 +987,13 @@ readwrite(int net_fd)
|
|||||||
|
|
||||||
/* stdin gone and queue empty? */
|
/* stdin gone and queue empty? */
|
||||||
if (pfd[POLL_STDIN].fd == -1 && stdinbufpos == 0) {
|
if (pfd[POLL_STDIN].fd == -1 && stdinbufpos == 0) {
|
||||||
if (pfd[POLL_NETOUT].fd != -1 && Nflag)
|
if (pfd[POLL_NETOUT].fd != -1 && Nflag) {
|
||||||
|
if (FreeBSD_Mflag) {
|
||||||
|
FreeBSD_stats_print(net_fd);
|
||||||
|
stats_printed = 1;
|
||||||
|
}
|
||||||
shutdown(pfd[POLL_NETOUT].fd, SHUT_WR);
|
shutdown(pfd[POLL_NETOUT].fd, SHUT_WR);
|
||||||
|
}
|
||||||
pfd[POLL_NETOUT].fd = -1;
|
pfd[POLL_NETOUT].fd = -1;
|
||||||
}
|
}
|
||||||
/* net in gone and queue empty? */
|
/* net in gone and queue empty? */
|
||||||
@ -1180,6 +1211,67 @@ udptest(int s)
|
|||||||
return (ret);
|
return (ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FreeBSD_stats_setup(int s)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (setsockopt(s, IPPROTO_TCP, TCP_STATS,
|
||||||
|
&FreeBSD_Mflag, sizeof(FreeBSD_Mflag)) == -1) {
|
||||||
|
if (errno == EOPNOTSUPP) {
|
||||||
|
warnx("getsockopt(TCP_STATS) failed; "
|
||||||
|
"kernel built without \"options STATS\"?");
|
||||||
|
}
|
||||||
|
err(1, "enable TCP_STATS gathering");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FreeBSD_stats_print(int s)
|
||||||
|
{
|
||||||
|
#ifdef WITH_STATS
|
||||||
|
struct statsblob *statsb;
|
||||||
|
struct sbuf *sb;
|
||||||
|
socklen_t sockoptlen;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This usleep is a workaround for TCP_STATS reporting
|
||||||
|
* incorrect values for TXPB.
|
||||||
|
*/
|
||||||
|
usleep(100000);
|
||||||
|
|
||||||
|
sockoptlen = 2048;
|
||||||
|
statsb = malloc(sockoptlen);
|
||||||
|
if (statsb == NULL)
|
||||||
|
err(1, "malloc");
|
||||||
|
error = getsockopt(s, IPPROTO_TCP, TCP_STATS, statsb, &sockoptlen);
|
||||||
|
if (error != 0) {
|
||||||
|
if (errno == EOVERFLOW && statsb->cursz > sockoptlen) {
|
||||||
|
/* Retry with a larger size. */
|
||||||
|
sockoptlen = statsb->cursz;
|
||||||
|
statsb = realloc(statsb, sockoptlen);
|
||||||
|
if (statsb == NULL)
|
||||||
|
err(1, "realloc");
|
||||||
|
error = getsockopt(s, IPPROTO_TCP, TCP_STATS,
|
||||||
|
statsb, &sockoptlen);
|
||||||
|
}
|
||||||
|
if (error != 0)
|
||||||
|
err(1, "getsockopt");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb = sbuf_new_auto();
|
||||||
|
error = stats_blob_tostr(statsb, sb, SB_STRFMT_JSON, SB_TOSTR_META);
|
||||||
|
if (error != 0)
|
||||||
|
errc(1, error, "stats_blob_tostr");
|
||||||
|
|
||||||
|
error = sbuf_finish(sb);
|
||||||
|
if (error != 0)
|
||||||
|
err(1, "sbuf_finish");
|
||||||
|
|
||||||
|
fprintf(stderr, "%s\n", sbuf_data(sb));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
set_common_sockopts(int s, int af)
|
set_common_sockopts(int s, int af)
|
||||||
{
|
{
|
||||||
@ -1224,6 +1316,8 @@ set_common_sockopts(int s, int af)
|
|||||||
&FreeBSD_Oflag, sizeof(FreeBSD_Oflag)) == -1)
|
&FreeBSD_Oflag, sizeof(FreeBSD_Oflag)) == -1)
|
||||||
err(1, "disable TCP options");
|
err(1, "disable TCP options");
|
||||||
}
|
}
|
||||||
|
if (FreeBSD_Mflag)
|
||||||
|
FreeBSD_stats_setup(s);
|
||||||
#ifdef IPSEC
|
#ifdef IPSEC
|
||||||
if (ipsec_policy[0] != NULL)
|
if (ipsec_policy[0] != NULL)
|
||||||
add_ipsec_policy(s, af, ipsec_policy[0]);
|
add_ipsec_policy(s, af, ipsec_policy[0]);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# $FreeBSD$
|
# $FreeBSD$
|
||||||
|
|
||||||
|
.include <src.opts.mk>
|
||||||
|
|
||||||
.PATH: ${SRCTOP}/contrib/netcat
|
.PATH: ${SRCTOP}/contrib/netcat
|
||||||
|
|
||||||
PROG= nc
|
PROG= nc
|
||||||
@ -8,6 +10,11 @@ SRCS= netcat.c atomicio.c socks.c
|
|||||||
CFLAGS+=-DIPSEC
|
CFLAGS+=-DIPSEC
|
||||||
LIBADD= ipsec
|
LIBADD= ipsec
|
||||||
|
|
||||||
|
.if ${MK_STATS} != "no" && !defined(RESCUE)
|
||||||
|
LIBADD+= sbuf stats
|
||||||
|
CFLAGS+= -DWITH_STATS
|
||||||
|
.endif
|
||||||
|
|
||||||
WARNS?= 2
|
WARNS?= 2
|
||||||
|
|
||||||
.include <bsd.prog.mk>
|
.include <bsd.prog.mk>
|
||||||
|
Loading…
Reference in New Issue
Block a user