Compare commits

...
17 Commits
Author SHA1 Message Date
Cameron Gutman f1ca6a71f0 Version 5.3 2020-08-14 18:35:19 -07:00
Cameron Gutman ac92212464 Fix flip-flopped HTTP and HTTPS rule IDs 2020-08-14 17:57:45 -07:00
Cameron Gutman dbf43ac7a1 Don't attempt to relocate WoL port 9 2020-08-14 17:54:48 -07:00
Cameron Gutman 893aa76c9c Replace hardcoded constant with #define 2020-08-14 17:52:59 -07:00
Cameron Gutman 21d8c71a2c Log the internal port for the UPnP mappings 2020-08-14 17:51:06 -07:00
Cameron Gutman 41ef072c9b Validate the port number of loopback traffic 2020-08-14 17:48:55 -07:00
Cameron Gutman 53246bd4c5 Don't redirect stdout for standalone exe invocation 2020-08-14 17:48:14 -07:00
Cameron Gutman ac850e79d8 Add firewall rules for GameStream just in case GFE didn't 2020-08-12 20:54:34 -07:00
Cameron Gutman 944c8993e8 Elevate priorities for the UDP relay threads 2020-08-11 01:12:48 -07:00
Cameron Gutman b6508d9024 Remove superfluous select() call 2020-08-11 00:59:13 -07:00
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
9 changed files with 303 additions and 33 deletions
+37
View File
@@ -80,6 +80,43 @@
Name="Moonlight Internet Streaming Tester"/>
</File>
</Component>
<Component Id="GameStreamFirewallRules" Guid="{16CA2511-B533-46F8-8882-F8AEEFDFD7DF}" KeyPath="yes">
<fire:FirewallException Id="HttpsFwException"
Scope="any"
Port="47984"
Protocol="tcp"
Name="Moonlight - HTTPS"/>
<fire:FirewallException Id="HttpFwException"
Scope="any"
Port="47989"
Protocol="tcp"
Name="Moonlight - HTTP"/>
<fire:FirewallException Id="RtspFwException"
Scope="any"
Port="48010"
Protocol="tcp"
Name="Moonlight - RTSP"/>
<fire:FirewallException Id="VideoFwException"
Scope="any"
Port="47998"
Protocol="udp"
Name="Moonlight - Video"/>
<fire:FirewallException Id="ControlFwException"
Scope="any"
Port="47999"
Protocol="udp"
Name="Moonlight - Control"/>
<fire:FirewallException Id="AudioFwException"
Scope="any"
Port="48000"
Protocol="udp"
Name="Moonlight - Audio"/>
<fire:FirewallException Id="RtspuFwException"
Scope="any"
Port="48010"
Protocol="udp"
Name="Moonlight - RTSPU"/>
</Component>
<Component Id="Shortcuts" Guid="*">
<Shortcut Id="StartMenuShortcut"
Name="Moonlight Internet Streaming Tester"
+114 -29
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,15 +96,40 @@ 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, Internal port: %s)" NL, intPort);
// 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);
printf("OK (%s seconds remaining, Internal port: %s)" NL, leaseDuration, intPort);
}
// If we're just validating, we found an entry, so we're done.
if (validationPass) {
return true;
}
if (!enable) {
@@ -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 && port >= 47000) { // 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;
}
}
}
@@ -344,7 +415,7 @@ bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable, bool inde
lifetime = 604800; // 1 week
}
else {
lifetime = 3600;
lifetime = PORT_MAPPING_DURATION_SEC;
}
printf("Updating NAT-PMP port mapping for %s %d...", proto == IPPROTO_TCP ? "TCP" : "UDP", port);
@@ -734,25 +805,28 @@ void NETIOAPI_API_ IpInterfaceChangeNotificationCallback(PVOID context, PMIB_IPI
SetEvent((HANDLE)context);
}
void ResetLogFile()
void ResetLogFile(bool standaloneExe)
{
char oldLogFilePath[MAX_PATH + 1];
char currentLogFilePath[MAX_PATH + 1];
char timeString[MAX_PATH + 1] = {};
SYSTEMTIME time;
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-old.log", oldLogFilePath, sizeof(oldLogFilePath));
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-current.log", currentLogFilePath, sizeof(currentLogFilePath));
if (!standaloneExe) {
char oldLogFilePath[MAX_PATH + 1];
char currentLogFilePath[MAX_PATH + 1];
// Close the existing stdout handle. This is important because otherwise
// it may still be open as stdout when we try to MoveFileEx below.
fclose(stdout);
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-old.log", oldLogFilePath, sizeof(oldLogFilePath));
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\miss-current.log", currentLogFilePath, sizeof(currentLogFilePath));
// Rotate the current to the old log file
MoveFileExA(currentLogFilePath, oldLogFilePath, MOVEFILE_REPLACE_EXISTING);
// Close the existing stdout handle. This is important because otherwise
// it may still be open as stdout when we try to MoveFileEx below.
fclose(stdout);
// Redirect stdout to this new file
freopen(currentLogFilePath, "w", stdout);
// Rotate the current to the old log file
MoveFileExA(currentLogFilePath, oldLogFilePath, MOVEFILE_REPLACE_EXISTING);
// Redirect stdout to this new file
freopen(currentLogFilePath, "w", stdout);
}
// Print a log header
printf("Moonlight Internet Streaming Service v" VER_VERSION_STR NL);
@@ -789,13 +863,24 @@ DWORD WINAPI GameStreamStateChangeThread(PVOID Context)
return err;
}
int Run()
int Run(bool standaloneExe)
{
HANDLE ifaceChangeEvent = CreateEvent(nullptr, true, false, nullptr);
HANDLE gsChangeEvent = CreateEvent(nullptr, true, false, nullptr);
HANDLE events[2] = { ifaceChangeEvent, gsChangeEvent };
ResetLogFile();
ResetLogFile(standaloneExe);
// Bump the process priority class to above normal. The UDP relay threads will
// further raise their own thread priorities to avoid preemption by other activity.
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
// 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);
@@ -817,7 +902,7 @@ int Run()
ULONGLONG beforeSleepTime = GetTickCount64();
DWORD ret = WaitForMultipleObjects(ARRAYSIZE(events), events, false, POLLING_DELAY_SEC * 1000);
if (ret == WAIT_OBJECT_0) {
ResetLogFile();
ResetLogFile(standaloneExe);
printf("Woke up for interface change notification after %lld seconds" NL,
(GetTickCount64() - beforeSleepTime) / 1000);
@@ -826,13 +911,13 @@ int Run()
Sleep(10000);
}
else if (ret == WAIT_OBJECT_0 + 1) {
ResetLogFile();
ResetLogFile(standaloneExe);
printf("Woke up for GameStream state change notification after %lld seconds" NL,
(GetTickCount64() - beforeSleepTime) / 1000);
}
else {
ResetLogFile();
ResetLogFile(standaloneExe);
printf("Woke up for periodic refresh" NL);
}
@@ -893,7 +978,7 @@ ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
// Start the service
err = Run();
err = Run(false);
if (err != 0) {
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = err;
@@ -916,7 +1001,7 @@ int main(int argc, char* argv[])
}
if (argc == 2 && !strcmp(argv[1], "exe")) {
Run();
Run(true);
return 0;
}
+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>
+106
View File
@@ -0,0 +1,106 @@
#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;
USHORT nboPort = htons(tuple->port);
SOCKADDR_IN lastRemoteAddr;
// Ensure the relay threads aren't preempted by games or other CPU intensive activity
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
RtlZeroMemory(&lastRemoteAddr, sizeof(lastRemoteAddr));
for (;;) {
char buffer[4096];
SOCKADDR_IN sourceAddr;
int sourceAddrLen;
int recvLen;
sourceAddrLen = sizeof(sourceAddr);
recvLen = recvfrom(tuple->socket, buffer, sizeof(buffer), 0, (PSOCKADDR)&sourceAddr, &sourceAddrLen);
if (recvLen == SOCKET_ERROR) {
continue;
}
SOCKADDR_IN destinationAddr;
if (RtlEqualMemory(&sourceAddr.sin_addr, &in4addr_loopback, sizeof(sourceAddr.sin_addr)) && sourceAddr.sin_port == nboPort) {
// 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 = nboPort;
}
sendto(tuple->socket, buffer, recvLen, 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);
+30 -1
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;
@@ -979,7 +1002,7 @@ UPnPPortStatus UPnPCheckPort(struct UPNPUrls* urls, struct IGDdatas* data, int p
}
else if (err == UPNPCOMMAND_SUCCESS) {
if (!strcmp(myAddr, intClient)) {
fprintf(LOG_OUT, "OK\n");
fprintf(LOG_OUT, "OK (Internal port: %s)\n", intPort);
return OK;
}
else {
@@ -1203,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.",
+2 -2
View File
@@ -1,7 +1,7 @@
#pragma once
#define VER_VERSION 5,1,0,0
#define VER_VERSION_STR "5.1.0.0"
#define VER_VERSION 5,3,0,0
#define VER_VERSION_STR "5.3.0.0"
#define VER_COMPANYNAME_STR "Moonlight Game Streaming Project"
#define VER_PRODUCTNAME_STR "Moonlight Internet Hosting Tool"