Non-Blocking socket and poll() quirks in proxy - newbie
I am a newbie dabbling in C and my little project is to write a simple SOCKS4 proxy. Thanks to the help here i got so far to use non-blocking sockets and poll() for my routine. However at this point i seem to have 2 problems:
The outgoing Socket dstSocket doesn't get closed if the incoming Socket rcvSocket gets closed and vice versa. I don't check for this in the loop, but i don't know how. I tried POLLHUP as revents, but that doesn't seem to do the trick. A normal check seems to be whether recv() returns 0, but is that also valid for non-blocking sockets? And if so, how does that work with revents, i can't seem to figure out where i would put this, since if POLLIN | POLLPRI are set it seems to me recv() never should return 0? Also i don't understand what the exact difference is between POLLIN and POLLPRI, seems to me just a check "data is available for reading" in either case?
The proxy seems to work for connections i tested with netcat. However if i use a browser, it says (when i target a website) whether i want to save "binary data". I checked the data in wireshark and what is received from the server is correctly forwarded to the client byte by byte it seems. If anyone maybe has an idea why that could happen with this program it would be nice :)
Attached the relevant code (beware programming newbie):
fds[1].fd = dstSocket;
fds[0].fd = rcvSocket;
fds[1].events = POLLIN | POLLPRI | POLLHUP;
fds[0].events = POLLIN | POLLPRI | POLLHUP;
timer = poll(fds, 2, timeout_msecs); /* i dont use this yet */
fcntl(rcvSocket, F_SETFL, O_NONBLOCK);
fcntl(dstSocket, F_SETFL, O_NONBLOCK);
while (1 == 1)
{
if (fds[0].revents &开发者_开发技巧 POLLIN | POLLPRI)
{
recvMsgSize = recv(rcvSocket, rcvBuffer, RCVBUFSIZE, 0);
if (recvMsgSize > 0) {send(dstSocket, rcvBuffer, recvMsgSize, 0);}
}
if (fds[1].revents & POLLIN | POLLPRI)
{
sndMsgSize = recv(dstSocket, sndBuffer, RCVBUFSIZE, 0);
if (sndMsgSize > 0) { send(rcvSocket, sndBuffer, sndMsgSize, 0);}
}
if ((fds[0].revents & POLLHUP) || (fds[1].revents & POLLHUP))
{
close(rcvSocket);
close(dstSocket);
}
}
recv()
returns 0 on a clean remote shutdown - this is true even for nonblocking sockets. In this case, POLLIN
will be returned - the notification that the remote side has closed the socket is considered a "readable" event.
You shouldn't need to use POLLPRI
for SOCKS / HTTP connections - it indicates TCP "urgent data", which isn't used by those protocols (or indeed, much used at all).
Apart from your direct questions, you need to do more to implement a reliable proxy:
- You need to be calling
poll()
on every loop, not just once. The way you have it written it is busy-looping, which is generally not considered acceptable practise. - You should be setting the disposition of
SIGPIPE
to ignored withsignal(SIGPIPE, SIG_IGN);
. This allows you to gracefully handle write failures. - You should be checking the result of
send()
. Note that it can write less than the amount you requested - in this case, you will have to keep the unsent data buffered, return to thepoll()
and try sending the remaining data again ifPOLLOUT
is raised on the socket. You only want to requestPOLLOUT
if there is unsent data remaining, so you need to make sure.events
is set correctly before everypoll()
call. - You should be checking
errno
ifrecv()
orsend()
returns a value less than 0.EINTR
andEWOULDBLOCK
should be ignored; any other error should be treated as a connection failure. - You should not be closing both directions immediately when one socket closes - you must support asymmetric shutdowns. This means that when you see that
fds[0]
has been closed by the remote end, you should callshutdown(fds[1], SHUT_WR);
, and vice-versa; only when both have been shutdown (or a connection failure has occured) should you callclose()
on both file descriptors and finish up.
精彩评论