g_tcp_connect fix:

- add addr_is_ipv4 helper function
- add addr_is_ipv6 helper function
- add get_socket_family helper function
- g_tcp_connect:
  - #define for MAX_PORT_STR length
  - create *addr_arg char that is used in getaddrinfo so we can do any last-minute changes directly
  - add chars host and service which are primarily used for debug logging of attempted connection points
  - calls get_socket_family to discover family of socket
  - add switch case logic for AF_INET6/4:
    - v4: just calls addr_is_ipv6 so we don't fail on a hostname input
    - v6: drop addrconfig flag and add AI_ALL so we can try to map ipv4 destinations against ipv6 socket to support varied configurations we could see
    - if an ipv4 address comes in against an ipv6 socket, we map it to try and connect anyways
  - getaddrinfo now calls addr_arg so it picks up changes that an ipv6-case may have done
  - before calling connect, use getnameinfo to get logging values for the actual host ip/port we are about to attempt a connect on - I found this useful when debugging so thought it had value to keep
- change if (res > -1) to if (res == 0): the bsd man pages for getaddrinfo only promise that it returns 0 on success, so it seems sensible to me to cover the event that an error code could be positive, which freebsd has some positive EAI_* errors based on this: https://github.com/freebsd/freebsd-src/blob/main/lib/libc/net/gai_strerror.c
- remove connect_loopback entirely:  this had several bits of logic handling ipv6 and ipv4 mixing already and ended up being redundant because of the restructuring of g_tcp_connect, which covers direct ip address char* inputs now
- add comment about OSX to IPv6 function for clarity
This commit is contained in:
Matt Cunningham
2026-03-23 08:21:29 -04:00
parent b9742d94f7
commit fcc5bd79ea
+123 -49
View File
@@ -791,60 +791,82 @@ g_sck_close(int sck)
#if defined(XRDP_ENABLE_IPV6) #if defined(XRDP_ENABLE_IPV6)
/*****************************************************************************/ /*****************************************************************************/
/* Helper function for g_tcp_connect. */ /**
* Helper for g_tcp_connect
* Returns boolean, 1 if the address is IPv4, else 0
*/
static int static int
connect_loopback(int sck, const char *port) addr_is_ipv4(const char *address)
{ {
struct sockaddr_in6 sa; struct in_addr addr;
struct sockaddr_in s;
int res;
// First IPv6 if (address == NULL || *address == '\0')
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; return 0;
} }
// else IPv4 /* inet_pton returns 1 on success */
g_memset(&s, 0, sizeof(s)); return inet_pton(AF_INET, address, &addr) == 1;
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) * Helper for g_tcp_connect
{ * Returns boolean, 1 if the address is IPv6, else 0
return -1; */
} static int
if (res == 0 || (res == -1 && errno == EISCONN)) addr_is_ipv6(const char *address)
{
struct in6_addr addr;
if (address == NULL || *address == '\0')
{ {
return 0; return 0;
} }
// else IPv6 with IPv4 address /* inet_pton returns 1 on success */
g_memset(&sa, 0, sizeof(sa)); return inet_pton(AF_INET6, address, &addr) == 1;
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; /*****************************************************************************/
/**
* Another helper for g_tcp_connect
* Get the family/domain for a socket
*
* This is a little awkward, as POSIX does not yet offer a
* standardized way to do this
*/
static int
get_socket_family(int sck, int *family)
{
int error;
#ifdef SO_DOMAIN
socklen_t len = sizeof(*family);
// SO_DOMAIN is widely used but not defined by POSIX
error = getsockopt(sck, SOL_SOCKET, SO_DOMAIN, family, &len);
if (error != 0)
{
LOG(LOG_LEVEL_WARNING, "getsockopt() failed on socket %d: %s",
sck, g_get_strerror());
}
#else
union sock_info sock_info;
socklen_t sock_len = sizeof(sock_info);
/* Use getsockname() as a fallback. This is not guaranteed to work
on an unbound socket, according to POSIX */
error = getsockname(sck, &sock_info.sa, &sock_len);
if (error != 0)
{
LOG(LOG_LEVEL_WARNING, "getsockname() failed on socket %d: %s",
sck, g_get_strerror());
}
else
{
*family = sock_info.sa.sa_family;
}
#endif
return error;
} }
#endif #endif
@@ -856,42 +878,94 @@ connect_loopback(int sck, const char *port)
int int
g_tcp_connect(int sck, const char *address, const char *port) g_tcp_connect(int sck, const char *address, const char *port)
{ {
#define MAX_PORT_STR 6
int res = 0; int res = 0;
struct addrinfo p; struct addrinfo p;
struct addrinfo *h = (struct addrinfo *)NULL; struct addrinfo *h = (struct addrinfo *)NULL;
struct addrinfo *rp = (struct addrinfo *)NULL; struct addrinfo *rp = (struct addrinfo *)NULL;
const char *addr_arg = address;
char mapped_address[INET6_ADDRSTRLEN];
char host[INET6_ADDRSTRLEN];
char service[MAX_PORT_STR];
g_memset(&p, 0, sizeof(struct addrinfo)); g_memset(&p, 0, sizeof(struct addrinfo));
p.ai_socktype = SOCK_STREAM; p.ai_socktype = SOCK_STREAM;
p.ai_protocol = IPPROTO_TCP; p.ai_protocol = IPPROTO_TCP;
p.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED;
p.ai_family = AF_INET6; if (get_socket_family(sck, &p.ai_family) != 0)
if (g_strcmp(address, "127.0.0.1") == 0)
{ {
return connect_loopback(sck, port); LOG(LOG_LEVEL_ERROR, "get_socket_family() failed on socket %d: %s",
sck, g_get_strerror());
return -1;
} }
else
switch (p.ai_family)
{ {
res = getaddrinfo(address, port, &p, &h); case AF_INET:
if (addr_is_ipv6(address))
{
LOG(LOG_LEVEL_ERROR, "g_tcp_connect: socket is IPv4, but address is IPv6: %s",
address);
return -1;
} }
p.ai_flags = AI_ADDRCONFIG;
break;
case AF_INET6:
// AI_ALL + MAPPED ensures we will get the v4 mapped addr back later
// no ADDRCONFIG because v4 can still be mapped on v6, so is still worth attempting
p.ai_flags = AI_V4MAPPED | AI_ALL;
if (addr_is_ipv4(address))
{
/* Dest was IPv4, but sck is IPv6, so convert IPv4 dest literal to IPv4-mapped IPv6 literal:
ipv6 with ipv4 address, for short */
g_snprintf(mapped_address, sizeof(mapped_address), "::FFFF:%s", address);
addr_arg = mapped_address;
}
break;
default:
LOG(LOG_LEVEL_ERROR, "g_tcp_connect: Unknown socket family %d", p.ai_family);
return -1;
}
/* by now we have covered if dest was a direct ip that was mismatch with sck, so
the only hiccup can be a hostname that doesn't actually have any valid options.
Time to attempt to connect on every option we've got */
res = getaddrinfo(addr_arg, port, &p, &h);
if (res != 0) if (res != 0)
{ {
LOG(LOG_LEVEL_ERROR, "g_tcp_connect(%d, %s, %s): getaddrinfo() failed: %s", LOG(LOG_LEVEL_ERROR, "g_tcp_connect(%d, %s, %s): getaddrinfo() failed: %s",
sck, address, port, gai_strerror(res)); sck, address, port, gai_strerror(res));
} }
if (res > -1) if (res == 0)
{ {
if (h != NULL) if (h != NULL)
{ {
for (rp = h; rp != NULL; rp = rp->ai_next) for (rp = h; rp != NULL; rp = rp->ai_next)
{ {
// NI_NUMERICHOST and NI_NUMERICSERV are POSIX, so they should be safe to use
res = getnameinfo(rp->ai_addr, rp->ai_addrlen, host, INET6_ADDRSTRLEN,
service, MAX_PORT_STR, NI_NUMERICHOST | NI_NUMERICSERV);
if (res != 0)
{
// can't really count on host/service being accurate if getnameinfo fails
LOG(LOG_LEVEL_ERROR, "g_tcp_connect(%d, %s, %s): getnameinfo failed",
sck, address, port);
res = -1;
break;
}
LOG(LOG_LEVEL_DEBUG, "g_tcp_connect(%d, %s, %s): trying address: %s:%s",
sck, address, port, host, service);
res = connect(sck, (struct sockaddr *)(rp->ai_addr), res = connect(sck, (struct sockaddr *)(rp->ai_addr),
rp->ai_addrlen); rp->ai_addrlen);
if (res == -1 && errno == EINPROGRESS) if (res == -1 && errno == EINPROGRESS)
{ {
break; /* Return -1 */ break; /* Return -1 */
} }
/* Mac OSX connect() returns -1 for already established connections */
if (res == 0 || (res == -1 && errno == EISCONN)) if (res == 0 || (res == -1 && errno == EISCONN))
{ {
res = 0; res = 0;