Merge pull request #249 from derfian/reconnect-related-fixes

* When connecting to a Windows 2008 Server, pressing Cancel or hitting Escape while entering credentials (on the server) would lead to a reconnect if there was a resize pending.

* When connecting to a RDS farm name (round robin DNS entry as per MS recommendations), reconnecting could connect you to a different server, leading to a login screen rather than your session.

* rdesktop never had any logging that told you which address you were connecting to.
This commit is contained in:
Karl Mikaelsson 2018-03-23 09:38:17 +01:00 committed by GitHub
commit 8346fea6cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 156 additions and 73 deletions

View File

@ -1395,55 +1395,56 @@ main(int argc, char *argv[])
deactivated = False;
g_reconnect_loop = False;
ext_disc_reason = 0;
rdp_main_loop(&deactivated, &ext_disc_reason);
tcp_run_ui(False);
logger(Core, Verbose, "Disconnecting...");
rdp_disconnect();
/* If error info is set we do want to exit rdesktop
connect loop. We do this by clearing flags that
triggers a reconnect that could be set elsewere */
if (ext_disc_reason != 0)
if (deactivated)
{
g_redirect = False;
g_network_error = False;
g_pending_resize = False;
/* Server disconnected while deactivated */
logger(Core, Notice, "Disconnecting...");
break;
}
else
{
/* Unexpected disconnect or rdesktop-initiated loop exit */
if (g_redirect)
continue;
/* handle network error and start autoreconnect */
if (g_network_error && !deactivated)
if (g_user_quit)
{
/* User closed window */
break;
}
else if (g_redirect)
{
/* see beginning of loop */
}
else if (g_network_error)
{
logger(Core, Notice,
"Disconnected due to network error, retrying to reconnect for %d minutes.",
RECONNECT_TIMEOUT / 60);
g_network_error = False;
g_reconnect_loop = True;
continue;
}
else if (g_pending_resize)
{
/* Prepare to re-create rdesktop window */
ui_seamless_end();
ui_destroy_window();
/* Enter a reconnect loop if we have a pending resize request */
if (g_pending_resize)
{
logger(Core, Verbose, "Resize reconnect loop triggered, new size %dx%d",
g_requested_session_width, g_requested_session_height);
g_pending_resize = False;
g_reconnect_loop = True;
}
continue;
}
/* exit main reconnect loop */
break;
}
ui_seamless_end();
ui_destroy_window();
cache_save_state();
ui_deinit();

118
tcp.c
View File

