From 106ae2cd43a2f02770ad752c085125d8ddd5fd7e Mon Sep 17 00:00:00 2001 From: MichaelSweden Date: Thu, 30 Mar 2017 09:56:38 +0200 Subject: [PATCH] Fix to handle OS disabled IPv6, issue #714. - Changes made only in the os_calls.c file. - Exported functions changed: g_tcp_bind g_tcp_bind_address g_tcp_connect - Support three network configurations: 1) Normal network, with IPv6 2) Partly disabled IPv6 via sysctl.conf 3) Total disabled IPv6 via grub --- common/os_calls.c | 352 ++++++++++++++++++++++++++++++---------------- 1 file changed, 228 insertions(+), 124 deletions(-) diff --git a/common/os_calls.c b/common/os_calls.c index 20cc6c23..ca8ab42f 100644 --- a/common/os_calls.c +++ b/common/os_calls.c @@ -727,8 +727,69 @@ g_sck_close(int sck) #endif } +#if defined(XRDP_ENABLE_IPV6) /*****************************************************************************/ -/* returns error, zero is good */ +/* Helper function for g_tcp_connect. */ +static int +connect_loopback(int sck, const char *port) +{ + struct sockaddr_in6 sa; + struct sockaddr_in s; + int res; + + // First IPv6 + g_memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_addr = in6addr_loopback; // IPv6 ::1 + sa.sin6_port = htons((tui16)atoi(port)); + res = connect(sck, (struct sockaddr*)&sa, sizeof(sa)); + if (res == -1 && errno == EINPROGRESS) + { + return -1; + } + if (res == 0 || (res == -1 && errno == EISCONN)) + { + return 0; + } + + // else IPv4 + g_memset(&sa, 0, sizeof(s)); + s.sin_family = AF_INET; + s.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // IPv4 127.0.0.1 + s.sin_port = htons((tui16)atoi(port)); + res = connect(sck, (struct sockaddr*)&s, sizeof(s)); + if (res == -1 && errno == EINPROGRESS) + { + return -1; + } + if (res == 0 || (res == -1 && errno == EISCONN)) + { + return 0; + } + + // else IPv6 with IPv4 address + g_memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + inet_pton(AF_INET6, "::FFFF:127.0.0.1", &sa.sin6_addr); + sa.sin6_port = htons((tui16)atoi(port)); + res = connect(sck, (struct sockaddr*)&sa, sizeof(sa)); + if (res == -1 && errno == EINPROGRESS) + { + return -1; + } + if (res == 0 || (res == -1 && errno == EISCONN)) + { + return 0; + } + + return -1; +} +#endif + +/*****************************************************************************/ +/* returns error, zero is good */ +/* The connection might get to be in progress, if so -1 is returned. */ +/* The caller needs to call again to check if succeed. */ #if defined(XRDP_ENABLE_IPV6) int g_tcp_connect(int sck, const char *address, const char *port) @@ -738,22 +799,15 @@ g_tcp_connect(int sck, const char *address, const char *port) struct addrinfo *h = (struct addrinfo *)NULL; struct addrinfo *rp = (struct addrinfo *)NULL; - /* initialize (zero out) local variables: */ g_memset(&p, 0, sizeof(struct addrinfo)); - /* in IPv6-enabled environments, set the AI_V4MAPPED - * flag in ai_flags and specify ai_family=AF_INET6 in - * order to ensure that getaddrinfo() returns any - * available IPv4-mapped addresses in case the target - * host does not have a true IPv6 address: - */ p.ai_socktype = SOCK_STREAM; p.ai_protocol = IPPROTO_TCP; p.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED; p.ai_family = AF_INET6; if (g_strcmp(address, "127.0.0.1") == 0) { - res = getaddrinfo("::1", port, &p, &h); + return connect_loopback(sck, port); } else { @@ -761,8 +815,8 @@ g_tcp_connect(int sck, const char *address, const char *port) } if (res != 0) { - log_message(LOG_LEVEL_ERROR, "g_tcp_connect: getaddrinfo() failed: %s", - gai_strerror(res)); + log_message(LOG_LEVEL_ERROR, "g_tcp_connect(%d, %s, %s): getaddrinfo() failed: %s", + sck, address, port, gai_strerror(res)); } if (res > -1) { @@ -770,23 +824,22 @@ g_tcp_connect(int sck, const char *address, const char *port) { for (rp = h; rp != NULL; rp = rp->ai_next) { - rp = h; res = connect(sck, (struct sockaddr *)(rp->ai_addr), rp->ai_addrlen); - if (res != -1) + if (res == -1 && errno == EINPROGRESS) + { + break; /* Return -1 */ + } + if (res == 0 || (res == -1 && errno == EISCONN)) { + res = 0; break; /* Success */ } } + freeaddrinfo(h); } } - /* Mac OSX connect() returns -1 for already established connections */ - if (res == -1 && errno == EISCONN) - { - res = 0; - } - return res; } #else @@ -871,113 +924,39 @@ g_sck_set_non_blocking(int sck) #if defined(XRDP_ENABLE_IPV6) /*****************************************************************************/ -/* return boolean */ -static int -address_match(const char *address, struct addrinfo *j) +/* returns error, zero is good */ +int +g_tcp_bind(int sck, const char *port) { - struct sockaddr_in *ipv4_in; - struct sockaddr_in6 *ipv6_in; + struct sockaddr_in6 sa; + struct sockaddr_in s; + int errno6; - if (address == 0) + // First IPv6 + g_memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_addr = in6addr_any; // IPv6 :: + sa.sin6_port = htons((tui16)atoi(port)); + if (bind(sck, (struct sockaddr*)&sa, sizeof(sa)) == 0) { - return 1; - } - if (address[0] == 0) - { - return 1; - } - if (g_strcmp(address, "0.0.0.0") == 0) - { - return 1; - } - if ((g_strcmp(address, "127.0.0.1") == 0) || - (g_strcmp(address, "::1") == 0) || - (g_strcmp(address, "localhost") == 0)) - { - if (j->ai_addr != 0) - { - if (j->ai_addr->sa_family == AF_INET) - { - ipv4_in = (struct sockaddr_in *) (j->ai_addr); - if (inet_pton(AF_INET, "127.0.0.1", &(ipv4_in->sin_addr))) - { - return 1; - } - } - if (j->ai_addr->sa_family == AF_INET6) - { - ipv6_in = (struct sockaddr_in6 *) (j->ai_addr); - if (inet_pton(AF_INET6, "::1", &(ipv6_in->sin6_addr))) - { - return 1; - } - } - } - } - if (j->ai_addr != 0) - { - if (j->ai_addr->sa_family == AF_INET) - { - ipv4_in = (struct sockaddr_in *) (j->ai_addr); - if (inet_pton(AF_INET, address, &(ipv4_in->sin_addr))) - { - return 1; - } - } - if (j->ai_addr->sa_family == AF_INET6) - { - ipv6_in = (struct sockaddr_in6 *) (j->ai_addr); - if (inet_pton(AF_INET6, address, &(ipv6_in->sin6_addr))) - { - return 1; - } - } + return 0; } - return 0; -} -#endif - -#if defined(XRDP_ENABLE_IPV6) -/*****************************************************************************/ -/* returns error, zero is good */ -static int -g_tcp_bind_flags(int sck, const char *port, const char *address, int flags) -{ - int res; - int error; - struct addrinfo hints; - struct addrinfo *i; + errno6 = errno; - res = -1; - g_memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_flags = flags; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - error = getaddrinfo(NULL, port, &hints, &i); - if (error == 0) + // else IPv4 + g_memset(&sa, 0, sizeof(s)); + s.sin_family = AF_INET; + s.sin_addr.s_addr = htonl(INADDR_ANY); // IPv4 0.0.0.0 + s.sin_port = htons((tui16)atoi(port)); + if (bind(sck, (struct sockaddr*)&s, sizeof(s)) == 0) { - while ((i != 0) && (res < 0)) - { - if (address_match(address, i)) - { - res = bind(sck, i->ai_addr, i->ai_addrlen); - } - i = i->ai_next; - } + return 0; } - return res; -} - -/*****************************************************************************/ -/* returns error, zero is good */ -int -g_tcp_bind(int sck, const char *port) -{ - int flags; - flags = AI_ADDRCONFIG | AI_PASSIVE; - return g_tcp_bind_flags(sck, port, 0, flags); + log_message(LOG_LEVEL_ERROR, "g_tcp_bind(%d, %s) failed " + "bind IPv6 (errno=%d) and IPv4 (errno=%d).", + sck, port, errno6, errno); + return -1; } #else int @@ -1013,14 +992,139 @@ g_sck_local_bind(int sck, const char *port) #if defined(XRDP_ENABLE_IPV6) /*****************************************************************************/ -/* returns error, zero is good */ +/* Helper function for g_tcp_bind_address. */ +static int +bind_loopback(int sck, const char *port) +{ + struct sockaddr_in6 sa; + struct sockaddr_in s; + int errno6; + int errno4; + + // First IPv6 + g_memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_addr = in6addr_loopback; // IPv6 ::1 + sa.sin6_port = htons((tui16)atoi(port)); + if (bind(sck, (struct sockaddr*)&sa, sizeof(sa)) == 0) + { + return 0; + } + errno6 = errno; + + // else IPv4 + g_memset(&sa, 0, sizeof(s)); + s.sin_family = AF_INET; + s.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // IPv4 127.0.0.1 + s.sin_port = htons((tui16)atoi(port)); + if (bind(sck, (struct sockaddr*)&s, sizeof(s)) == 0) + { + return 0; + } + errno4 = errno; + + // else IPv6 with IPv4 address + g_memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + inet_pton(AF_INET6, "::FFFF:127.0.0.1", &sa.sin6_addr); + sa.sin6_port = htons((tui16)atoi(port)); + if (bind(sck, (struct sockaddr*)&sa, sizeof(sa)) == 0) + { + return 0; + } + + log_message(LOG_LEVEL_ERROR, "bind_loopback(%d, %s) failed; " + "IPv6 ::1 (errno=%d), IPv4 127.0.0.1 (errno=%d) and IPv6 ::FFFF:127.0.0.1 (errno=%d).", + sck, port, errno6, errno4, errno); + return -1; +} + +/*****************************************************************************/ +/* Helper function for g_tcp_bind_address. */ +/* Returns error, zero is good. */ +static int +getaddrinfo_bind(int sck, const char *port, const char *address) +{ + int res; + int error; + struct addrinfo hints; + struct addrinfo *list; + struct addrinfo *i; + + res = -1; + g_memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_flags = 0; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + error = getaddrinfo(address, port, &hints, &list); + if (error == 0) + { + i = list; + while ((i != 0) && (res < 0)) + { + res = bind(sck, i->ai_addr, i->ai_addrlen); + i = i->ai_next; + } + freeaddrinfo(list); + } + else + { + log_message(LOG_LEVEL_ERROR, "getaddrinfo error: %s", gai_strerror(error)); + return -1; + } + return res; +} + +/*****************************************************************************/ +/* Binds a socket to a port. If no specified address the port will be bind */ +/* to 'any', i.e. available on all network. */ +/* For bind to local host, see valid address strings below. */ +/* Returns error, zero is good. */ int g_tcp_bind_address(int sck, const char *port, const char *address) { - int flags; + int res; + + if ((address == 0) || + (address[0] == 0) || + (g_strcmp(address, "0.0.0.0") == 0) || + (g_strcmp(address, "::") == 0)) + { + return g_tcp_bind(sck, port); + } + + if ((g_strcmp(address, "127.0.0.1") == 0) || + (g_strcmp(address, "::1") == 0) || + (g_strcmp(address, "localhost") == 0)) + { + return bind_loopback(sck, port); + } - flags = AI_ADDRCONFIG | AI_PASSIVE; - return g_tcp_bind_flags(sck, port, address, flags); + // Let getaddrinfo translate the address string... + // IPv4: ddd.ddd.ddd.ddd + // IPv6: x:x:x:x:x:x:x:x%, or x::x:x:x:x% + res = getaddrinfo_bind(sck, port, address); + if (res != 0) + { + // If fail and it is an IPv4 address, try with the mapped address + struct in_addr a; + if ((inet_aton(address, &a) == 1) && (strlen(address) <= 15)) + { + char sz[7+15+1]; + sprintf(sz, "::FFFF:%s", address); + res = getaddrinfo_bind(sck, port, sz); + if (res == 0) + { + return 0; + } + } + + log_message(LOG_LEVEL_ERROR, "g_tcp_bind_address(%d, %s, %s) Failed!", + sck, port, address); + return -1; + } + return 0; } #else int @@ -3381,7 +3485,7 @@ g_save_to_bmp(const char* filename, char* data, int stride_bytes, int file_stride_bytes; char* line; char* line_ptr; - + if ((depth == 24) && (bits_per_pixel == 32)) { } @@ -3407,7 +3511,7 @@ g_save_to_bmp(const char* filename, char* data, int stride_bytes, bh.reserved1 = 0; bh.reserved2 = 0; bh.offset = sizeof(bm) + sizeof(bh) + sizeof(dh); - + dh.hdr_size = sizeof(dh); dh.width = width; dh.height = height;