Index: contrib/netcat/nc.1 =================================================================== --- contrib/netcat/nc.1 +++ contrib/netcat/nc.1 @@ -27,7 +27,7 @@ .\" .\" $FreeBSD$ .\" -.Dd September 26, 2015 +.Dd August 20, 2019 .Dt NC 1 .Os .Sh NAME @@ -36,7 +36,7 @@ .Sh SYNOPSIS .Nm nc .Bk -words -.Op Fl 46DdEFhklNnrStUuvz +.Op Fl 46DdEFhklMNnrStUuvz .Op Fl e Ar IPsec_policy .Op Fl I Ar length .Op Fl i Ar interval @@ -170,6 +170,12 @@ Additionally, any timeouts specified with the .Fl w 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 .Xr shutdown 2 the network socket after EOF on the input. Index: contrib/netcat/netcat.c =================================================================== --- contrib/netcat/netcat.c +++ contrib/netcat/netcat.c @@ -33,10 +33,16 @@ * *Hobbit* . */ +#include +#include +#include #include #include +#include #include #include +#include +#include #include #include #include @@ -85,6 +91,7 @@ unsigned int iflag; /* Interval Flag */ int kflag; /* More than one connect */ int lflag; /* Bind to local port */ +int FreeBSD_Mflag; /* Measure using stats(3) */ int Nflag; /* shutdown() network socket */ int nflag; /* Don't do name look up */ int FreeBSD_Oflag; /* Do not use TCP options */ @@ -123,6 +130,7 @@ int unix_bind(char *); int unix_connect(char *); int unix_listen(char *); +void FreeBSD_print_measurements(int); void set_common_sockopts(int, int); int map_tos(char *, int *); void report_connect(const struct sockaddr *, socklen_t); @@ -167,7 +175,7 @@ signal(SIGPIPE, SIG_IGN); 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) { switch (ch) { case '4': @@ -224,6 +232,9 @@ case 'l': lflag = 1; break; + case 'M': + FreeBSD_Mflag = 1; + break; case 'N': Nflag = 1; break; @@ -827,17 +838,23 @@ /* both inputs are gone, buffers are empty, we are done */ if (pfd[POLL_STDIN].fd == -1 && pfd[POLL_NETIN].fd == -1 && stdinbufpos == 0 && netinbufpos == 0) { + if (FreeBSD_Mflag) + FreeBSD_print_measurements(net_fd); close(net_fd); return; } /* both outputs are gone, we can't continue */ if (pfd[POLL_NETOUT].fd == -1 && pfd[POLL_STDOUT].fd == -1) { + if (FreeBSD_Mflag) + FreeBSD_print_measurements(net_fd); close(net_fd); return; } /* listen and net in gone, queues empty, done */ if (lflag && pfd[POLL_NETIN].fd == -1 && stdinbufpos == 0 && netinbufpos == 0) { + if (FreeBSD_Mflag) + FreeBSD_print_measurements(net_fd); close(net_fd); return; } @@ -858,8 +875,11 @@ } /* timeout happened */ - if (num_fds == 0) + if (num_fds == 0) { + if (FreeBSD_Mflag) + FreeBSD_print_measurements(net_fd); return; + } /* treat socket error conditions */ for (n = 0; n < 4; n++) { @@ -1181,6 +1201,48 @@ } void +FreeBSD_print_measurements(int s) +{ + struct statsblob *statsb; + struct sbuf *sb; + socklen_t sockoptlen; + int error; + + 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) { + warnx("getsockopt(TCP_STATS) failed; " + "kernel built without \"options STATS\"?"); + 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)); +} + +void set_common_sockopts(int s, int af) { int x = 1; @@ -1218,6 +1280,11 @@ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &Oflag, sizeof(Oflag)) == -1) err(1, "set TCP send buffer size"); + } + if (FreeBSD_Mflag) { + if (setsockopt(s, IPPROTO_TCP, TCP_STATS, + &FreeBSD_Mflag, sizeof(FreeBSD_Mflag)) == -1) + err(1, "enable TCP_STATS gathering"); } if (FreeBSD_Oflag) { if (setsockopt(s, IPPROTO_TCP, TCP_NOOPT, Index: usr.bin/nc/Makefile =================================================================== --- usr.bin/nc/Makefile +++ usr.bin/nc/Makefile @@ -6,7 +6,7 @@ SRCS= netcat.c atomicio.c socks.c CFLAGS+=-DIPSEC -LIBADD= ipsec +LIBADD= ipsec sbuf stats WARNS?= 2