Repository navigation
libvncclient: use poll() if available (re-land of #528, repeater regression fixed) - #738
Open
Juern-Univention wants to merge 2 commits into
Open
Juern-Univention wants to merge 2 commits into
Juern-Univention wants to merge 2 commits into
Conversation
select() cannot handle file descriptors >= FD_SETSIZE (1024 on most systems): FD_SET() then writes past the fd_set. The stray write lands in whatever follows it, so the socket is never registered and the wait times out instead of failing loudly -- a client whose socket got a high number cannot connect at all. Use poll() where both the function and its header are available and keep select() as the fallback. Based on 993df68 by Tobias Junghans, which was reverted in 7dd2750 because it broke connecting a LibVNCServer-based server to an RFB repeater. That regression is fixed here, along with two further defects: * sock_wait_for_connected() waited for POLLIN|POLLPRI, while completion of a non-blocking connect() is signalled as writability. Against a peer that stays silent until spoken to -- an UltraVNC repeater in mode 2 reads a 250 byte id before it says anything -- the wait ran into the full rfbMaxClientWait timeout, so the caller gave up and closed the connection before sending that id, which the repeater in turn reported as "Incorrect id" (bk138/droidVNC-NG#109). It waits for POLLOUT now. * WaitForMessage() truncated its microsecond timeout to milliseconds, so any wait shorter than 1 ms became a non-blocking poll() and turned callers that wait briefly into busy loops. The conversion rounds up. * pfd.revents was read even when poll() returned 0 or -1, i.e. when it had not been filled in. It is only inspected on a positive return now, and POLLERR/POLLHUP are handled the way select() behaves: reported as ready, so that the following read()/write() drains what is left and then reports the error. Also let the invalid-socket check apply to both implementations again, and gate the poll() code on LIBVNCSERVER_HAVE_POLL *and* LIBVNCSERVER_HAVE_POLL_H so that a platform which has the symbol but not the header cannot end up calling poll() unprototyped.
Covers rfbConnectToTcpAddr() -> sock_wait_for_connected() against a peer that says nothing until it has been spoken to, which is what broke when poll() was first introduced in 993df68 and what got that commit reverted in 7dd2750: an UltraVNC repeater in mode 2 reads a 250 byte id before it answers, so a connect completion wait that waits for readability instead of writability never fires. The test stands in for the repeater with a plain listening socket on a kernel-picked loopback port and checks that the connection is established without waiting out rfbMaxClientWait, that the id arrives in full and that the ProtocolVersion message follows it. It is UNIX-only since it speaks BSD sockets directly, and carries a ctest timeout so that a regression fails the run rather than stalling it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This re-lands #528 / 993df68, which was reverted in 7dd2750 because it broke
connecting a LibVNCServer-based server to an RFB repeater
(bk138/droidVNC-NG#109). That regression and two further defects in the
original patch are fixed here.
Why
select()cannot handle file descriptors >=FD_SETSIZE(1024 on mostsystems):
FD_SET()writes past thefd_set. It does not fail loudly — in arun with a socket on fd 1035 the stray write landed in the neighbouring
fd_seton the stack, so the socket was never registered and the connect waitsimply timed out:
Note the empty write set. A client whose socket gets a high number cannot
connect at all, which is what large deployments run into.
What was wrong with the original patch
sock_wait_for_connected()waited forPOLLIN|POLLPRI. Completion of anon-blocking
connect()is signalled as writability. Against a peer thatstays silent until spoken to — an UltraVNC repeater in mode 2 reads a 250
byte id before it answers — the wait ran out the full
rfbMaxClientWait, sothe caller closed the connection before sending the id, and the repeater
reported the missing id as
Incorrect id. Now waits forPOLLOUT.WaitForMessage()truncated its microsecond timeout to milliseconds, soany wait below 1 ms became a non-blocking
poll()and turned callers thatwait briefly into busy loops. The conversion now rounds up.
pfd.reventswas read even whenpoll()returned 0 or -1, i.e. when ithad not been filled in. It is now only inspected on a positive return, and
POLLERR/POLLHUPare treated the wayselect()behaves: reported asready, so the following
read()/write()drains what is left and thenreports the error.
Additionally the invalid-socket check applies to both implementations again,
and the
poll()code is gated onLIBVNCSERVER_HAVE_POLLandLIBVNCSERVER_HAVE_POLL_H, so a platform with the symbol but not the headercannot end up calling
poll()unprototyped.Testing
test/repeatertest.c(second commit) covers the regression that caused therevert: it stands in for the repeater with a listening socket on a
kernel-picked loopback port and checks the connection is established without
waiting out
rfbMaxClientWait, that the id arrives in full, and that theProtocolVersion message follows.
WaitForMessage(999us)poll()forced off (select fallback)Also verified:
WriteToRFBServer()of 8 MB over a slow-draining non-blockingsocket takes the
poll(POLLOUT)path 42 times (strace) and delivers everybyte, in both the poll and select builds; both
#ifdefbranches compilewarning-free under
-Wall -Wextra; fullctestsuite passes (5/5).