Compare commits

..
13 Commits
Author SHA1 Message Date
Cameron Gutman ef5bb72d5c Version 5.2 2020-08-10 22:05:43 -07:00
Cameron Gutman 3343ebb225 Don't allow testing while a stream is active 2020-08-10 21:50:52 -07:00
Cameron Gutman 05413a554c Work around IGDs that deduplicate entries based on the internal port
This is a violation of the UPnP IGD specification but we can relay through an alternate port as a workaround.
2020-08-10 21:16:21 -07:00
Cameron Gutman 5f015acdaa Improve robustness for broken UPnP IGDs 2020-08-09 16:01:39 -07:00
Cameron Gutman df286ef56d Use indefinite mappings if the IGD returns unexpected error codes 2020-08-09 15:30:48 -07:00
Cameron Gutman 47d60b9f24 Leave permanent mappings alone during the update cycle 2020-08-09 15:16:05 -07:00
Cameron Gutman 659d3aea23 Open UDP 47009 for WoL
It may work where UDP 9 fails because it's an unprivileged port
2020-08-09 14:00:14 -07:00
Cameron Gutman e4a4d42ece Version 5.1 2020-08-08 16:57:06 -07:00
Cameron Gutman 189e861362 Fix systems where Windows Firewall has been improperly disabled 2020-08-08 16:37:38 -07:00
Cameron Gutman fd022b67d3 Only test on the first reachable IPv4 and IPv6 relay servers 2020-08-07 21:58:36 -07:00
Cameron Gutman 14d27b4cf5 Use WinHTTP timeouts to avoid having to test HTTP ports twice 2020-08-04 02:03:14 -07:00
Cameron Gutman 17cb084968 Improve heuristic for distinguishing CGN vs double-NAT 2020-08-04 01:13:21 -07:00
Cameron Gutman ad8ef228d5 Fix double-close of connection handle on failure 2020-07-19 10:08:26 -07:00
9 changed files with 437 additions and 42 deletions
+31
View File
@@ -14,6 +14,28 @@
</Feature>
</Product>
<!-- Enable the Windows Firewall service if it is disabled. Disabling the Windows Firewall service
will paradoxically block *all* incoming network traffic and prevent GameStream, MIST, MISS,
and other local servers from working at all. The proper way to disable Windows Firewall is via
the Windows Firewall control panel applet. -->
<Fragment>
<Property Id="MPSSVC_START">
<RegistrySearch Id="MpsSvcStart"
Root="HKLM"
Key="System\CurrentControlSet\Services\MpsSvc"
Name="Start"
Type="raw" />
</Property>
<SetProperty Id="EnableMpsSvc" Value='"[SystemFolder]sc.exe" config MpsSvc start= auto' Sequence="execute" Before="InstallInitialize"/>
<CustomAction Id="EnableMpsSvc" BinaryKey="WixCA" DllEntry="WixQuietExec"
Execute="deferred" Return="ignore" Impersonate="no"/>
<SetProperty Id="StartMpsSvc" Value='"[SystemFolder]net.exe" start MpsSvc' Sequence="execute" Before="InstallInitialize"/>
<CustomAction Id="StartMpsSvc" BinaryKey="WixCA" DllEntry="WixQuietExec"
Execute="deferred" Return="ignore" Impersonate="no"/>
</Fragment>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
@@ -69,5 +91,14 @@
<RegistryValue Root="HKCU" Key="Software\Moonlight Internet Streaming Tester" Name="Installed" Type="integer" Value="1" KeyPath="yes" />
</Component>
</ComponentGroup>
<InstallExecuteSequence>
<Custom Action="EnableMpsSvc" Before="StartMpsSvc">
<![CDATA[MPSSVC_START <> "#2"]]>
</Custom>
<Custom Action="StartMpsSvc" Before="StopServices">
<![CDATA[MPSSVC_START <> "#2"]]>
</Custom>
</InstallExecuteSequence>
</Fragment>
</Wix>
+86 -8
View File
@@ -12,6 +12,7 @@
#include <assert.h>
#include <stdlib.h>
#include "relay.h"
#include "..\version.h"
#pragma comment(lib, "miniupnpc.lib")
@@ -53,9 +54,9 @@ static struct port_entry {
{IPPROTO_UDP, 48010}
};
static const int k_WolPorts[] = { 9 };
static const int k_WolPorts[] = { 9, 47009 };
bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const char* myAddr, int port, bool enable, bool indefinite)
bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const char* myAddr, int port, bool enable, bool indefinite, bool validationPass)
{
char intClient[16];
char intPort[6];
@@ -95,17 +96,42 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
if (err == 714) {
// NoSuchEntryInArray
printf("NOT FOUND" NL);
if (validationPass) {
// On validation, we found a missing entry. Convert this entry to indefinite
// to see if it will stick.
indefinite = true;
}
}
else if (err == 606) {
printf("UNAUTHORIZED" NL);
// If we're just validating, we're done. We can't know if the entry was
// actually applied but we'll return true to avoid false errors if it was.
if (validationPass) {
return true;
}
}
else if (err == UPNPCOMMAND_SUCCESS) {
// Some routers change the description, so we can't check that here
if (!strcmp(intClient, myAddr)) {
if (atoi(leaseDuration) == 0) {
printf("OK (Permanent)" NL);
printf("OK (Static)" NL);
// If we have an existing permanent mapping, we can just leave it alone.
if (enable) {
return true;
}
}
else {
printf("OK (%s seconds remaining)" NL, leaseDuration);
}
// If we're just validating, we found an entry, so we're done.
if (validationPass) {
return true;
}
if (!enable) {
// This is our entry. Go ahead and nuke it
printf("Deleting UPnP mapping for %s %s -> %s...", protoStr, portStr, myAddr);
@@ -123,6 +149,11 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
else {
printf("CONFLICT: %s %s" NL, intClient, desc);
// If we're just validating, we found an entry, so we're done.
if (validationPass) {
return true;
}
// Some UPnP IGDs won't let unauthenticated clients delete other conflicting port mappings
// for security reasons, but we will give it a try anyway. If GameStream is not enabled,
// we will leave the conflicting entry alone to avoid disturbing another PC's port forwarding
@@ -146,6 +177,10 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
}
else {
printf("ERROR %d (%s)" NL, err, strupnperror(err));
// If we get a strange error from the router, we'll assume it's some old broken IGDv1
// device and only use indefinite lease durations to hopefully avoid confusing it.
indefinite = true;
}
// Bail if GameStream is disabled
@@ -160,11 +195,31 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
err = UPNP_AddPortMapping(
urls->controlURL, data->first.servicetype, portStr,
portStr, myAddr, myDesc, protoStr, nullptr, leaseDuration);
if (err == 725 && !indefinite) { // OnlyPermanentLeasesSupported
if (err != UPNPCOMMAND_SUCCESS && !indefinite) {
// This may be a broken IGD that doesn't like non-static mappings. Try a static
// mapping before finally giving up.
err = UPNP_AddPortMapping(
urls->controlURL, data->first.servicetype, portStr,
portStr, myAddr, myDesc, protoStr, nullptr, "0");
printf("PERMANENT ");
printf("STATIC RETRY ");
}
else if (indefinite) {
printf("STATIC ");
}
if (err == 718 && proto == IPPROTO_UDP) { // ConflictInMappingEntry
// Some UPnP implementations incorrectly deduplicate on the internal port instead
// of the external port, in violation of the UPnP IGD specification. Since GFE creates
// mappings on the same internal port as us, those routers break our mappings. To
// work around this issue, we run relays for each of the UDP ports on an alternate
// internal port. We'll try the alternate port if we get a conflict for a UDP entry.
// Given that these are already horribly non-spec compliant, we won't take any chances
// and we'll use an indefinite mapping too.
char altPortStr[6];
snprintf(altPortStr, sizeof(altPortStr), "%d", port + RELAY_PORT_OFFSET);
err = UPNP_AddPortMapping(
urls->controlURL, data->first.servicetype, portStr,
altPortStr, myAddr, myDesc, protoStr, nullptr, "0");
printf("ALTERNATE ");
}
if (err == UPNPCOMMAND_SUCCESS) {
printf("OK" NL);
@@ -279,8 +334,9 @@ bool UPnPHandleDeviceList(struct UPNPDev* list, bool enable, char* lanAddrOverri
portMappingInternalAddress = localAddress;
}
// Create the port mappings
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
if (!UPnPMapPort(&urls, &data, k_Ports[i].proto, portMappingInternalAddress, k_Ports[i].port, enable, false)) {
if (!UPnPMapPort(&urls, &data, k_Ports[i].proto, portMappingInternalAddress, k_Ports[i].port, enable, false, false)) {
success = false;
}
}
@@ -304,13 +360,28 @@ bool UPnPHandleDeviceList(struct UPNPDev* list, bool enable, char* lanAddrOverri
char broadcastAddrStr[128];
inet_ntop(AF_INET, &broadcastAddr, broadcastAddrStr, sizeof(broadcastAddrStr));
UPnPMapPort(&urls, &data, IPPROTO_UDP, broadcastAddrStr, k_WolPorts[i], enable, true);
UPnPMapPort(&urls, &data, IPPROTO_UDP, broadcastAddrStr, k_WolPorts[i], enable, true, false);
}
}
else {
// When we're mapping the WOL ports upstream of our router, we map directly to
// the port on the upstream address (likely our router's WAN interface).
UPnPMapPort(&urls, &data, IPPROTO_UDP, lanAddrOverride, k_WolPorts[i], enable, true);
UPnPMapPort(&urls, &data, IPPROTO_UDP, lanAddrOverride, k_WolPorts[i], enable, true, false);
}
}
// Validate the rules are present and correct if they claimed to be added successfully
if (success && enable) {
// Wait 10 seconds for the router state to quiesce
printf("Waiting before UPnP port validation...");
Sleep(10000);
printf("done" NL);
// Perform the validation pass (converting any now missing entries to permanent ones)
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
if (!UPnPMapPort(&urls, &data, k_Ports[i].proto, portMappingInternalAddress, k_Ports[i].port, enable, false, true)) {
success = false;
}
}
}
@@ -797,6 +868,13 @@ int Run()
ResetLogFile();
// Create the UDP alternate port relays
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
if (k_Ports[i].proto == IPPROTO_UDP) {
StartUdpRelay(k_Ports[i].port);
}
}
// Create the thread to watch for GameStream state changes
CreateThread(nullptr, 0, GameStreamStateChangeThread, gsChangeEvent, 0, nullptr);
+2
View File
@@ -163,6 +163,7 @@
<ItemGroup>
<ClCompile Include="miss.cpp" />
<ClCompile Include="pcp.cpp" />
<ClCompile Include="relay.cpp" />
<ClCompile Include="tracer.cpp" />
</ItemGroup>
<ItemGroup>
@@ -170,6 +171,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\version.h" />
<ClInclude Include="relay.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
+6
View File
@@ -24,6 +24,9 @@
<ClCompile Include="pcp.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="relay.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="miss.rc">
@@ -34,5 +37,8 @@
<ClInclude Include="..\version.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="relay.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>
+112
View File
@@ -0,0 +1,112 @@
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <Ws2ipdef.h>
#include "relay.h"
typedef struct _UDP_TUPLE {
SOCKET socket;
unsigned short port;
} UDP_TUPLE, *PUDP_TUPLE;
DWORD
WINAPI
UdpRelayThreadProc(LPVOID Context)
{
PUDP_TUPLE tuple = (PUDP_TUPLE)Context;
fd_set fds;
int err;
SOCKADDR_IN lastRemoteAddr;
RtlZeroMemory(&lastRemoteAddr, sizeof(lastRemoteAddr));
for (;;) {
char buffer[4096];
SOCKADDR_IN sourceAddr;
int sourceAddrLen;
FD_ZERO(&fds);
FD_SET(tuple->socket, &fds);
err = select(0, &fds, NULL, NULL, NULL);
if (err <= 0) {
break;
}
sourceAddrLen = sizeof(sourceAddr);
err = recvfrom(tuple->socket, buffer, sizeof(buffer), 0, (PSOCKADDR)&sourceAddr, &sourceAddrLen);
if (err == SOCKET_ERROR) {
continue;
}
SOCKADDR_IN destinationAddr;
if (RtlEqualMemory(&sourceAddr.sin_addr, &in4addr_loopback, sizeof(sourceAddr.sin_addr))) {
// Traffic incoming from loopback interface - send it to the last remote address
destinationAddr = lastRemoteAddr;
}
else {
// Traffic incoming from the remote host - remember the source
lastRemoteAddr = sourceAddr;
// Send it to the normal port via the loopback adapter
destinationAddr = sourceAddr;
destinationAddr.sin_addr = in4addr_loopback;
destinationAddr.sin_port = htons(tuple->port);
}
sendto(tuple->socket, buffer, err, 0, (PSOCKADDR)&destinationAddr, sizeof(destinationAddr));
}
closesocket(tuple->socket);
free(tuple);
return 0;
}
int StartUdpRelay(unsigned short Port)
{
SOCKET sock;
SOCKADDR_IN addr;
HANDLE thread;
PUDP_TUPLE tuple;
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET) {
printf("socket() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
// Bind to the alternate port
RtlZeroMemory(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(Port + RELAY_PORT_OFFSET);
if (bind(sock, (PSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR) {
printf("bind() failed: %d\n", WSAGetLastError());
closesocket(sock);
return WSAGetLastError();
}
tuple = (PUDP_TUPLE)malloc(sizeof(*tuple));
if (tuple == NULL) {
return ERROR_OUTOFMEMORY;
}
tuple->socket = sock;
tuple->port = Port;
thread = CreateThread(NULL, 0, UdpRelayThreadProc, tuple, 0, NULL);
if (thread == NULL) {
printf("CreateThread() failed: %d\n", GetLastError());
closesocket(sock);
return GetLastError();
}
CloseHandle(thread);
return 0;
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#define RELAY_PORT_OFFSET -10000
int StartUdpRelay(unsigned short Port);
+192 -31
View File
@@ -9,6 +9,7 @@
#include <wtsapi32.h>
#include <powerbase.h>
#include <VersionHelpers.h>
#include <tlhelp32.h>
#pragma comment(lib, "miniupnpc.lib")
#pragma comment(lib, "libnatpmp.lib")
@@ -268,6 +269,28 @@ bool IsGameStreamEnabled()
}
}
bool IsCurrentlyStreaming()
{
bool ret = false;
HANDLE processSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 procEntry;
procEntry.dwSize = sizeof(procEntry);
Process32First(processSnapshot, &procEntry);
do {
// If we find nvstreamer.exe running, we're currently streaming
if (_stricmp(procEntry.szExeFile, "nvstreamer.exe") == 0) {
ret = true;
break;
}
} while (Process32Next(processSnapshot, &procEntry));
CloseHandle(processSnapshot);
return ret;
}
bool IsConsoleSessionActive()
{
PWTS_SESSION_INFO_1 sessionInfo;
@@ -563,6 +586,11 @@ PortTestStatus TestHttpPort(PSOCKADDR_STORAGE addr, int port, bool isLoopbackRel
goto Exit;
}
// WinHTTP's default timeouts are very long. Set them to something more reasonable.
if (!WinHttpSetTimeouts(hSession, 0, 3000, 5000, 5000)) {
fprintf(LOG_OUT, "WinHttpSetTimeouts() failed: %d\n", GetLastError());
}
// Windows 8.1 enabled TLSv1.2 for WinHTTP by default (8.0 enables it for Schannel but not WinHTTP)
// https://docs.microsoft.com/en-us/security/engineering/solving-tls1-problem
if (!IsWindows8Point1OrGreater()) {
@@ -597,7 +625,6 @@ PortTestStatus TestHttpPort(PSOCKADDR_STORAGE addr, int port, bool isLoopbackRel
port == 47984 ? WINHTTP_FLAG_SECURE : 0);
if (hConnection == nullptr) {
fprintf(LOG_OUT, "WinHttpOpenRequest() failed: %d\n", GetLastError());
WinHttpCloseHandle(hConnection);
result = PortTestError;
goto Exit;
}
@@ -650,9 +677,7 @@ bool TestAllPorts(PSOCKADDR_STORAGE addr, char* portMsg, int portMsgLen, bool is
}
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
fprintf(LOG_OUT, "Testing %s %d...",
k_Ports[i].proto == IPPROTO_TCP ? "TCP" : "UDP",
k_Ports[i].port);
PortTestStatus status;
if (consolePrint) {
fprintf(CONSOLE_OUT, "\tTesting %s %d...\n",
@@ -660,16 +685,19 @@ bool TestAllPorts(PSOCKADDR_STORAGE addr, char* portMsg, int portMsgLen, bool is
k_Ports[i].port);
}
PortTestStatus status = TestPort(addr, k_Ports[i].proto, k_Ports[i].port, k_Ports[i].withServer, isLoopbackRelay);
if (status != PortTestError && !k_Ports[i].withServer) {
if (!k_Ports[i].withServer) {
// Test using a real HTTP client if the port wasn't totally dead.
// This is required to confirm functionality with the loopback relay.
// TestHttpPort() can take significantly longer to timeout than TestPort(),
// so we only do this test if we believe we're likely to get a response.
assert(k_Ports[i].proto == IPPROTO_TCP);
fprintf(LOG_OUT, "Testing TCP %d with HTTP traffic...", k_Ports[i].port);
status = TestHttpPort(addr, k_Ports[i].port, isLoopbackRelay);
}
else {
fprintf(LOG_OUT, "Testing %s %d...",
k_Ports[i].proto == IPPROTO_TCP ? "TCP" : "UDP",
k_Ports[i].port);
status = TestPort(addr, k_Ports[i].proto, k_Ports[i].port, k_Ports[i].withServer, isLoopbackRelay);
}
if (status != PortTestOk) {
// If we got an unknown result, assume it matches with whatever
@@ -695,6 +723,81 @@ bool TestAllPorts(PSOCKADDR_STORAGE addr, char* portMsg, int portMsgLen, bool is
return ret;
}
bool IsTestServerReachable(struct addrinfo* addrinfo, unsigned short port)
{
SOCKET s;
FD_SET writeFds, exceptFds;
int err;
struct timeval tv = {};
SOCKADDR_STORAGE addr;
char testServerStr[INET6_ADDRSTRLEN];
memcpy(&addr, addrinfo->ai_addr, addrinfo->ai_addrlen);
((PSOCKADDR_IN6)&addr)->sin6_port = htons(port);
if (addr.ss_family == AF_INET) {
inet_ntop(AF_INET, &((struct sockaddr_in*)&addr)->sin_addr, testServerStr, sizeof(testServerStr));
}
else {
inet_ntop(AF_INET6, &((struct sockaddr_in6*)&addr)->sin6_addr, testServerStr, sizeof(testServerStr));
}
fprintf(LOG_OUT, "Testing reachability of relay server %s...", testServerStr);
s = socket(addrinfo->ai_family, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET) {
fprintf(LOG_OUT, "socket() failed: %d\n", WSAGetLastError());
return false;
}
ULONG nbIo = 1;
err = ioctlsocket(s, FIONBIO, &nbIo);
if (err == SOCKET_ERROR) {
fprintf(LOG_OUT, "ioctlsocket() failed: %d\n", WSAGetLastError());
closesocket(s);
return false;
}
err = connect(s, (PSOCKADDR)&addr, addrinfo->ai_addrlen);
if (err == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) {
fprintf(LOG_OUT, "Unreachable (%d)\n", WSAGetLastError());
closesocket(s);
return false;
}
FD_ZERO(&writeFds);
FD_ZERO(&exceptFds);
FD_SET(s, &writeFds);
FD_SET(s, &exceptFds);
tv.tv_sec = 3;
err = select(0, nullptr, &writeFds, &exceptFds, &tv);
if (err == SOCKET_ERROR) {
fprintf(LOG_OUT, "select() failed: %d\n", WSAGetLastError());
closesocket(s);
return false;
}
else if (err == 0) {
fprintf(LOG_OUT, "Unreachable (timeout)\n");
closesocket(s);
return false;
}
else {
int optlen = sizeof(err);
getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&err, &optlen);
closesocket(s);
if (err != 0 || FD_ISSET(s, &exceptFds)) {
fprintf(LOG_OUT, "Unreachable (%d)\n", err);
return false;
}
fprintf(LOG_OUT, "Reachable\n");
return true;
}
}
bool FindLocalInterfaceIPAddress(int family, PSOCKADDR_STORAGE addr)
{
SOCKET s;
@@ -710,7 +813,14 @@ bool FindLocalInterfaceIPAddress(int family, PSOCKADDR_STORAGE addr)
hint.ai_flags = AI_ADDRCONFIG;
err = getaddrinfo("moonlight-stream.org", "443", &hint, &result);
if (err != 0 || result == NULL) {
fprintf(LOG_OUT, "getaddrinfo() failed: %d\n", err);
// AI_ADDRCONFIG will mask unusable addresses, so we may get nothing
// We get WSANO_DATA or WSAHOST_NOT_FOUND depending on whether it's V4 or V6 :(
if (err == WSANO_DATA || err == WSAHOST_NOT_FOUND) {
fprintf(LOG_OUT, "NONE\n");
}
else {
fprintf(LOG_OUT, "getaddrinfo() failed: %d\n", err);
}
return false;
}
@@ -1007,7 +1117,7 @@ bool CheckWANAccess(PSOCKADDR_IN wanAddr, PSOCKADDR_IN reportedWanAddr, bool* fo
closenatpmp(&natpmp);
if (natPmpErr == 0) {
char addrStr[64];
char addrStr[INET_ADDRSTRLEN];
reportedWanAddr->sin_addr = response.pnu.publicaddress.addr;
inet_ntop(AF_INET, &response.pnu.publicaddress.addr, addrStr, sizeof(addrStr));
fprintf(LOG_OUT, "%s\n", addrStr);
@@ -1034,7 +1144,7 @@ bool CheckWANAccess(PSOCKADDR_IN wanAddr, PSOCKADDR_IN reportedWanAddr, bool* fo
return false;
}
else {
char addrStr[64];
char addrStr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &wanAddr->sin_addr, addrStr, sizeof(addrStr));
fprintf(LOG_OUT, "%s\n", addrStr);
@@ -1048,16 +1158,12 @@ bool CheckWANAccess(PSOCKADDR_IN wanAddr, PSOCKADDR_IN reportedWanAddr, bool* fo
return true;
}
bool IsPossibleCGN(PSOCKADDR_IN wanAddr)
bool IsCGN(PSOCKADDR_IN wanAddr)
{
DWORD addr = htonl(wanAddr->sin_addr.S_un.S_addr);
// 10.0.0.0/8 - ISPs used to use this
if ((addr & 0xFF000000) == 0x0A000000) {
return true;
}
// 100.64.0.0/10 - RFC6598 official CGN address
else if ((addr & 0xFFC00000) == 0x64400000) {
if ((addr & 0xFFC00000) == 0x64400000) {
return true;
}
@@ -1120,6 +1226,12 @@ int main(int argc, char* argv[])
return -1;
}
if (IsCurrentlyStreaming()) {
DisplayMessage("The test cannot proceed because a GameStream session is currently running on this PC.\n\n"
"Quit the currently running app on this host within Moonlight, or reboot your PC.");
return -1;
}
if (!IsConsoleSessionActive()) {
DisplayMessage("The system display is currently locked. You must sign in to your PC again to use GameStream.\n\n"
"This is most often due to Microsoft Remote Desktop locking the screen. Use an alternate GameStream-compatible remote desktop solution like Chrome Remote Desktop or TeamViewer to unlock the PC and prevent this error in the future.",
@@ -1237,9 +1349,24 @@ int main(int argc, char* argv[])
}
}
if (!FindLocalInterfaceIPAddress(AF_INET, &ss) && !FindLocalInterfaceIPAddress(AF_INET6, &ss)) {
DisplayMessage("Unable to perform GameStream connectivity check. Please check your Internet connection and try again.");
return -1;
bool hasV4Connectivity, hasV6Connectivity;
{
SOCKADDR_STORAGE v4, v6;
hasV4Connectivity = FindLocalInterfaceIPAddress(AF_INET, &v4);
hasV6Connectivity = FindLocalInterfaceIPAddress(AF_INET6, &v6);
// Prefer v4 connectivity because that's what GFE uses natively
if (hasV4Connectivity) {
ss = v4;
}
else if (hasV6Connectivity) {
ss = v6;
}
else {
DisplayMessage("Unable to perform GameStream connectivity check. Please check your Internet connection and try again.");
return -1;
}
}
fprintf(CONSOLE_OUT, "Testing GameStream connectivity on your local network...\n");
@@ -1255,7 +1382,7 @@ int main(int argc, char* argv[])
bool igdDisconnected;
SOCKADDR_IN locallyReportedWanAddr;
char wanAddrStr[INET_ADDRSTRLEN];
char wanAddrStr[INET6_ADDRSTRLEN];
if (ss.ss_family == AF_INET) {
bool rulesFound;
@@ -1295,6 +1422,7 @@ int main(int argc, char* argv[])
fprintf(LOG_OUT, "getaddrinfo() failed: %d\n", err);
}
else {
bool testServerWasReachable = false;
bool allPortsFailedOnV4 = true;
// First try the relay server over IPv4. If this passes, it's considered a full success
@@ -1302,6 +1430,14 @@ int main(int argc, char* argv[])
for (struct addrinfo* current = result; current != NULL; current = current->ai_next) {
if (current->ai_family == AF_INET) {
fprintf(CONSOLE_OUT, "Testing GameStream connectivity over the Internet using a relay server...\n");
if (!IsTestServerReachable(current, 443)) {
fprintf(CONSOLE_OUT, "Skipping unreachable relay server...\n");
continue;
}
testServerWasReachable = true;
if (TestAllPorts((PSOCKADDR_STORAGE)current->ai_addr, portMsgBuf, sizeof(portMsgBuf), true, true, &allPortsFailedOnV4)) {
freeaddrinfo(result);
snprintf(msgBuf, sizeof(msgBuf), "This PC is ready to host over the Internet!\n\n"
@@ -1310,6 +1446,10 @@ int main(int argc, char* argv[])
DisplayMessage(msgBuf, nullptr, MpInfo);
return 0;
}
else {
// Tested against a working server and it failed
break;
}
}
}
@@ -1318,6 +1458,14 @@ int main(int argc, char* argv[])
for (struct addrinfo* current = result; current != NULL; current = current->ai_next) {
if (current->ai_family == AF_INET6) {
fprintf(CONSOLE_OUT, "Testing GameStream connectivity over the Internet using an IPv6 relay server...\n");
if (!IsTestServerReachable(current, 443)) {
fprintf(CONSOLE_OUT, "Skipping unreachable IPv6 relay server...\n");
continue;
}
testServerWasReachable = true;
// Pass the portMsgBuf only if we've detected an IPv6-only setup. Otherwise, we want to preserve
// the failing ports from the IPv4 to display in the error dialog.
if (TestAllPorts((PSOCKADDR_STORAGE)current->ai_addr,
@@ -1333,32 +1481,45 @@ int main(int argc, char* argv[])
// no IPv6 firewall (hopefully not) or that we were able to talk to a PCP/IGDv6 gateway to allow us through. Hopefully if we have
// a gateway that is unresponsive to UPnP/NAT-PMP, we wouldn't even be able to establish this connection so we would inherently fall
// to the checks below for IPv4 issues.
if (IsDoubleNAT(&locallyReportedWanAddr) || igdDisconnected || IsPossibleCGN(&locallyReportedWanAddr) || ss.ss_family == AF_INET6 || allPortsFailedOnV4) {
if (IsDoubleNAT(&locallyReportedWanAddr) || igdDisconnected || IsCGN(&locallyReportedWanAddr) || ss.ss_family == AF_INET6 || allPortsFailedOnV4) {
snprintf(msgBuf, sizeof(msgBuf), "This PC has limited connectivity for Internet hosting. It will work only for clients on certain networks.\n\n"
"If you want to try streaming with this configuration, you must pair Moonlight to your gaming PC from your home network before trying to stream over the Internet.\n\n"
"To get full connectivity, please contact your ISP and ask for a \"public IP address\" which they may offer for free upon request. For more information and workarounds, click the Help button.");
"To get full connectivity, please contact your ISP and ask for a \"public IPv4 address\" which they may offer for free upon request. For more information and workarounds, click the Help button.");
DisplayMessage(msgBuf, "https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#limited-connectivity-for-hosting-error", MpWarn);
freeaddrinfo(result);
return 0;
}
}
else {
// Tested against a working server and it failed
break;
}
}
}
freeaddrinfo(result);
}
if (!testServerWasReachable) {
snprintf(msgBuf, sizeof(msgBuf), "None of Moonlight's connection testing servers were reachable from your PC. Check your Internet connection and try again later.");
DisplayMessage(msgBuf);
return 0;
}
}
// Many UPnP devices report IGD disconnected when double-NATed. If it was really offline,
// we probably would not have even gotten past STUN.
if (IsDoubleNAT(&locallyReportedWanAddr) || igdDisconnected) {
//
// We try to tell double-NAT from CGN by checking if IPv6 connectivity is available. If it
// is, we assume we're in a DS-Lite or similar configuration. If not, we'll assume it's a
// real double-NAT setup.
if (IsCGN(&locallyReportedWanAddr) || ((IsDoubleNAT(&locallyReportedWanAddr) || igdDisconnected) && hasV6Connectivity)) {
snprintf(msgBuf, sizeof(msgBuf), "Your ISP is running a Carrier-Grade NAT that is preventing you from hosting services like Moonlight on the Internet.\n\n"
"Ask your ISP for a \"public IPv4 address\" which they may offer for free upon request. For more information and workarounds, click the Help button.");
DisplayMessage(msgBuf, "https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#carrier-grade-nat-error");
}
else if ((IsDoubleNAT(&locallyReportedWanAddr) || igdDisconnected) /* && !hasV6Connectivity */) {
snprintf(msgBuf, sizeof(msgBuf), "Your router appears be connected to the Internet through another router. Click the Help button for guidance on fixing this issue.");
DisplayMessage(msgBuf, "https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#connected-through-another-router-error");
}
else if (IsPossibleCGN(&locallyReportedWanAddr)) {
snprintf(msgBuf, sizeof(msgBuf), "Your ISP is running a Carrier-Grade NAT that is preventing you from hosting services like Moonlight on the Internet.\n\n"
"Ask your ISP for a \"public IP address\" which they may offer for free upon request. For more information and workarounds, click the Help button.");
DisplayMessage(msgBuf, "https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#carrier-grade-nat-error");
}
else {
snprintf(msgBuf, sizeof(msgBuf), "Internet GameStream connectivity check failed.\n\n"
"First, try restarting your router. If that fails, check that UPnP is enabled in your router settings. For more information and workarounds, click the Help button.\n\n"
+2 -2
View File
@@ -1,7 +1,7 @@
#pragma once
#define VER_VERSION 5,0,0,0
#define VER_VERSION_STR "5.0.0.0"
#define VER_VERSION 5,2,0,0
#define VER_VERSION_STR "5.2.0.0"
#define VER_COMPANYNAME_STR "Moonlight Game Streaming Project"
#define VER_PRODUCTNAME_STR "Moonlight Internet Hosting Tool"