tcpdump(8) can provide something very similar; search for TCP packets with the SYN flag to catch the first packet in a three-way handshake that is for your other partner:
$ sudo tcpdump -c 10 -i eth0 "tcp[tcpflags] & (tcp-syn) != 0 and dst 192.168.0.1" tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 18:26:24.800308 IP haig.59419 > 192.168.0.1.telnet: Flags [S], seq 3197302320, win 14600, options [mss 1460,sackOK,TS val 19460844 ecr 0,nop,wscale 7], length 0 ... 18:26:27.420132 IP haig.59428 > 192.168.0.1.telnet: Flags [S], seq 1238498237, win 14600, options [mss 1460,sackOK,TS val 19461106 ecr 0,nop,wscale 7], length 0 10 packets captured 10 packets received by filter 0 packets dropped by kernel
You can use /usr/bin/time or the built-in shell time or do some arithmetic with timestamps on the output to get the average speed per second. (Use more than ten packages - this was just for demonstration.)
Update
I wrote a small program to run tcpdump(8) , count packets and report how many packets were sent in the specified interval:
# ./counter --host 192.168.0.1 --interface eth0 --interval 3 2 requests in 3 seconds; average 0.67 req/seq 20 requests in 3 seconds; average 6.67 req/seq 19 requests in 3 seconds; average 6.33 req/seq 19 requests in 3 seconds; average 6.33 req/seq ^C # ./counter --host 192.168.0.1 --interface eth0 --interval 5 30 requests in 5 seconds; average 6.00 req/seq 20 requests in 5 seconds; average 4.00 req/seq 1176 requests in 5 seconds; average 235.20 req/seq 1414 requests in 5 seconds; average 282.80 req/seq 0 requests in 5 seconds; average 0.00 req/seq ^C
Since it asks tcpdump(8) use linear buffering output, I'm a little afraid that it might not scale beyond 200-300 requests per second, at least on my hardware. But without outputting the line with the buffer, tcpdump(8) will wait until its output buffer (see setvbuf(3) for details) is full before sending any result, which will lead to extremely sharp results.
But if your connection level is not so high, it will probably do what you need. If your connection level is higher, then this little hack is probably best ignored - it seems to me that perhaps iptables(8) can count streams.
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <getopt.h> #include <signal.h> #include <errno.h> #include <fcntl.h> #include <sys/types.h> #define CMDLEN 1024 #define TCPDUMPLEN 4096 int show_stats; long counter; void alarm_handler(int signum) { show_stats = 1; } void install_handler(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); sa.sa_handler = &alarm_handler; if (sigaction(SIGALRM, &sa, NULL) == -1) { perror("Can't install alarm handler"); exit(1); } } int count_lines(char *p, int bytes) { int counter = 0; char *i; for (i=p; i < p+bytes ; i++) { if (*i == '\n') counter++; } return counter; } int spawn_tcpdump(char *host, char *interface) { int fd[2]; pid_t child; if (pipe(fd) == -1) { perror("Can't create pipes"); exit(1); } child = fork(); if (child == -1) { perror("Can't fork(2) for tcpdump"); exit(1); } if (child == 0) { int null; int len; char syn_and_dst[CMDLEN]; len = snprintf(syn_and_dst, CMDLEN, "tcp[tcpflags] & (tcp-syn) != 0 and dst %s", host); if (len > CMDLEN) { perror("host argument too long"); exit(1); } /* child writes into pipe */ close(fd[0]); dup2(fd[1], STDOUT_FILENO); /* throw away first two lines of tcpdump output */ null = open("/dev/null", O_WRONLY); if (null == -1) { perror("Can't open /dev/null"); exit(1); } dup2(null, STDERR_FILENO); execl("/usr/sbin/tcpdump", "tcpdump", "-l", "-n", "-s 96", "-i", interface, syn_and_dst, (char *) NULL); /* can't reach */ perror("Cannot execute tcpdump"); exit(1); } else { /* parent reads from pipe */ close(fd[1]); return fd[0]; } } int main(int argc, char *argv[]) { int tcpdump; char *host; char *interface; long interval; while (1) { int option_index; int c; static struct option opts[] = { {"host", required_argument, NULL, 'h'}, {"interface", required_argument, NULL, 'i'}, {"interval", required_argument, NULL, 'n'}, {0, 0, 0, 0}, }; c = getopt_long(argc, argv, "", opts, &option_index); if (c == -1) break; switch (c) { case 'h': host = strdup(optarg); break; case 'i': interface = strdup(optarg); break; case 'n': { char *endptr; interval = strtol(optarg, &endptr, 10); if (!(optarg[0] != '\0' && endptr[0] == '\0')) { fprintf(stderr, "Expected integer; invalid" " input '%s'\n", optarg); exit(1); } } break; default: fprintf(stderr, "Option parsing error\n"); exit(1); } } if (optind < argc) { fprintf(stderr, "unexpected arguments: "); while (optind < argc) { fprintf(stderr, "%s ", argv[optind++]); } fprintf(stderr, "\n"); } tcpdump = spawn_tcpdump(host, interface); install_handler(); alarm(interval); while(1) { if (show_stats) { printf("%ld requests in %ld seconds; average %2.2f req/seq\n", counter, interval, (double)counter / (double)interval); counter = 0; show_stats = 0; alarm(interval); } else { char buffer[TCPDUMPLEN]; int ret; memset(buffer, 0, TCPDUMPLEN); ret = read(tcpdump, buffer, TCPDUMPLEN); if (ret == -1 && errno == EINTR) { /* nop */ } else if (ret == -1) { perror("read"); exit(1); } else { counter += count_lines(buffer, ret); } } } exit(0); }