Improve reliability with NAT reflection disabled, UPnP IGD reporting disconnected, double-NATs, and CGNs

This commit is contained in:
Cameron Gutman 2018-10-21 21:04:08 -07:00
parent 7bc806edb4
commit 45b624c203

View File

@ -65,33 +65,53 @@ static struct port_entry {
char logFilePath[MAX_PATH + 1]; char logFilePath[MAX_PATH + 1];
void DisplayMessage(const char* message, bool error = true) enum MessagePriority {
MpInfo,
MpWarn,
MpError
};
void DisplayMessage(const char* message, MessagePriority priority = MpError, bool terminal = true)
{ {
printf("%s\n", message); printf("%s\n", message);
printf("--------------- MISS LOG -------------------\n");
char missPath[MAX_PATH + 1]; if (terminal) {
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-current.log", missPath, sizeof(missPath)); printf("--------------- MISS LOG -------------------\n");
FILE* f = fopen(missPath, "r");
if (f != nullptr) { char missPath[MAX_PATH + 1];
char buffer[1024]; ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-current.log", missPath, sizeof(missPath));
while (!feof(f)) { FILE* f = fopen(missPath, "r");
int bytesRead = fread(buffer, 1, ARRAYSIZE(buffer), f); if (f != nullptr) {
fwrite(buffer, 1, bytesRead, stdout); char buffer[1024];
while (!feof(f)) {
int bytesRead = fread(buffer, 1, ARRAYSIZE(buffer), f);
fwrite(buffer, 1, bytesRead, stdout);
}
fclose(f);
} }
fclose(f); else {
} printf("Failed to find MISS log\n");
else { }
printf("Failed to find MISS log\n");
fflush(stdout);
} }
fflush(stdout);
DWORD flags = MB_OK | MB_TOPMOST | MB_SETFOREGROUND; DWORD flags = MB_OK | MB_TOPMOST | MB_SETFOREGROUND;
flags |= error ? MB_ICONERROR : MB_ICONINFORMATION; switch (priority) {
case MpInfo:
flags |= MB_ICONINFORMATION;
break;
case MpWarn:
flags |= MB_ICONWARNING;
break;
case MpError:
flags |= MB_ICONERROR;
break;
}
MessageBoxA(nullptr, message, "Moonlight Internet Streaming Tester", flags); MessageBoxA(nullptr, message, "Moonlight Internet Streaming Tester", flags);
if (error) { if (priority == MpError && terminal) {
flags = MB_YESNO | MB_TOPMOST | MB_SETFOREGROUND | MB_ICONINFORMATION; flags = MB_YESNO | MB_TOPMOST | MB_SETFOREGROUND | MB_ICONINFORMATION;
switch (MessageBoxA(nullptr, "Would you like to view the troubleshooting log?", switch (MessageBoxA(nullptr, "Would you like to view the troubleshooting log?",
"Moonlight Internet Streaming Tester", flags)) "Moonlight Internet Streaming Tester", flags))
@ -115,7 +135,7 @@ bool IsGameStreamEnabled()
error = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\NVIDIA Corporation\\NvStream", 0, KEY_READ | KEY_WOW64_64KEY, &key); error = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\NVIDIA Corporation\\NvStream", 0, KEY_READ | KEY_WOW64_64KEY, &key);
if (error != ERROR_SUCCESS) { if (error != ERROR_SUCCESS) {
printf("RegOpenKeyEx() failed: %d\n", error); printf("RegOpenKeyEx() failed: %d\n", error);
DisplayMessage("GeForce Experience is not installed. Please install GeForce Experience to use Moonlight."); DisplayMessage("GeForce Experience was not detected on this PC. Make sure you're installing this utility on your GeForce GameStream-compatible PC, not the device running Moonlight.");
return false; return false;
} }
@ -253,7 +273,7 @@ PortTestStatus TestPort(PSOCKADDR_STORAGE addr, int proto, int port, bool withSe
return err == 1 ? PortTestOk : PortTestError; return err == 1 ? PortTestOk : PortTestError;
} }
else { else {
const char testMsg[] = "mist-test"; const char testMsg[] = "moonlight-test";
err = sendto(clientSock, testMsg, sizeof(testMsg), 0, (struct sockaddr*)&sin6, addrLen); err = sendto(clientSock, testMsg, sizeof(testMsg), 0, (struct sockaddr*)&sin6, addrLen);
if (err == SOCKET_ERROR) { if (err == SOCKET_ERROR) {
printf("sendto() failed: %d\n", WSAGetLastError()); printf("sendto() failed: %d\n", WSAGetLastError());
@ -289,13 +309,10 @@ PortTestStatus TestPort(PSOCKADDR_STORAGE addr, int proto, int port, bool withSe
} }
} }
bool TestAllPorts(PSOCKADDR_STORAGE addr, const char* baseMessage, char* message, int messageLength) bool TestAllPorts(PSOCKADDR_STORAGE addr, char* portMsg, int portMsgLen)
{ {
strcpy_s(message, messageLength, baseMessage);
message += strlen(baseMessage);
messageLength -= strlen(baseMessage);
bool ret = true; bool ret = true;
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) { for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
printf("Testing %s %d...", printf("Testing %s %d...",
k_Ports[i].proto == IPPROTO_TCP ? "TCP" : "UDP", k_Ports[i].proto == IPPROTO_TCP ? "TCP" : "UDP",
@ -305,11 +322,11 @@ bool TestAllPorts(PSOCKADDR_STORAGE addr, const char* baseMessage, char* message
// If we got an unknown result, assume it matches with whatever // If we got an unknown result, assume it matches with whatever
// we've gotten so far. // we've gotten so far.
if (status == PortTestError || !ret) { if (status == PortTestError || !ret) {
int msgLen = snprintf(message, messageLength, "%s %d\n", int msgLen = snprintf(portMsg, portMsgLen, "%s %d\n",
k_Ports[i].proto == IPPROTO_TCP ? "TCP" : "UDP", k_Ports[i].proto == IPPROTO_TCP ? "TCP" : "UDP",
k_Ports[i].port); k_Ports[i].port);
message += msgLen; portMsg += msgLen;
messageLength -= msgLen; portMsgLen -= msgLen;
// Keep going to check all ports and report the failing ones // Keep going to check all ports and report the failing ones
ret = false; ret = false;
@ -524,14 +541,14 @@ bool STUNFindWanAddress(PSOCKADDR_IN wanAddr)
return true; return true;
} }
bool CheckWANAccess(PSOCKADDR_IN wanAddr, bool* foundPortForwardingRules) bool CheckWANAccess(PSOCKADDR_IN wanAddr, PSOCKADDR_IN reportedWanAddr, bool* foundPortForwardingRules, bool* igdDisconnected)
{ {
natpmp_t natpmp; natpmp_t natpmp;
*foundPortForwardingRules = false; *foundPortForwardingRules = false;
*igdDisconnected = false;
printf("Finding WAN IP address..."); bool gotReportedWanAddress = false;
bool gotWanAddress = false;
int natPmpErr = initnatpmp(&natpmp, 0, 0); int natPmpErr = initnatpmp(&natpmp, 0, 0);
if (natPmpErr != 0) { if (natPmpErr != 0) {
printf("initnatpmp() failed: %d\n", natPmpErr); printf("initnatpmp() failed: %d\n", natPmpErr);
@ -556,15 +573,23 @@ bool CheckWANAccess(PSOCKADDR_IN wanAddr, bool* foundPortForwardingRules)
if (ret != 0) { if (ret != 0) {
// Connected or disconnected IGD // Connected or disconnected IGD
if (ret == 1 || ret == 2) { if (ret == 1 || ret == 2) {
if (ret == 2) {
*igdDisconnected = true;
}
printf("Discovered UPnP IGD at: %s\n", urls.controlURL);
printf("Detecting WAN IP address via UPnP...");
ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, wanAddrStr); ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, wanAddrStr);
if (ret == UPNPCOMMAND_SUCCESS && strlen(wanAddrStr) > 0) { if (ret == UPNPCOMMAND_SUCCESS && strlen(wanAddrStr) > 0) {
wanAddr->sin_addr.S_un.S_addr = inet_addr(wanAddrStr); reportedWanAddr->sin_addr.S_un.S_addr = wanAddr->sin_addr.S_un.S_addr = inet_addr(wanAddrStr);
printf("%s (UPnP)\n", wanAddrStr); printf("%s\n", wanAddrStr);
if (wanAddr->sin_addr.S_un.S_addr != 0) { if (wanAddr->sin_addr.S_un.S_addr != 0) {
gotWanAddress = true; gotReportedWanAddress = true;
} }
} }
else {
printf("FAILED %d\n", ret);
}
char conflictMessage[512]; char conflictMessage[512];
*foundPortForwardingRules = true; *foundPortForwardingRules = true;
@ -588,6 +613,9 @@ bool CheckWANAccess(PSOCKADDR_IN wanAddr, bool* foundPortForwardingRules)
} }
} }
} }
else {
printf("No UPnP IGD detected\n");
}
FreeUPNPUrls(&urls); FreeUPNPUrls(&urls);
} }
@ -595,36 +623,46 @@ bool CheckWANAccess(PSOCKADDR_IN wanAddr, bool* foundPortForwardingRules)
// Use the delay of upnpDiscoverAll() to also allow the NAT-PMP endpoint time to respond // Use the delay of upnpDiscoverAll() to also allow the NAT-PMP endpoint time to respond
if (natPmpErr >= 0) { if (natPmpErr >= 0) {
printf("Detecting WAN IP address via NAT-PMP...");
natpmpresp_t response; natpmpresp_t response;
natPmpErr = readnatpmpresponseorretry(&natpmp, &response); natPmpErr = readnatpmpresponseorretry(&natpmp, &response);
closenatpmp(&natpmp); closenatpmp(&natpmp);
if (natPmpErr == 0 && !gotWanAddress) { if (natPmpErr == 0) {
char addrStr[64]; char addrStr[64];
wanAddr->sin_addr = response.pnu.publicaddress.addr; reportedWanAddr->sin_addr = wanAddr->sin_addr = response.pnu.publicaddress.addr;
inet_ntop(AF_INET, &response.pnu.publicaddress.addr, addrStr, sizeof(addrStr)); inet_ntop(AF_INET, &response.pnu.publicaddress.addr, addrStr, sizeof(addrStr));
printf("%s (NAT-PMP)\n", addrStr); printf("%s\n", addrStr);
if (wanAddr->sin_addr.S_un.S_addr != 0) { if (wanAddr->sin_addr.S_un.S_addr != 0) {
gotWanAddress = true; gotReportedWanAddress = true;
} }
} }
else {
printf("FAILED %d\n", natPmpErr);
}
} }
if (!gotWanAddress) { printf("Detecting WAN IP address via STUN...");
if (!STUNFindWanAddress(wanAddr)) { if (!STUNFindWanAddress(wanAddr)) {
printf("FAILED\n"); if (!gotReportedWanAddress) {
DisplayMessage("MIST was unable to determine your public IP address. Please check your Internet connection."); DisplayMessage("Unable to determine your public IP address. Please check your Internet connection.");
return false; return false;
} }
}
else {
char addrStr[64]; char addrStr[64];
inet_ntop(AF_INET, &wanAddr->sin_addr, addrStr, sizeof(addrStr)); inet_ntop(AF_INET, &wanAddr->sin_addr, addrStr, sizeof(addrStr));
printf("%s (STUN)\n", addrStr); printf("%s (STUN)\n", addrStr);
return true;
} if (!gotReportedWanAddress) {
else { // If we didn't get anything from UPnP or NAT-PMP, just populate the reported
return true; // address with what we got from STUN
*reportedWanAddr = *wanAddr;
}
} }
return true;
} }
bool IsPossibleCGN(PSOCKADDR_IN wanAddr) bool IsPossibleCGN(PSOCKADDR_IN wanAddr)
@ -692,6 +730,7 @@ int main(int argc, char* argv[])
SOCKADDR_IN6 sin6; SOCKADDR_IN6 sin6;
}; };
char msgBuf[2048]; char msgBuf[2048];
char portMsgBuf[512];
fprintf(stderr, "Testing local GameStream connectivity...\n"); fprintf(stderr, "Testing local GameStream connectivity...\n");
@ -700,9 +739,10 @@ int main(int argc, char* argv[])
sin.sin_family = AF_INET; sin.sin_family = AF_INET;
sin.sin_addr = in4addr_loopback; sin.sin_addr = in4addr_loopback;
printf("Testing GameStream ports via loopback\n"); printf("Testing GameStream ports via loopback\n");
if (!TestAllPorts(&ss, if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf))) {
"Local GameStream connectivity check failed. Please try reinstalling GeForce Experience.\n\nThe following ports were not working:\n", snprintf(msgBuf, sizeof(msgBuf),
msgBuf, sizeof(msgBuf))) { "Local GameStream connectivity check failed. Please try reinstalling GeForce Experience.\n\nThe following ports were not working:\n%s",
portMsgBuf);
DisplayMessage(msgBuf); DisplayMessage(msgBuf);
return -1; return -1;
} }
@ -716,47 +756,69 @@ int main(int argc, char* argv[])
// Try to connect via LAN IPv4 address // Try to connect via LAN IPv4 address
printf("Testing GameStream ports via local network\n"); printf("Testing GameStream ports via local network\n");
if (!TestAllPorts(&ss, if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf))) {
"Local network GameStream connectivity check failed. Try temporarily disabling your firewall software or adding firewall exceptions for the following ports:\n", snprintf(msgBuf, sizeof(msgBuf),
msgBuf, sizeof(msgBuf))) { "Local network GameStream connectivity check failed. Try temporarily disabling your firewall software or adding firewall exceptions for the following ports:\n%s",
portMsgBuf);
DisplayMessage(msgBuf); DisplayMessage(msgBuf);
return -1; return -1;
} }
fprintf(stderr, "Detecting public IP address...\n"); fprintf(stderr, "Detecting public IP address...\n");
bool upnpRulesFound; bool upnpRulesFound, igdDisconnected;
if (!CheckWANAccess(&sin, &upnpRulesFound)) { SOCKADDR_IN locallyReportedWanAddr;
if (!CheckWANAccess(&sin, &locallyReportedWanAddr, &upnpRulesFound, &igdDisconnected)) {
return -1; return -1;
} }
if (igdDisconnected) {
DisplayMessage("Your router reports to be disconnected from the Internet. Make sure UPnP is enabled in your router settings. "
"If this message persists, make sure your router isn't connected to the Internet through another router. If it is, switch one of the routers to bridge mode.\n\n"
"Just in case this warning is due to a buggy router, the test will continue anyway.", MpWarn, false);
}
// Detect a double NAT by detecting STUN and and UPnP mismatches
if (sin.sin_addr.S_un.S_addr != locallyReportedWanAddr.sin_addr.S_un.S_addr) {
printf("Testing GameStream ports via UPnP/NAT-PMP reported WAN address\n");
// We don't actually care about the outcome here but it's nice to have in logs
// to determine whether solving the double NAT will actually make Moonlight work.
TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf));
printf("Detected inconsistency between UPnP/NAT-PMP and STUN reported WAN addresses!\n");
}
fprintf(stderr, "Testing Internet GameStream connectivity...\n"); fprintf(stderr, "Testing Internet GameStream connectivity...\n");
char wanAddrStr[64];
inet_ntop(AF_INET, &sin.sin_addr, wanAddrStr, sizeof(wanAddrStr));
// Try to connect via WAN IPv4 address // Try to connect via WAN IPv4 address
printf("Testing GameStream ports via WAN address\n"); printf("Testing GameStream ports via STUN-reported WAN address\n");
if (!TestAllPorts(&ss, if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf))) {
upnpRulesFound ? "Found UPnP rules, but they did not work correctly. Check for conflicting port forwarding entries in your router settings.\n\nThe following ports were not forwarded properly:\n" : if (IsDoubleNAT(&locallyReportedWanAddr)) {
"Internet GameStream connectivity check failed. Make sure UPnP is enabled in your router settings.\n\nThe following ports were not forwarded properly:\n", DisplayMessage("Your router appears be connected to the Internet through another router. This configuration breaks port forwarding. To resolve this, switch one of the routers into bridge mode.");
msgBuf, sizeof(msgBuf))) { }
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. Contact your ISP and ask for a dedicated public IP address.");
}
else if (igdDisconnected) {
snprintf(msgBuf, sizeof(msgBuf), "Internet GameStream connectivity check failed. Make sure UPnP is enabled in your router settings and that you don't have two devices acting as routers connected together.");
}
else if (upnpRulesFound) {
snprintf(msgBuf, sizeof(msgBuf), "Found UPnP rules, but we couldn't confirm that they were working. You can confirm by streaming from a different network and typing the following address into Moonlight's Add PC dialog: %s\n\n"
"If that doesn't work, check your router settings for any existing Moonlight port forwarding entries and delete them.", wanAddrStr);
}
else {
snprintf(msgBuf, sizeof(msgBuf), "Internet GameStream connectivity check failed. Make sure UPnP is enabled in your router settings.\n\nThe following ports were not forwarded properly:\n%s", portMsgBuf);
}
DisplayMessage(msgBuf); DisplayMessage(msgBuf);
return -1; return -1;
} }
// Check for double-NAT snprintf(msgBuf, sizeof(msgBuf), "All tests passed! You should be able to stream by typing the following address into Moonlight's Add PC dialog: %s", wanAddrStr);
if (IsDoubleNAT(&sin)) { DisplayMessage(msgBuf, MpInfo);
DisplayMessage("Your router appears be connected to another router. This configuration breaks port forwarding. To resolve this, switch one of the devices into bridge mode.");
return -1;
}
// Check for CGN
else if (IsPossibleCGN(&sin)) {
DisplayMessage("Your ISP is running a Carrier-Grade NAT. This prevents you from hosting services like GameStream. Contact your ISP to get a real public IP address.");
return -1;
}
char addrStr[64];
inet_ntop(AF_INET, &sin.sin_addr, addrStr, sizeof(addrStr));
snprintf(msgBuf, sizeof(msgBuf), "All tests passed! You should be able to stream by typing the following address into Moonlight's Add PC dialog: %s", addrStr);
DisplayMessage(msgBuf, false);
return 0; return 0;
} }