#define _CRT_RAND_S #define _CRT_SECURE_NO_WARNINGS #include #define WIN32_LEAN_AND_MEAN #define _WINSOCK_DEPRECATED_NO_WARNINGS #include #include #include #include #include #include #include #include #include "..\version.h" #pragma comment(lib, "miniupnpc.lib") #pragma comment(lib, "libnatpmp.lib") #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "wininet.lib") #define MINIUPNP_STATICLIB #include #include #include #define NATPMP_STATICLIB #include bool getExternalAddressPortIP4(int proto, unsigned short localPort, PSOCKADDR_IN wanAddr); static struct port_entry { int proto; int port; bool withServer; } k_Ports[] = { {IPPROTO_TCP, 47984, false}, {IPPROTO_TCP, 47989, false}, {IPPROTO_TCP, 48010, true}, {IPPROTO_UDP, 47998, true}, {IPPROTO_UDP, 47999, true}, {IPPROTO_UDP, 48000, true}, #if 0 // These are not currently used, so let's // avoid testing them for now. {IPPROTO_UDP, 48002, true}, {IPPROTO_UDP, 48010, true} #endif }; char logFilePath[MAX_PATH + 1]; enum MessagePriority { MpInfo, MpWarn, MpError }; VOID CALLBACK MsgBoxHelpCallback(LPHELPINFO lpHelpInfo) { const char* helpUrl = (const char*)lpHelpInfo->dwContextId; if (!helpUrl) { return; } // It's recommended to initialize COM before calling ShellExecute() CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); ShellExecuteA(nullptr, "open", helpUrl, nullptr, nullptr, SW_SHOWNORMAL); } void DisplayMessage(const char* message, const char* helpUrl = nullptr, MessagePriority priority = MpError, bool terminal = true) { printf("%s\n", message); if (terminal) { char missPath[MAX_PATH + 1]; FILE* f; printf("--------------- CURRENT MISS LOG -------------------\n"); ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-current.log", missPath, sizeof(missPath)); f = fopen(missPath, "r"); if (f != nullptr) { char buffer[1024]; while (!feof(f)) { int bytesRead = fread(buffer, 1, ARRAYSIZE(buffer), f); fwrite(buffer, 1, bytesRead, stdout); } fclose(f); } else { printf("Failed to find current MISS log\n"); } printf("\n----------------- OLD MISS LOG ---------------------\n"); ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-old.log", missPath, sizeof(missPath)); f = fopen(missPath, "r"); if (f != nullptr) { char buffer[1024]; while (!feof(f)) { int bytesRead = fread(buffer, 1, ARRAYSIZE(buffer), f); fwrite(buffer, 1, bytesRead, stdout); } fclose(f); } else { printf("Failed to find old MISS log\n"); } fflush(stdout); } MSGBOXPARAMSA msgParams; msgParams.cbSize = sizeof(msgParams); msgParams.hwndOwner = nullptr; msgParams.hInstance = nullptr; msgParams.lpszText = message; msgParams.lpszCaption = "Moonlight Internet Streaming Tester"; msgParams.dwStyle = MB_OK | MB_TOPMOST | MB_SETFOREGROUND; if (helpUrl) { msgParams.dwStyle |= MB_HELP; } switch (priority) { case MpInfo: msgParams.dwStyle |= MB_ICONINFORMATION; break; case MpWarn: msgParams.dwStyle |= MB_ICONWARNING; break; case MpError: msgParams.dwStyle |= MB_ICONERROR; break; } msgParams.lpfnMsgBoxCallback = MsgBoxHelpCallback; msgParams.dwContextHelpId = (DWORD_PTR)helpUrl; msgParams.dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); MessageBoxIndirectA(&msgParams); if (priority != MpInfo && terminal) { UINT flags = MB_YESNO | MB_TOPMOST | MB_SETFOREGROUND | MB_ICONINFORMATION; switch (MessageBoxA(nullptr, "Would you like to view the troubleshooting log?", "Moonlight Internet Streaming Tester", flags)) { case IDYES: // It's recommended to initialize COM before calling ShellExecute() CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); ShellExecuteA(nullptr, "open", logFilePath, nullptr, nullptr, SW_SHOWNORMAL); break; } } } bool IsGameStreamEnabled() { DWORD error; DWORD enabled; DWORD len; HKEY key; error = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\NVIDIA Corporation\\NvStream", 0, KEY_READ | KEY_WOW64_64KEY, &key); if (error != ERROR_SUCCESS) { printf("RegOpenKeyEx() failed: %d\n", error); 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.", "https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide"); return false; } len = sizeof(enabled); error = RegQueryValueExA(key, "EnableStreaming", nullptr, nullptr, (LPBYTE)&enabled, &len); RegCloseKey(key); if (error != ERROR_SUCCESS || !enabled) { // GFE may not even write EnableStreaming until the user enables GameStream for the first time if (error != ERROR_SUCCESS) { printf("RegQueryValueExA() failed: %d\n", error); } DisplayMessage("GameStream is not enabled in GeForce Experience. Please open GeForce Experience settings, navigate to the Shield tab, and turn GameStream on.", "https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide"); return false; } else { printf("GeForce Experience installed and GameStream is enabled\n"); return true; } } enum PortTestStatus { PortTestOk, PortTestError, PortTestUnknown }; PortTestStatus TestPort(PSOCKADDR_STORAGE addr, int proto, int port, bool withServer) { SOCKET clientSock = INVALID_SOCKET, serverSock = INVALID_SOCKET; int err; clientSock = socket(addr->ss_family, proto == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM, proto); if (clientSock == INVALID_SOCKET) { printf("socket() failed: %d\n", WSAGetLastError()); return PortTestError; } if (withServer) { serverSock = socket(addr->ss_family, proto == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM, proto); if (serverSock == INVALID_SOCKET) { printf("socket() failed: %d\n", WSAGetLastError()); closesocket(clientSock); return PortTestError; } SOCKADDR_IN sin = {}; sin.sin_family = AF_INET; sin.sin_port = htons(port); err = bind(serverSock, (struct sockaddr*)&sin, sizeof(sin)); if (err == SOCKET_ERROR) { if (WSAGetLastError() == WSAEADDRINUSE) { // If someone is already listening (perhaps GFE is currently streaming), // we can proceed if it's a TCP connection. if (proto == IPPROTO_TCP) { closesocket(serverSock); serverSock = INVALID_SOCKET; } else { // We can't continue to test for UDP ports. printf("Unknown (in use)\n"); closesocket(clientSock); closesocket(serverSock); return PortTestUnknown; } } else { printf("bind() failed: %d\n", WSAGetLastError()); closesocket(clientSock); closesocket(serverSock); return PortTestError; } } if (proto == IPPROTO_TCP && serverSock != INVALID_SOCKET) { err = listen(serverSock, 1); if (err == SOCKET_ERROR) { printf("listen() failed: %d\n", WSAGetLastError()); closesocket(clientSock); closesocket(serverSock); return PortTestError; } } } ULONG nbIo = 1; err = ioctlsocket(clientSock, FIONBIO, &nbIo); if (err == SOCKET_ERROR) { printf("ioctlsocket() failed: %d\n", WSAGetLastError()); closesocket(clientSock); if (serverSock != INVALID_SOCKET) { closesocket(serverSock); } return PortTestError; } SOCKADDR_IN6 sin6; int addrLen = addr->ss_family == AF_INET ? sizeof(SOCKADDR_IN) : sizeof(SOCKADDR_IN6); RtlCopyMemory(&sin6, addr, addrLen); sin6.sin6_port = htons(port); if (proto == IPPROTO_TCP) { err = connect(clientSock, (struct sockaddr*)&sin6, addrLen); if (err == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) { printf("connect() failed: %d\n", WSAGetLastError()); } else { struct timeval timeout = {}; fd_set fds; FD_ZERO(&fds); if (serverSock != INVALID_SOCKET) { FD_SET(serverSock, &fds); } else { FD_SET(clientSock, &fds); } // If we have a server socket, listen for the accept() instead of the // connect() so we can be compatible with the loopback relay. timeout.tv_sec = 3; err = select(0, serverSock != INVALID_SOCKET ? &fds : nullptr, serverSock == INVALID_SOCKET ? &fds : nullptr, nullptr, &timeout); if (err == 1) { // Our FD was signalled for connect() or accept() completion printf("Success\n"); } else if (err == 0) { // Timed out printf("Timeout\n"); } else { printf("select() failed: %d\n", WSAGetLastError()); } } closesocket(clientSock); if (serverSock != INVALID_SOCKET) { closesocket(serverSock); } return err == 1 ? PortTestOk : PortTestError; } else { const char testMsg[] = "moonlight-test"; err = sendto(clientSock, testMsg, sizeof(testMsg), 0, (struct sockaddr*)&sin6, addrLen); if (err == SOCKET_ERROR) { printf("sendto() failed: %d\n", WSAGetLastError()); closesocket(clientSock); closesocket(serverSock); return PortTestError; } struct timeval timeout = {}; fd_set fds; FD_ZERO(&fds); FD_SET(serverSock, &fds); timeout.tv_sec = 3; err = select(0, &fds, nullptr, nullptr, &timeout); if (err == 1) { // Our FD was signalled for data available printf("Success\n"); } else if (err == 0) { // Timed out printf("Timeout\n"); } else { printf("select() failed: %d\n", WSAGetLastError()); } closesocket(clientSock); closesocket(serverSock); return err == 1 ? PortTestOk : PortTestError; } } PortTestStatus TestHttpPort(PSOCKADDR_STORAGE addr, int port) { HINTERNET hInternet; HINTERNET hRequest; char url[1024]; hInternet = InternetOpenA("MIST", 0, nullptr, nullptr, 0); if (hInternet == nullptr) { printf("InternetOpen() failed: %d\n", GetLastError()); return PortTestError; } char addrStr[64]; inet_ntop(AF_INET, &((struct sockaddr_in*)addr)->sin_addr, addrStr, sizeof(addrStr)); sprintf(url, "%s://%s:%d/", port == 47989 ? "http" : "https", addrStr, port); hRequest = InternetOpenUrlA(hInternet, url, nullptr, 0, INTERNET_FLAG_IGNORE_CERT_DATE_INVALID | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_RELOAD, NULL); if (hRequest == nullptr) { if (GetLastError() == ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED) { // This is expected for our HTTPS connection printf("Success\n"); } else { // CANNOT_CONNECT is the "expected" error if (GetLastError() == ERROR_INTERNET_CANNOT_CONNECT) { printf("Failed\n"); } else { printf("Failed: %d\n", GetLastError()); } InternetCloseHandle(hInternet); return PortTestError; } } else { printf("Success\n"); InternetCloseHandle(hRequest); } InternetCloseHandle(hInternet); return PortTestOk; } bool TestAllPorts(PSOCKADDR_STORAGE addr, char* portMsg, int portMsgLen) { bool ret = true; for (int i = 0; i < ARRAYSIZE(k_Ports); i++) { printf("Testing %s %d...", k_Ports[i].proto == IPPROTO_TCP ? "TCP" : "UDP", k_Ports[i].port); PortTestStatus status; status = TestPort(addr, k_Ports[i].proto, k_Ports[i].port, k_Ports[i].withServer); if (status != PortTestError && !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. printf("Testing TCP port %d with HTTP traffic...", k_Ports[i].port); status = TestHttpPort(addr, k_Ports[i].port); } if (status != PortTestOk) { // If we got an unknown result, assume it matches with whatever // we've gotten so far. if (status == PortTestError || !ret) { int msgLen = snprintf(portMsg, portMsgLen, "%s %d\n", k_Ports[i].proto == IPPROTO_TCP ? "TCP" : "UDP", k_Ports[i].port); portMsg += msgLen; portMsgLen -= msgLen; // Keep going to check all ports and report the failing ones ret = false; } } } return ret; } bool FindLocalInterfaceIP4Address(PSOCKADDR_IN addr) { SOCKET s; struct hostent* host; printf("Finding local IP address..."); host = gethostbyname("moonlight-stream.org"); if (host == nullptr) { printf("gethostbyname() failed: %d\n", WSAGetLastError()); return false; } s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (s == INVALID_SOCKET) { printf("socket() failed: %d\n", WSAGetLastError()); return false; } SOCKADDR_IN sin = {}; sin.sin_family = AF_INET; sin.sin_port = htons(443); sin.sin_addr = *(struct in_addr*)host->h_addr; int err = connect(s, (struct sockaddr*)&sin, sizeof(sin)); if (err == SOCKET_ERROR) { printf("connect() failed: %d\n", WSAGetLastError()); closesocket(s); return false; } // Determine which local interface we bound to int nameLen = sizeof(*addr); err = getsockname(s, (struct sockaddr*)addr, &nameLen); if (err == SOCKET_ERROR) { printf("getsockname() failed: %d\n", WSAGetLastError()); closesocket(s); return false; } char addrStr[64]; inet_ntop(AF_INET, &addr->sin_addr, addrStr, sizeof(addrStr)); printf("%s\n", addrStr); return true; } enum UPnPPortStatus { NOT_FOUND, OK, CONFLICTED, ERRORED }; UPnPPortStatus UPnPCheckPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const char* myAddr, int port, char* conflictMessage) { char intClient[16]; char intPort[6]; char desc[80]; char enabled[4]; char leaseDuration[16]; const char* protoStr; char portStr[6]; snprintf(portStr, sizeof(portStr), "%d", port); switch (proto) { case IPPROTO_TCP: protoStr = "TCP"; break; case IPPROTO_UDP: protoStr = "UDP"; break; default: assert(false); return ERRORED; } printf("Checking for UPnP port mapping for %s %s -> %s...", protoStr, portStr, myAddr); int err = UPNP_GetSpecificPortMappingEntry( urls->controlURL, data->first.servicetype, portStr, protoStr, nullptr, intClient, intPort, desc, enabled, leaseDuration); if (err == 714) { // NoSuchEntryInArray printf("NOT FOUND\n"); return NOT_FOUND; } else if (err == UPNPCOMMAND_SUCCESS) { if (!strcmp(myAddr, intClient)) { printf("OK\n"); return OK; } else { printf("CONFLICT - %s %s\n", desc, intClient); snprintf(conflictMessage, 128, "%s (%s)", desc, intClient); return CONFLICTED; } } else { printf("ERROR %d\n", err); return ERRORED; } } bool CheckWANAccess(PSOCKADDR_IN wanAddr, PSOCKADDR_IN reportedWanAddr, bool* foundPortForwardingRules, bool* igdDisconnected) { natpmp_t natpmp; bool foundUpnpIgd = false; *foundPortForwardingRules = false; *igdDisconnected = false; bool gotReportedWanAddress = false; int natPmpErr = initnatpmp(&natpmp, 0, 0); if (natPmpErr != 0) { printf("initnatpmp() failed: %d\n", natPmpErr); } else { natPmpErr = sendpublicaddressrequest(&natpmp); if (natPmpErr < 0) { printf("sendpublicaddressrequest() failed: %d\n", natPmpErr); closenatpmp(&natpmp); } } { int upnpErr; struct UPNPDev* ipv4Devs = upnpDiscoverAll(5000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 0, 2, &upnpErr); struct UPNPUrls urls; struct IGDdatas data; char myAddr[128]; char wanAddrStr[128]; int ret = UPNP_GetValidIGD(ipv4Devs, &urls, &data, myAddr, sizeof(myAddr)); if (ret != 0) { // Connected or disconnected IGD if (ret == 1 || ret == 2) { foundUpnpIgd = true; 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); if (ret == UPNPCOMMAND_SUCCESS && strlen(wanAddrStr) > 0) { reportedWanAddr->sin_addr.S_un.S_addr = inet_addr(wanAddrStr); printf("%s\n", wanAddrStr); if (reportedWanAddr->sin_addr.S_un.S_addr != 0) { gotReportedWanAddress = true; } } else { printf("FAILED %d\n", ret); } char conflictMessage[512]; *foundPortForwardingRules = true; for (int i = 0; i < ARRAYSIZE(k_Ports); i++) { char conflictEntry[128]; UPnPPortStatus status = UPnPCheckPort(&urls, &data, k_Ports[i].proto, myAddr, k_Ports[i].port, conflictEntry); if (status != OK) { *foundPortForwardingRules = false; } switch (status) { case CONFLICTED: snprintf(conflictMessage, sizeof(conflictMessage), "Detected a port forwarding conflict with another PC on your network: %s\n\n" "Remove that PC from your network or uninstall the Moonlight Internet Streaming Service from it, then restart your router.", conflictEntry); DisplayMessage(conflictMessage); return false; default: continue; } } } else { printf("No UPnP IGD detected\n"); } FreeUPNPUrls(&urls); } else { printf("No UPnP devices detected\n"); } } // Use the delay of upnpDiscoverAll() to also allow the NAT-PMP endpoint time to respond if (natPmpErr >= 0) { printf("Detecting WAN IP address via NAT-PMP..."); natpmpresp_t response; natPmpErr = readnatpmpresponseorretry(&natpmp, &response); closenatpmp(&natpmp); if (natPmpErr == 0) { char addrStr[64]; reportedWanAddr->sin_addr = response.pnu.publicaddress.addr; inet_ntop(AF_INET, &response.pnu.publicaddress.addr, addrStr, sizeof(addrStr)); printf("%s\n", addrStr); if (reportedWanAddr->sin_addr.S_un.S_addr != 0) { gotReportedWanAddress = true; if (!foundUpnpIgd) { // Just in case we have a NAT-PMP gateway that doesn't do NAT reflection // let's assume it's all okay if we got any response at all *foundPortForwardingRules = true; } } } else { printf("FAILED %d\n", natPmpErr); } } printf("Detecting WAN IP address via STUN..."); if (!getExternalAddressPortIP4(IPPROTO_UDP, 0, wanAddr) && !getExternalAddressPortIP4(IPPROTO_TCP, 0, wanAddr)) { DisplayMessage("Unable to determine your public IP address. Please check your Internet connection or try again in a few minutes."); return false; } else { char addrStr[64]; inet_ntop(AF_INET, &wanAddr->sin_addr, addrStr, sizeof(addrStr)); printf("%s\n", addrStr); if (!gotReportedWanAddress) { // If we didn't get anything from UPnP or NAT-PMP, just populate the reported // address with what we got from STUN *reportedWanAddr = *wanAddr; } } return true; } bool IsPossibleCGN(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) { return true; } return false; } bool IsDoubleNAT(PSOCKADDR_IN wanAddr) { DWORD addr = htonl(wanAddr->sin_addr.S_un.S_addr); // 10.0.0.0/8 if ((addr & 0xFF000000) == 0x0A000000) { return true; } // 172.16.0.0/12 else if ((addr & 0xFFF00000) == 0xAC100000) { return true; } // 192.168.0.0/16 else if ((addr & 0xFFFF0000) == 0xC0A80000) { return true; } return false; } int main(int argc, char* argv[]) { WSADATA wsaData; SYSTEMTIME time; char timeString[MAX_PATH + 1] = {}; char tempPath[MAX_PATH + 1]; GetTempPathA(sizeof(tempPath), tempPath); snprintf(logFilePath, sizeof(logFilePath), "%s\\%s", tempPath, "mis-test.log"); freopen(logFilePath, "w", stdout); // Print a log header printf("Moonlight Internet Streaming Tester v" VER_VERSION_STR "\n"); // Print the current time GetSystemTime(&time); GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &time, "hh':'mm':'ss tt", timeString, ARRAYSIZE(timeString)); printf("The current UTC time is: %s\n", timeString); // Print a console header fprintf(stderr, "Moonlight Internet Streaming Tester v" VER_VERSION_STR "\n\n"); int err = WSAStartup(MAKEWORD(2, 2), &wsaData); if (err != NO_ERROR) { DisplayMessage("Unable to initialize WinSock"); return err; } fprintf(stderr, "Checking if GameStream is enabled...\n"); // First check if GameStream is enabled if (!IsGameStreamEnabled()) { return -1; } union { SOCKADDR_STORAGE ss; SOCKADDR_IN sin; SOCKADDR_IN6 sin6; }; char msgBuf[2048]; char portMsgBuf[512]; fprintf(stderr, "Testing local GameStream connectivity...\n"); // Try to connect via IPv4 loopback ss = {}; sin.sin_family = AF_INET; sin.sin_addr = in4addr_loopback; printf("Testing GameStream ports via loopback\n"); if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf))) { snprintf(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, "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting"); return -1; } if (!FindLocalInterfaceIP4Address(&sin)) { DisplayMessage("Unable to perform GameStream connectivity check. Please check your Internet connection and try again."); return -1; } fprintf(stderr, "Testing network GameStream connectivity...\n"); // Try to connect via LAN IPv4 address printf("Testing GameStream ports via local network\n"); if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf))) { snprintf(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, "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting"); return -1; } fprintf(stderr, "Detecting public IP address...\n"); bool rulesFound, igdDisconnected; SOCKADDR_IN locallyReportedWanAddr; if (!CheckWANAccess(&sin, &locallyReportedWanAddr, &rulesFound, &igdDisconnected)) { return -1; } // 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((PSOCKADDR_STORAGE)&locallyReportedWanAddr, portMsgBuf, sizeof(portMsgBuf)); printf("Detected inconsistency between UPnP/NAT-PMP and STUN reported WAN addresses!\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 printf("Testing GameStream ports via STUN-reported WAN address\n"); if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf))) { // 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) { 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. Click the Help button for guidance on fixing this issue."); DisplayMessage(msgBuf, "https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#carrier-grade-nat-error"); } else { struct hostent* host; // We can get here if the router doesn't support NAT reflection. // We'll need to call out to our loopback server to get a second opinion. fprintf(stderr, "Testing Internet GameStream connectivity with loopback server...\n"); printf("Testing GameStream ports via loopback server\n"); host = gethostbyname("loopback.moonlight-stream.org"); if (host != nullptr) { sin.sin_addr = *(struct in_addr*)host->h_addr; if (TestAllPorts((PSOCKADDR_STORAGE)&sin, portMsgBuf, sizeof(portMsgBuf))) { goto AllTestsPassed; } } else { printf("gethostbyname() failed: %d\n", WSAGetLastError()); } } snprintf(msgBuf, sizeof(msgBuf), "Internet GameStream connectivity check failed. Click the Help button for guidance on fixing this issue.\n\nThe following ports were not forwarded properly:\n%s", portMsgBuf); DisplayMessage(msgBuf, "https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#internet-gamestream-connectivity-check-error"); return -1; } AllTestsPassed: snprintf(msgBuf, sizeof(msgBuf), "All tests passed! If Moonlight doesn't automatically connect outside your network, you can type the following address into Moonlight's Add PC dialog: %s", wanAddrStr); DisplayMessage(msgBuf, nullptr, MpInfo); return 0; }