diff --git a/src/Connection.c b/src/Connection.c index b48a7e7..d2e8292 100644 --- a/src/Connection.c +++ b/src/Connection.c @@ -386,18 +386,28 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre // now that we have resolved the target address and impose the video packet // size cap if required. if (StreamConfig.streamingRemotely == STREAM_CFG_AUTO) { - if (isPrivateNetworkAddress(&RemoteAddr)) { + bool isNat64 = isNat64SynthesizedAddress(&RemoteAddr); + + // It's possible to have a NAT64 prefix on a ULA or other private range, + // so we must exclude NAT64 addresses from our local address checks. + if (!isNat64 && isPrivateNetworkAddress(&RemoteAddr)) { StreamConfig.streamingRemotely = STREAM_CFG_LOCAL; } else { StreamConfig.streamingRemotely = STREAM_CFG_REMOTE; - if (StreamConfig.packetSize > 1024) { - // Cap packet size at 1024 for remote streaming to avoid - // MTU problems and fragmentation. - Limelog("Packet size capped at 1KB for remote streaming\n"); + if (RemoteAddr.ss_family == AF_INET || isNat64) { + // Cap packet size at 1024 for remote IPv4 streaming to avoid fragmentation. + Limelog("Packet size capped at 1024 bytes for remote IPv4 streaming\n"); StreamConfig.packetSize = 1024; } + else { + // IPv6 guarantees a minimum MTU of 1280 before fragmentation, so use a higher + // packet size cap for remote IPv6 streaming (when not using NAT64 which isn't + // end-to-end IPv6 traffic). + Limelog("Packet size capped at 1184 bytes for remote IPv6 streaming\n"); + StreamConfig.packetSize = 1184; + } } } diff --git a/src/PlatformSockets.c b/src/PlatformSockets.c index cb2eab7..5bb6609 100644 --- a/src/PlatformSockets.c +++ b/src/PlatformSockets.c @@ -716,6 +716,119 @@ bool isPrivateNetworkAddress(struct sockaddr_storage* address) { return false; } +bool isNat64SynthesizedAddress(struct sockaddr_storage* address) { +#ifdef AF_INET6 + if (address->ss_family == AF_INET6) { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)address; + struct addrinfo hints, *res, *currentAddr; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_flags = AI_ADDRCONFIG; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + err = getaddrinfo("ipv4only.arpa.", NULL, &hints, &res); + if (err != 0) { + Limelog("Client is not running in NAT64 environment (%d)\n", err); + return false; + } + else if (res == NULL) { + Limelog("getaddrinfo(ipv4only.arpa.) returned success without addresses\n"); + return false; + } + + for (currentAddr = res; currentAddr != NULL; currentAddr = currentAddr->ai_next) { + struct sockaddr_in6* candidate6 = (struct sockaddr_in6*)currentAddr->ai_addr; + static const unsigned char wellKnownAddresses[2][4] = { + { 0xC0, 0x00, 0x00, 0xAA }, // 192.0.0.170 + { 0xC0, 0x00, 0x00, 0xAB }, // 192.0.0.171 + }; + + if (candidate6->sin6_family != AF_INET6) { + // This shouldn't be possible but check anyway + continue; + } + + for (int i = 0; i < 2; i++) { + int foundCount = 0; + int prefixLen = 0; + int suffixStart = 0; + + // Search for each well-known IPv4 address at all locations specified by + // https://datatracker.ietf.org/doc/html/rfc6052#section-2.2 + if (memcmp(&candidate6->sin6_addr.s6_addr[4], wellKnownAddresses[i], 4) == 0) { + foundCount++; + + prefixLen = 4; + suffixStart = 9; + } + if (memcmp(&candidate6->sin6_addr.s6_addr[5], &wellKnownAddresses[i][0], 3) == 0 && + memcmp(&candidate6->sin6_addr.s6_addr[9], &wellKnownAddresses[i][3], 1) == 0) { + foundCount++; + + prefixLen = 5; + suffixStart = 10; + } + if (memcmp(&candidate6->sin6_addr.s6_addr[6], &wellKnownAddresses[i][0], 2) == 0 && + memcmp(&candidate6->sin6_addr.s6_addr[9], &wellKnownAddresses[i][2], 2) == 0) { + foundCount++; + + prefixLen = 6; + suffixStart = 11; + } + if (memcmp(&candidate6->sin6_addr.s6_addr[7], &wellKnownAddresses[i][0], 1) == 0 && + memcmp(&candidate6->sin6_addr.s6_addr[9], &wellKnownAddresses[i][1], 3) == 0) { + foundCount++; + + prefixLen = 7; + suffixStart = 12; + } + if (memcmp(&candidate6->sin6_addr.s6_addr[9], &wellKnownAddresses[i], 4) == 0) { + foundCount++; + + prefixLen = 8; + suffixStart = 13; + } + if (memcmp(&candidate6->sin6_addr.s6_addr[12], &wellKnownAddresses[i], 4) == 0) { + foundCount++; + + prefixLen = 12; + suffixStart = 16; + } + + // We must find the well-known address exactly once. If we find it zero or multiple + // times, we must try the second well-known address or other AAAA records. + if (foundCount != 1) { + continue; + } + + // We have a valid NAT64 address identified, so we know we're running in an NAT64 environment. + // + // Now we must check to see if the address we resolved for the remote host actually falls + // within the NAT64 range to see if we must restrict ourselves to the IPv4 MTU. + if (memcmp(&sin6->sin6_addr.s6_addr[0], &candidate6->sin6_addr.s6_addr[0], prefixLen) == 0 && + (suffixStart == 16 || memcmp(&sin6->sin6_addr.s6_addr[suffixStart], + &candidate6->sin6_addr.s6_addr[suffixStart], + 16 - suffixStart) == 0)) { + freeaddrinfo(res); + return true; + } + else { + // This one didn't match, so let's break out of the loop and try the next AAAA record. + break; + } + } + } + + freeaddrinfo(res); + return false; + } +#endif + + return false; +} + // Enable platform-specific low latency options (best-effort) void enterLowLatencyMode(void) { #if defined(LC_WINDOWS_DESKTOP) diff --git a/src/PlatformSockets.h b/src/PlatformSockets.h index 0eab7ef..d2e90ec 100644 --- a/src/PlatformSockets.h +++ b/src/PlatformSockets.h @@ -110,6 +110,7 @@ void shutdownTcpSocket(SOCKET s); int setNonFatalRecvTimeoutMs(SOCKET s, int timeoutMs); void closeSocket(SOCKET s); bool isPrivateNetworkAddress(struct sockaddr_storage* address); +bool isNat64SynthesizedAddress(struct sockaddr_storage* address); int pollSockets(struct pollfd* pollFds, int pollFdsCount, int timeoutMs); bool isSocketReadable(SOCKET s);