Skip to content

Commit 5277c0e

Browse files
committed
libvncserver: add listener socket rebinding
1 parent 5e972fc commit 5277c0e

3 files changed

Lines changed: 118 additions & 1 deletion

File tree

include/rfb/rfb.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,10 @@ typedef struct _rfbScreenInfo
382382
#endif
383383
/* Timeout value for select() calls, mainly used for multithreaded servers. */
384384
int select_timeout_usec;
385+
/** Set by rfbRequestListenRebind() to ask the listener thread to re-create
386+
* its listening sockets from the current listenInterface/listen6Interface/
387+
* port/ipv6port values on its next loop iteration. Cleared by the thread. */
388+
rfbBool rebindListenSockets;
385389
} rfbScreenInfo, *rfbScreenInfoPtr;
386390

387391

@@ -784,6 +788,15 @@ extern int rfbMaxClientWait;
784788

785789
extern void rfbInitSockets(rfbScreenInfoPtr rfbScreen);
786790
extern void rfbShutdownSockets(rfbScreenInfoPtr rfbScreen);
791+
/** Close and re-create the TCP/TCP6 listening sockets from the screen's current
792+
* listenInterface/listen6Interface/port/ipv6port. Must run on the same thread
793+
* that select()s on those sockets; off-thread callers use rfbRequestListenRebind().
794+
* Returns TRUE if at least one family is now listening. */
795+
extern rfbBool rfbRebindListenSockets(rfbScreenInfoPtr rfbScreen);
796+
/** Thread-safe request to rebind the listening sockets: the actual swap is done
797+
* by the background listener thread on its next iteration. Caller must have set
798+
* the desired listenInterface/listen6Interface/port/ipv6port beforehand. */
799+
extern void rfbRequestListenRebind(rfbScreenInfoPtr rfbScreen);
787800
extern void rfbDisconnectUDPSock(rfbScreenInfoPtr rfbScreen);
788801
extern void rfbCloseClient(rfbClientPtr cl);
789802
extern int rfbReadExact(rfbClientPtr cl, char *buf, int len);

src/libvncserver/main.c

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,12 @@ listenerRun(void *data)
661661
/* Reset the pipe */
662662
char buf;
663663
while (read(screen->pipe_notify_listener_thread[0], &buf, sizeof(buf)) == sizeof(buf));
664+
/* A rebind request is serviced here, on the listener thread, so the
665+
close()/socket() swap cannot race the select() above. */
666+
if (screen->rebindListenSockets) {
667+
screen->rebindListenSockets = FALSE;
668+
rfbRebindListenSockets(screen);
669+
}
664670
/* Go on with loop */
665671
continue;
666672
}
@@ -686,7 +692,28 @@ listenerRun(void *data)
686692

687693
#endif
688694

689-
void
695+
void
696+
rfbRequestListenRebind(rfbScreenInfoPtr screen)
697+
{
698+
/* Flag the request; the listener thread picks it up after being woken. */
699+
screen->rebindListenSockets = TRUE;
700+
701+
#if defined(LIBVNCSERVER_HAVE_LIBPTHREAD) && !defined(WIN32)
702+
if (screen->backgroundLoop && screen->pipe_notify_listener_thread[1] != -1) {
703+
const char buf = 'r';
704+
if (write(screen->pipe_notify_listener_thread[1], &buf, sizeof(buf)) < 0)
705+
rfbLogPerror("rfbRequestListenRebind: write to notify pipe");
706+
return;
707+
}
708+
#endif
709+
710+
/* No background listener thread: do the swap inline. Safe because the
711+
single-threaded event loop is not concurrently in select() here. */
712+
screen->rebindListenSockets = FALSE;
713+
rfbRebindListenSockets(screen);
714+
}
715+
716+
void
690717
rfbStartOnHoldClient(rfbClientPtr cl)
691718
{
692719
cl->onHold = FALSE;

src/libvncserver/sockets.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,83 @@ void rfbShutdownSockets(rfbScreenInfoPtr rfbScreen)
336336
#endif
337337
}
338338

