Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix connection failure on iOS when connecting to a Tailscale IPv4 address from an IPv6-only mobile network such as T-Mobile #98

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions src/Connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ void LiStopConnection(void) {
Limelog("done\n");
}
LC_ASSERT(stage == STAGE_NONE);

if (RemoteAddrString != NULL) {
free(RemoteAddrString);
RemoteAddrString = NULL;
Expand Down Expand Up @@ -281,7 +281,7 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre

alreadyTerminated = false;
ConnectionInterrupted = false;

// Validate the audio configuration
if (MAGIC_BYTE_FROM_AUDIO_CONFIG(StreamConfig.audioConfiguration) != 0xCA ||
CHANNEL_COUNT_FROM_AUDIO_CONFIGURATION(StreamConfig.audioConfiguration) > AUDIO_CONFIGURATION_MAX_CHANNEL_COUNT) {
Expand Down Expand Up @@ -328,7 +328,7 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
Limelog("Disabling reference frame invalidation for 4K streaming with GFE\n");
VideoCallbacks.capabilities &= ~CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
}

Limelog("Initializing platform...");
ListenerCallbacks.stageStarting(STAGE_PLATFORM_INIT);
err = initializePlatform();
Expand Down Expand Up @@ -380,7 +380,26 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
stage++;
LC_ASSERT(stage == STAGE_NAME_RESOLUTION);
ListenerCallbacks.stageComplete(STAGE_NAME_RESOLUTION);
Limelog("done\n");

#ifdef AF_INET6
// Handle the special case of wanting to connect to an IPv4 address or IPv4-only domain
// while on an IPv6-only network. This is common with mobile providers such as T-Mobile.
if (RemoteAddr.ss_family == AF_INET6 && isIPv4Address(serverInfo->address)) {
if (is464XLATSynthesizedAddress(&RemoteAddr, serverInfo->address)) {
// we must treat this as an IPv4 address so it gets routed correctly
logWithSockaddrStorage(&RemoteAddr, "IPv4 address was resolved to synthesized IPv6 address %s, this network might be using 464XLAT\n");

err = resolveHostName(serverInfo->address, AF_INET, 47984, &RemoteAddr, &AddrLen);
if (err != 0) {
Limelog("resolveHostName for AF_INET failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err);
goto Cleanup;
}

logWithSockaddrStorage(&RemoteAddr, "IPv4 address was restored via AF_INET: %s\n");
}
}
#endif

// If STREAM_CFG_AUTO was requested, determine the streamingRemotely value
// now that we have resolved the target address and impose the video packet
Expand Down Expand Up @@ -517,7 +536,7 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
LC_ASSERT(stage == STAGE_INPUT_STREAM_START);
ListenerCallbacks.stageComplete(STAGE_INPUT_STREAM_START);
Limelog("done\n");

// Wiggle the mouse a bit to wake the display up
LiSendMouseMoveEvent(1, 1);
PltSleepMs(10);
Expand Down
121 changes: 121 additions & 0 deletions src/PlatformSockets.c
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,18 @@ int enableNoDelay(SOCKET s) {
return 0;
}

static void logWithSockaddr(const char *format, const char *arg, const struct sockaddr *addr, socklen_t len) {
char host[NI_MAXHOST], service[NI_MAXSERV];
int res = getnameinfo(addr, len, host, sizeof(host), service, sizeof(service),
NI_NUMERICHOST | NI_NUMERICSERV);
if (res == 0) {
Limelog(format, arg, host, service);
}
else {
Limelog("getnameinfo() failed: %s\n", gai_strerror(res));
}
}

int resolveHostName(const char* host, int family, int tcpTestPort, struct sockaddr_storage* addr, SOCKADDR_LEN* addrLen)
{
struct addrinfo hints, *res, *currentAddr;
Expand All @@ -621,6 +633,8 @@ int resolveHostName(const char* host, int family, int tcpTestPort, struct sockad
}

for (currentAddr = res; currentAddr != NULL; currentAddr = currentAddr->ai_next) {
logWithSockaddr("Resolved %s to %s %s\n", host, currentAddr->ai_addr, sizeof(currentAddr->ai_addr));

// Use the test port to ensure this address is working if:
// a) We have multiple addresses
// b) The caller asked us to test even with a single address
Expand All @@ -634,6 +648,7 @@ int resolveHostName(const char* host, int family, int tcpTestPort, struct sockad
continue;
}
else {
Limelog("tcp test on port %d is ok\n", tcpTestPort);
closeSocket(testSocket);
}
}
Expand Down Expand Up @@ -829,6 +844,79 @@ bool isNat64SynthesizedAddress(struct sockaddr_storage* address) {
return false;
}

static uint32_t ipv4ToHex(const char *ipv4Str) {
struct in_addr addr;
if (inet_pton(AF_INET, ipv4Str, &addr) != 1) {
Limelog("ipv4ToHex: invalid IPv4 address: %s\n", ipv4ToHex);
return 0;
}
return ntohl(addr.s_addr);
}

// Return true if the given IPv6 sockaddr_storage is a synthesized address that
// encapsulates an IPv4 address for use on an IPv6-only network.
// See RFC 7050 and 8880 for more details.
// input: IPv6 address as a struct sockaddr_storage *, and an IPv4 string
bool is464XLATSynthesizedAddress(struct sockaddr_storage* ipv6Address, const char *ipv4Str) {
bool ret = false;

#ifdef AF_INET6
int err;
struct addrinfo hints, *res, *currentAddr;
memset(&hints, 0, sizeof(hints));

// perform an AAAA lookup on ipv4only.arpa
// If this returns one or more responses, the network has a CLAT that is synthesizing IPv6 addresses
hints.ai_family = AF_INET6; // will query AAAA only
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_CANONNAME;
err = getaddrinfo("ipv4only.arpa", NULL, &hints, &res);
if (err != 0) {
Limelog("is464XLATSynthesizedAddress getaddrinfo(ipv4only.arpa) failed: %d\n", err);
return false;
}
else if (res == NULL) {
return false;
}

char ipv6AddressStr[INET6_ADDRSTRLEN];
memset(&ipv6AddressStr, 0, sizeof(ipv6AddressStr));
struct sockaddr_in6 *ipv6Addr = (struct sockaddr_in6 *)ipv6Address;
inet_ntop(AF_INET6, &ipv6Addr->sin6_addr, ipv6AddressStr, sizeof(ipv6AddressStr));

// Look through all responses, we may see the Well Known Prefix 64:ff9b::/96 as well as
// Network Specific Prefixes such as T-Mobile's 2607:7700:0:51::*
char currentAddrStr[INET6_ADDRSTRLEN];
for (currentAddr = res; currentAddr != NULL; currentAddr = currentAddr->ai_next) {
memset(&currentAddrStr, 0, sizeof(currentAddrStr));
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)currentAddr->ai_addr;
inet_ntop(AF_INET6, &addr->sin6_addr, currentAddrStr, sizeof(currentAddrStr));

// Check first 96 bits of IPv6 for matching prefix
if (memcmp(&ipv6Addr->sin6_addr, &addr->sin6_addr, 12) == 0) {
// The prefixes match; now compare the IPv4 portion
uint32_t ipv6Hex;
memcpy(&ipv6Hex, ((uint8_t*)&ipv6Addr->sin6_addr) + 12, sizeof(ipv6Hex));
ipv6Hex = ntohl(ipv6Hex);

if (ipv6Hex == ipv4ToHex(ipv4Str)) {
ret = true;
break;
}
}
}

freeaddrinfo(res);
#endif

return ret;
}

bool isIPv4Address(const char *str) {
struct in_addr addr;
return inet_pton(AF_INET, str, &addr) == 1;
}

// Enable platform-specific low latency options (best-effort)
void enterLowLatencyMode(void) {
#if defined(LC_WINDOWS_DESKTOP)
Expand Down Expand Up @@ -959,3 +1047,36 @@ void cleanupPlatformSockets(void) {
#else
#endif
}

void logWithSockaddrStorage(struct sockaddr_storage* addr, const char *format) {
void *src = NULL;

#ifdef AF_INET6
char ip[INET6_ADDRSTRLEN];
#else
char ip[INET_ADDRSTRLEN];
#endif

#ifdef AF_INET6
if (addr->ss_family == AF_INET6) {
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;
src = &(addr_in6->sin6_addr);
}
else
#endif
if (addr->ss_family == AF_INET) {
struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
src = &(addr_in->sin_addr);
}
else {
Limelog("Unknown address family: %d\n", addr->ss_family);
return;
}

if (inet_ntop(addr->ss_family, src, ip, sizeof(ip)) == NULL) {
Limelog("invalid struct sockaddr_storage\n");
return;
}

Limelog(format, ip);
}
5 changes: 5 additions & 0 deletions src/PlatformSockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ typedef int SOCKADDR_LEN;
#include <errno.h>
#include <signal.h>
#include <poll.h>
#include <stdarg.h>

#define ioctlsocket ioctl
#define LastSocketError() errno
Expand Down Expand Up @@ -111,6 +112,8 @@ int setNonFatalRecvTimeoutMs(SOCKET s, int timeoutMs);
void closeSocket(SOCKET s);
bool isPrivateNetworkAddress(struct sockaddr_storage* address);
bool isNat64SynthesizedAddress(struct sockaddr_storage* address);
bool is464XLATSynthesizedAddress(struct sockaddr_storage* address, const char *ipv4Str);
bool isIPv4Address(const char *str);
int pollSockets(struct pollfd* pollFds, int pollFdsCount, int timeoutMs);
bool isSocketReadable(SOCKET s);

Expand All @@ -123,3 +126,5 @@ void exitLowLatencyMode(void);

int initializePlatformSockets(void);
void cleanupPlatformSockets(void);

void logWithSockaddrStorage(struct sockaddr_storage* addr, const char *format);