@ -58,6 +58,13 @@
#define STREAM_COUNT 1
#endif
#ifdef IPv6
static struct addrinfo *g_server_address = NULL;
#else
struct sockaddr_in *g_server_address = NULL;
#endif
static char *g_last_server_name = NULL;
static RD_BOOL g_ssl_initialized = False;
static SSL *g_ssl = NULL;
static SSL_CTX *g_ssl_ctx = NULL;
@ -416,20 +423,42 @@ tcp_tls_get_server_pubkey(STREAM s)
return (s->size != 0);
}
/* Establish a connection on the TCP layer */
/* Helper function to determine if rdesktop should resolve hostnames again or not */
static RD_BOOL
tcp_connect_resolve_hostname(const char *server)
{
return (g_server_address == NULL ||
g_last_server_name == NULL ||
strcmp(g_last_server_name, server) != 0);
}
/* Establish a connection on the TCP layer
This function tries to avoid resolving any server address twice. The
official Windows 2008 documentation states that the windows farm name
should be a round-robin DNS entry containing all the terminal servers
in the farm. When connected to the farm address, if we look up the
address again when reconnecting (for any reason) we risk reconnecting
to a different server in the farm.
*/
RD_BOOL
tcp_connect(char *server)
{
socklen_t option_len;
uint32 option_value;
int i;
char buf[NI_MAXHOST];
#ifdef IPv6
int n;
struct addrinfo hints, *res, *ressave;
struct addrinfo hints, *res, *addr;
struct sockaddr *oldaddr;
char tcp_port_rdp_s[10];
if (tcp_connect_resolve_hostname(server))
{
snprintf(tcp_port_rdp_s, 10, "%d", g_tcp_port_rdp);
memset(&hints, 0, sizeof(struct addrinfo));
@ -441,22 +470,38 @@ tcp_connect(char *server)
logger(Core, Error, "tcp_connect(), getaddrinfo() failed: %s", gai_strerror(n));
return False;
}
}
else
{
res = g_server_address;
}
ressave = res;
g_sock = -1;
while (res)
for (addr = res; addr != NULL; addr = addr->ai_next)
{
g_sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (!(g_sock < 0))
g_sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (g_sock < 0)
{
if (connect(g_sock, res->ai_addr, res->ai_addrlen) == 0)
logger(Core, Debug, "tcp_connect(), socket() failed: %s", TCP_STRERROR);
continue;
}
n = getnameinfo(addr->ai_addr, addr->ai_addrlen, buf, sizeof(buf), NULL, 0, NI_NUMERICHOST);
if (n != 0)
{
logger(Core, Error, "tcp_connect(), getnameinfo() failed: %s", gai_strerror(n));
return False;
}
logger(Core, Debug, "tcp_connect(), trying %s (%s)", server, buf);
if (connect(g_sock, addr->ai_addr, addr->ai_addrlen) == 0)
break;
TCP_CLOSE(g_sock);
g_sock = -1;
}
res = res->ai_next;
}
freeaddrinfo(ressave);
if (g_sock == -1)
{
@ -464,20 +509,51 @@ tcp_connect(char *server)
return False;
}
#else /* no IPv6 support */
/* Save server address for later use, if we haven't already. */
struct hostent *nslookup;
struct sockaddr_in servaddr;
if (g_server_address == NULL)
{
g_server_address = xmalloc(sizeof(struct addrinfo));
g_server_address->ai_addr = xmalloc(sizeof(struct sockaddr_storage));
}
if (g_server_address != addr)
{
/* don't overwrite ptr to allocated sockaddr */
oldaddr = g_server_address->ai_addr;
memcpy(g_server_address, addr, sizeof(struct addrinfo));
g_server_address->ai_addr = oldaddr;
memcpy(g_server_address->ai_addr, addr->ai_addr, addr->ai_addrlen);
g_server_address->ai_canonname = NULL;
g_server_address->ai_next = NULL;
freeaddrinfo(res);
}
#else /* no IPv6 support */
struct hostent *nslookup = NULL;
if (tcp_connect_resolve_hostname(server))
{
if (g_server_address != NULL)
xfree(g_server_address);
g_server_address = xmalloc(sizeof(struct sockaddr_in));
g_server_address->sin_family = AF_INET;
g_server_address->sin_port = htons((uint16) g_tcp_port_rdp);
if ((nslookup = gethostbyname(server)) != NULL)
{
memcpy(&servaddr.sin_addr, nslookup->h_addr, sizeof(servaddr.sin_addr));
memcpy(&g_server_address->sin_addr, nslookup->h_addr,
sizeof(g_server_address->sin_addr));
}
else if ((servaddr.sin_addr.s_addr = inet_addr(server)) == INADDR_NONE)
else if ((g_server_address->sin_addr.s_addr = inet_addr(server)) == INADDR_NONE)
{
logger(Core, Error, "tcp_connect(), unable to resolve host '%s'", server);
return False;
}
}
if ((g_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
@ -485,10 +561,12 @@ tcp_connect(char *server)
return False;
}
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons((uint16) g_tcp_port_rdp);
logger(Core, Debug, "tcp_connect(), trying %s (%s)",
server, inet_ntop(g_server_address->sin_family,
&g_server_address->sin_addr,
buf, sizeof(buf)));
if (connect(g_sock, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) < 0)
if (connect(g_sock, (struct sockaddr *) g_server_address, sizeof(struct sockaddr)) < 0)
{
if (!g_reconnect_loop)
logger(Core, Error, "tcp_connect(), connect() failed: %s", TCP_STRERROR);
@ -524,6 +602,10 @@ tcp_connect(char *server)
g_out[i].data = (uint8 *) xmalloc(g_out[i].size);
}
/* After successful connect: update the last server name */
if (g_last_server_name)
xfree(g_last_server_name);
g_last_server_name = strdup(server);
return True;
}