339+
/*
340+
* rfbRebindListenSockets closes the current TCP/TCP6 listening sockets and
341+
* re-creates them from the screen's current listenInterface/listen6Interface
342+
* and port/ipv6port. This is used to follow an interface whose IP address
343+
* changed (e.g. after the bound network interface went down and back up).
344+
*
345+
* It must be called on the thread that select()s on the listening sockets so
346+
* that the close()/socket() swap cannot race a concurrent select(): in the
347+
* threaded (background loop) case that is the listener thread, which invokes
348+
* this from listenerRun() in response to rfbRequestListenRebind(). Already-
349+
* connected clients keep their own sockets and are unaffected. The HTTP
350+
* listeners are bound to the same interface address, so they are re-created too.
351+
*/
352+
rfbBool
353+
rfbRebindListenSockets(rfbScreenInfoPtr rfbScreen)
354+
{
355+
in_addr_t iface = rfbScreen->listenInterface;
356+
357+
/* This deliberately only handles the plain TCP/TCP6 listeners; inetd and
358+
autoPort setups have no stable address to rebind to. */
359+
if (rfbScreen->inetdSock != RFB_INVALID_SOCKET || rfbScreen->autoPort)
360+
return FALSE;
361+
362+
/* tear down the existing listeners; rfbCloseSocket() also resets the fd to
363+
RFB_INVALID_SOCKET, the outer if only skips the FD_CLR when there is none */
364+
if (rfbScreen->listenSock != RFB_INVALID_SOCKET) {
365+
FD_CLR(rfbScreen->listenSock, &rfbScreen->allFds);
366+
rfbCloseSocket(rfbScreen->listenSock);
367+
}
368+
#ifdef LIBVNCSERVER_IPv6
369+
if (rfbScreen->listen6Sock != RFB_INVALID_SOCKET) {
370+
FD_CLR(rfbScreen->listen6Sock, &rfbScreen->allFds);
371+
rfbCloseSocket(rfbScreen->listen6Sock);
372+
}
373+
#endif
374+
375+
/* re-create from the current interface addresses */
376+
if (rfbScreen->port > 0) {
377+
if ((rfbScreen->listenSock = rfbListenOnTCPPort(rfbScreen->port, iface)) == RFB_INVALID_SOCKET) {
378+
rfbLogPerror("rfbRebindListenSockets: ListenOnTCPPort");
379+
} else {
380+
rfbLog("rfbRebindListenSockets: listening for VNC connections on TCP port %d\n", rfbScreen->port);
381+
FD_SET(rfbScreen->listenSock, &rfbScreen->allFds);
382+
rfbScreen->maxFd = rfbMax((int)rfbScreen->listenSock, rfbScreen->maxFd);
383+
}
384+
}
385+
386+
#ifdef LIBVNCSERVER_IPv6
387+
if (rfbScreen->ipv6port > 0) {
388+
if ((rfbScreen->listen6Sock = rfbListenOnTCP6Port(rfbScreen->ipv6port, rfbScreen->listen6Interface)) == RFB_INVALID_SOCKET) {
389+
/* rfbListenOnTCP6Port has its own detailed error printout */
390+
} else {
391+
rfbLog("rfbRebindListenSockets: listening for VNC connections on TCP6 port %d\n", rfbScreen->ipv6port);
392+
FD_SET(rfbScreen->listen6Sock, &rfbScreen->allFds);
393+
rfbScreen->maxFd = rfbMax((int)rfbScreen->listen6Sock, rfbScreen->maxFd);
394+
}
395+
}
396+
#endif
397+
398+
/* The HTTP listeners are bound to the same interface address and go stale the
399+
same way, so re-create them too. rfbHttpCheckFds() runs on this same listener
400+
thread, so closing/reopening here cannot race it. Only relevant with an httpDir
401+
configured; reset httpInitDone so rfbHttpInitSockets() actually runs again. This
402+
is best-effort and does not affect the RFB-based return value below. */
403+
if (rfbScreen->httpDir) {
404+
rfbHttpShutdownSockets(rfbScreen);
405+
rfbScreen->httpInitDone = FALSE;
406+
rfbHttpInitSockets(rfbScreen);
407+
}
408+
409+
return (rfbScreen->listenSock != RFB_INVALID_SOCKET
410+
#ifdef LIBVNCSERVER_IPv6
411+
|| rfbScreen->listen6Sock != RFB_INVALID_SOCKET
412+
#endif
413+
) ? TRUE : FALSE;
414+
}
415+
339416
/*
340417
* rfbCheckFds is called from ProcessInputEvents to check for input on the RFB
341418
* socket(s). If there is input to process, the appropriate function in the

0 commit comments

Comments
 (0)