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;
}
}
}