From 83a7c713a99a65f910fabab1bb95428762f569fb Mon Sep 17 00:00:00 2001 From: Christian Beier Date: Mon, 20 Feb 2012 15:52:19 +0100 Subject: IPv6 support for LibVNCServer, part one: accept IPv4 and IPv6 connections. This uses a separate-socket approach since there are systems that do not support dual binding sockets under *any* circumstances, for instance OpenBSD. Using separate sockets for IPv4 and IPv6 is thus more portable than having a v6 socket handle v4 connections as well. Signed-off-by: Christian Beier --- libvncserver/sockets.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 159 insertions(+), 7 deletions(-) (limited to 'libvncserver/sockets.c') diff --git a/libvncserver/sockets.c b/libvncserver/sockets.c index 415f712..132a721 100644 --- a/libvncserver/sockets.c +++ b/libvncserver/sockets.c @@ -137,6 +137,8 @@ rfbInitSockets(rfbScreenInfoPtr rfbScreen) if(rfbScreen->autoPort) { int i; + FD_ZERO(&(rfbScreen->allFds)); + rfbLog("Autoprobing TCP port \n"); for (i = 5900; i < 6000; i++) { if ((rfbScreen->listenSock = rfbListenOnTCPPort(i, iface)) >= 0) { @@ -150,22 +152,52 @@ rfbInitSockets(rfbScreenInfoPtr rfbScreen) return; } - rfbLog("Autoprobing selected port %d\n", rfbScreen->port); - FD_ZERO(&(rfbScreen->allFds)); + rfbLog("Autoprobing selected TCP port %d\n", rfbScreen->port); FD_SET(rfbScreen->listenSock, &(rfbScreen->allFds)); rfbScreen->maxFd = rfbScreen->listenSock; + +#ifdef LIBVNCSERVER_IPv6 + rfbLog("Autoprobing TCP6 port \n"); + for (i = 5900; i < 6000; i++) { + if ((rfbScreen->listen6Sock = rfbListenOnTCP6Port(i, rfbScreen->listen6Interface)) >= 0) { + rfbScreen->ipv6port = i; + break; + } + } + + if (i >= 6000) { + rfbLogPerror("Failure autoprobing"); + return; + } + + rfbLog("Autoprobing selected TCP6 port %d\n", rfbScreen->ipv6port); + FD_SET(rfbScreen->listen6Sock, &(rfbScreen->allFds)); + rfbScreen->maxFd = max((int)rfbScreen->listen6Sock,rfbScreen->maxFd); +#endif } else if(rfbScreen->port>0) { - rfbLog("Listening for VNC connections on TCP port %d\n", rfbScreen->port); + FD_ZERO(&(rfbScreen->allFds)); if ((rfbScreen->listenSock = rfbListenOnTCPPort(rfbScreen->port, iface)) < 0) { rfbLogPerror("ListenOnTCPPort"); return; } - - FD_ZERO(&(rfbScreen->allFds)); + rfbLog("Listening for VNC connections on TCP port %d\n", rfbScreen->port); + FD_SET(rfbScreen->listenSock, &(rfbScreen->allFds)); rfbScreen->maxFd = rfbScreen->listenSock; + +#ifdef LIBVNCSERVER_IPv6 + if ((rfbScreen->listen6Sock = rfbListenOnTCP6Port(rfbScreen->ipv6port, rfbScreen->listen6Interface)) < 0) { + /* ListenOnTCP6Port has its own detailed error printout */ + return; + } + rfbLog("Listening for VNC connections on TCP6 port %d\n", rfbScreen->ipv6port); + + FD_SET(rfbScreen->listen6Sock, &(rfbScreen->allFds)); + rfbScreen->maxFd = max((int)rfbScreen->listen6Sock,rfbScreen->maxFd); +#endif + } if (rfbScreen->udpPort != 0) { @@ -175,6 +207,8 @@ rfbInitSockets(rfbScreenInfoPtr rfbScreen) rfbLogPerror("ListenOnUDPPort"); return; } + rfbLog("Listening for VNC connections on TCP port %d\n", rfbScreen->port); + FD_SET(rfbScreen->udpSock, &(rfbScreen->allFds)); rfbScreen->maxFd = max((int)rfbScreen->udpSock,rfbScreen->maxFd); } @@ -199,6 +233,12 @@ void rfbShutdownSockets(rfbScreenInfoPtr rfbScreen) rfbScreen->listenSock=-1; } + if(rfbScreen->listen6Sock>-1) { + closesocket(rfbScreen->listen6Sock); + FD_CLR(rfbScreen->listen6Sock,&rfbScreen->allFds); + rfbScreen->listen6Sock=-1; + } + if(rfbScreen->udpSock>-1) { closesocket(rfbScreen->udpSock); FD_CLR(rfbScreen->udpSock,&rfbScreen->allFds); @@ -270,6 +310,16 @@ rfbCheckFds(rfbScreenInfoPtr rfbScreen,long usec) return result; } + if (rfbScreen->listen6Sock != -1 && FD_ISSET(rfbScreen->listen6Sock, &fds)) { + + if (!rfbProcessNewConnection(rfbScreen)) + return -1; + + FD_CLR(rfbScreen->listen6Sock, &fds); + if (--nfds == 0) + return result; + } + if ((rfbScreen->udpSock != -1) && FD_ISSET(rfbScreen->udpSock, &fds)) { if(!rfbScreen->udpClient) rfbNewUDPClient(rfbScreen); @@ -330,10 +380,29 @@ rfbProcessNewConnection(rfbScreenInfoPtr rfbScreen) { const int one = 1; int sock = -1; - struct sockaddr_in addr; + struct sockaddr_storage addr; socklen_t addrlen = sizeof(addr); + fd_set listen_fds; + int chosen_listen_sock = -1; + + /* Do another select() call to find out which listen socket + has an incoming connection pending. We know that at least + one of them has, so this should not block for too long! */ + FD_ZERO(&listen_fds); + if(rfbScreen->listenSock >= 0) + FD_SET(rfbScreen->listenSock, &listen_fds); + if(rfbScreen->listen6Sock >= 0) + FD_SET(rfbScreen->listen6Sock, &listen_fds); + if (select(rfbScreen->maxFd+1, &listen_fds, NULL, NULL, NULL) == -1) { + rfbLogPerror("rfbProcessNewConnection: error in select"); + return FALSE; + } + if (FD_ISSET(rfbScreen->listenSock, &listen_fds)) + chosen_listen_sock = rfbScreen->listenSock; + if (FD_ISSET(rfbScreen->listen6Sock, &listen_fds)) + chosen_listen_sock = rfbScreen->listen6Sock; - if ((sock = accept(rfbScreen->listenSock, + if ((sock = accept(chosen_listen_sock, (struct sockaddr *)&addr, &addrlen)) < 0) { rfbLogPerror("rfbCheckFds: accept"); return FALSE; @@ -361,7 +430,15 @@ rfbProcessNewConnection(rfbScreenInfoPtr rfbScreen) } #endif +#ifdef LIBVNCSERVER_IPv6 + char host[1024]; + if(getnameinfo((struct sockaddr*)&addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0) { + rfbLogPerror("rfbProcessNewConnection: error in getnameinfo"); + } + rfbLog("Got connection from client %s\n", host); +#else rfbLog("Got connection from client %s\n", inet_ntoa(addr.sin_addr)); +#endif rfbNewClient(rfbScreen,sock); @@ -774,6 +851,81 @@ rfbListenOnTCPPort(int port, return sock; } + +int +rfbListenOnTCP6Port(int port, + const char* iface) +{ +#ifndef LIBVNCSERVER_IPv6 + rfbLogPerror("This LibVNCServer does not have IPv6 support"); + return -1; +#else + int sock; + int one = 1; + int rv; + struct addrinfo hints, *servinfo, *p; + char port_str[8]; + + snprintf(port_str, 8, "%d", port); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; /* fill in wildcard address if iface == NULL */ + + if ((rv = getaddrinfo(iface, port_str, &hints, &servinfo)) != 0) { + rfbErr("rfbListenOnTCP6Port error in getaddrinfo: %s\n", gai_strerror(rv)); + return -1; + } + + /* loop through all the results and bind to the first we can */ + for(p = servinfo; p != NULL; p = p->ai_next) { + if ((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) { + continue; + } + +#ifdef IPV6_V6ONLY + /* we have seperate IPv4 and IPv6 sockets since some OS's do not support dual binding */ + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&one, sizeof(one)) < 0) { + rfbLogPerror("rfbListenOnTCP6Port error in setsockopt IPV6_V6ONLY"); + closesocket(sock); + return -1; + } +#endif + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) < 0) { + rfbLogPerror("rfbListenOnTCP6Port: error in setsockopt SO_REUSEADDR"); + closesocket(sock); + return -1; + } + + if (bind(sock, p->ai_addr, p->ai_addrlen) < 0) { + closesocket(sock); + continue; + } + + break; + } + + if (p == NULL) { + rfbLogPerror("rfbListenOnTCP6Port: error in bind IPv6 socket"); + return -1; + } + + /* all done with this structure now */ + freeaddrinfo(servinfo); + + if (listen(sock, 32) < 0) { + rfbLogPerror("rfbListenOnTCP6Port: error in listen on IPv6 socket"); + closesocket(sock); + return -1; + } + + return sock; +#endif +} + + int rfbConnectToTcpAddr(char *host, int port) -- cgit v1.2.1