Ted Unangst wrote: > On Mon, Jun 09, 2014 at 21:54, Theo de Raadt wrote: >>> > A better patch is probably the following which also increases the size >>> > of the buffer to at least 64k: >>> >>> Agreed. >> >> One thing to be aware of. That function is syncronous. It will read >> as much as it can get, then it will do an "atomic" write operation to >> flush the buffer out the other way. >> >> If you have substantially different speeds, this can be a substantial >> 'buffer bloat'. >> >> Since it is handling a session in both directions... expect to see >> some substantial jaggies. > > Having convinced me this a problem (it's already in the code, but 64k > buffers will make it worse), I scaled back to 16k. > > Now soliciting diffs to change readwrite to a loop with two buffers > that poll()s in all four directions. :)
The attached code is based on a stripped down I/O buffer that I wrote as a hobby project. I'm not a big user of netcat so I'm not sure it'll work in all kinds of situations. I don't want to spend a lot of time on it either but I hope the attached code is useful as is, or may possibly inspire other people to come up with something better. e.g., I tried adding some error handling but I'm not sure I did it right. Another thing bothering me is the atelnet function. The way I see it is that netcat sets up two communication channels, one for incoming traffic on a socket to stdout, another one for traffic from stdin going out of that socket. atelnet seems to short-circuit that which feels wrong. (I suppose atelnet can't be removed as people probably rely on it) First I'll shortly try to explain the idea behind the buffer (and a single communication channel). Assume data traveling from a file descriptor (fd1) to the buffer, and from the buffer to another file descriptor (fd2). initial buffer: |--------------| <----size----> ^ cur (current position in the buffer) start with fd1 enabled and fd2 disabled. 1) a read from fd1 writes into the buffer: |WWWWWWWWWWWW--| <----size----> <---used---> ^ cur (isn't used in this case) switch off fd1 and enable fd2. 2) a read from buffer (cur position) writes to fd2: (cur position gets updated by amount of data written to fd2) |RRRRWWWWWWWW--| <----size----> <-used-> ^ cur repeat this step until all data has been written to fd2: |RRRRRRRRRRRR--| <----size----> ^ cur 3) all data has been transferred, reset buffer, disable fd2 and enable fd1: |--------------| <----size----> ^ cur 4) repeat step 1, 2 and 3. The patch itself: Index: netcat.c =================================================================== RCS file: /cvs/src/usr.bin/nc/netcat.c,v retrieving revision 1.121 diff -u -p -u -r1.121 netcat.c --- netcat.c 10 Jun 2014 16:35:42 -0000 1.121 +++ netcat.c 27 Jun 2014 12:42:18 -0000 @@ -732,60 +732,141 @@ local_listen(char *host, char *port, str void readwrite(int nfd) { - struct pollfd pfd[2]; - unsigned char buf[16 * 1024]; - int n, wfd = fileno(stdin); - int lfd = fileno(stdout); - int plen; - - plen = sizeof(buf); + static struct { + #define _BUFFER_SIZE (16*1024) + size_t size; + size_t used; + unsigned char *cur; + unsigned char data[_BUFFER_SIZE]; + } buf[2] = + { + { _BUFFER_SIZE, 0, buf[0].data }, + { _BUFFER_SIZE, 0, buf[1].data }, + }; + struct pollfd pfd[4]; + int npfd; + ssize_t n; /* Setup Network FD */ pfd[0].fd = nfd; pfd[0].events = POLLIN; + pfd[1].fd = -1; + pfd[1].events = POLLOUT; - /* Set up STDIN FD. */ - pfd[1].fd = wfd; - pfd[1].events = POLLIN; + /* Set up STDIN/STDOUT FD. */ + pfd[2].fd = -1; + pfd[2].events = POLLOUT; + pfd[3].fd = STDIN_FILENO; + pfd[3].events = POLLIN; - while (pfd[0].fd != -1) { + while (pfd[0].fd != -1 || pfd[1].fd != -1 || + pfd[2].fd != -1 || pfd[3].fd != -1) { if (iflag) sleep(iflag); - if ((n = poll(pfd, 2 - dflag, timeout)) < 0) { + npfd = poll(pfd, 4 - dflag, timeout); + if (npfd < 0) { close(nfd); err(1, "Polling Error"); } - if (n == 0) + if (npfd == 0) return; if (pfd[0].revents & POLLIN) { - if ((n = read(nfd, buf, plen)) < 0) - return; - else if (n == 0) { + n = read(pfd[0].fd, buf[0].data, buf[0].size); + if (n == -1) { + if (errno != EAGAIN && errno != EINTR) + return; + } else if (n == 0) { shutdown(nfd, SHUT_RD); pfd[0].fd = -1; pfd[0].events = 0; - } else { + } else if (n > 0) { + buf[0].used = n; if (tflag) - atelnet(nfd, buf, n); - if (atomicio(vwrite, lfd, buf, n) != n) + atelnet(nfd, buf[0].data, buf[0].used); + pfd[0].fd = -1; + pfd[2].fd = STDOUT_FILENO; + } + } + + if (pfd[1].revents & POLLOUT) { + n = write(pfd[1].fd, buf[1].cur, buf[1].used); + if (n == -1) { + switch(errno) { + case EAGAIN: + case EINTR: + case ENOBUFS: + break; + case EPIPE: + /*close(pfd[1].fd);*/ + pfd[1].fd = -1; + pfd[3].fd = -1; + pfd[3].events = 0; + break; + default: return; + } + } else if (n > 0) { + if (n == buf[1].used) { + buf[1].cur = buf[1].data; + buf[1].used = 0; + pfd[1].fd = -1; + if (pfd[3].events & POLLIN) + pfd[3].fd = STDIN_FILENO; + } else { + buf[1].cur += n; + buf[1].used -= n; + } } } - if (!dflag && pfd[1].revents & POLLIN) { - if ((n = read(wfd, buf, plen)) < 0) - return; - else if (n == 0) { + if (pfd[2].revents & POLLOUT) { + n = write(pfd[2].fd, buf[0].cur, buf[0].used); + if (n == -1) { + switch(errno) { + case EAGAIN: + case EINTR: + case ENOBUFS: + break; + case EPIPE: + /*close(pfd[2].fd);*/ + pfd[2].fd = -1; + pfd[0].fd = -1; + pfd[0].events = 0; + break; + default: + return; + } + } else if (n > 0) { + if (n == buf[0].used) { + buf[0].cur = buf[0].data; + buf[0].used = 0; + pfd[2].fd = -1; + if (pfd[0].events & POLLIN) + pfd[0].fd = nfd; + } else { + buf[0].cur += n; + buf[0].used -= n; + } + } + } + + if (!dflag && pfd[3].revents & POLLIN) { + n = read(pfd[3].fd, buf[1].data, buf[1].size); + if (n == -1) { + if (errno != EAGAIN && errno != EINTR) + return; + } else if (n == 0) { if (Nflag) shutdown(nfd, SHUT_WR); - pfd[1].fd = -1; - pfd[1].events = 0; - } else { - if (atomicio(vwrite, nfd, buf, n) != n) - return; + pfd[3].fd = -1; + pfd[3].events = 0; + } else if (n > 0) { + buf[1].used = n; + pfd[3].fd = -1; + pfd[1].fd = nfd; } } }