Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
021474cdd2 | ||
|
|
308cd0dd00 | ||
|
|
83360d775b | ||
|
|
cdbaa0aff3 | ||
|
|
36ea86faaa | ||
|
|
8590a0813b | ||
|
|
51ae1c8770 | ||
|
|
f1ca6a71f0 | ||
|
|
ac92212464 | ||
|
|
dbf43ac7a1 | ||
|
|
893aa76c9c | ||
|
|
21d8c71a2c | ||
|
|
41ef072c9b | ||
|
|
53246bd4c5 | ||
|
|
ac850e79d8 | ||
|
|
944c8993e8 | ||
|
|
b6508d9024 | ||
|
|
ef5bb72d5c | ||
|
|
3343ebb225 | ||
|
|
05413a554c | ||
|
|
5f015acdaa | ||
|
|
df286ef56d | ||
|
|
47d60b9f24 | ||
|
|
659d3aea23 | ||
|
|
e4a4d42ece | ||
|
|
189e861362 | ||
|
|
fd022b67d3 | ||
|
|
14d27b4cf5 | ||
|
|
17cb084968 | ||
|
|
ad8ef228d5 | ||
|
|
8d2252c4ac | ||
|
|
365b3ccd61 | ||
|
|
74140a8511 | ||
|
|
a9bf4ffd70 | ||
|
|
164d8b5467 | ||
|
|
f1ebefa59c | ||
|
|
3e060fae54 | ||
|
|
f97c04a8cc | ||
|
|
85cd20005c | ||
|
|
e3e4bf608f | ||
|
|
0be2ca51e9 | ||
|
|
3e1ffce16f | ||
|
|
c7813a7a0b | ||
|
|
87fd3ad8e2 | ||
|
|
9d3ef0a215 | ||
|
|
ae83853a27 | ||
|
|
0b8b117cb2 | ||
|
|
0050566124 | ||
|
|
cc24472193 | ||
|
|
4a11ab0f7f | ||
|
|
c1c16edf7d | ||
|
|
ae2647f36e | ||
|
|
8e40ef80bd | ||
|
|
16bb727b99 | ||
|
|
9194b8e22b | ||
|
|
2805464e86 | ||
|
|
20e7374480 | ||
|
|
0fdbee831b | ||
|
|
b37b99e9c3 | ||
|
|
29bbdc5319 |
+1
-1
Submodule GS-IPv6-Forwarder updated: c1d7dbe846...665958b759
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+70
-3
@@ -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">
|
||||
@@ -47,9 +69,8 @@
|
||||
Account="NT AUTHORITY\LocalService"
|
||||
ErrorControl="ignore"
|
||||
Interactive="no">
|
||||
<ServiceConfig DelayedAutoStart="no" OnInstall="yes" OnReinstall="yes" ServiceSid="restricted">
|
||||
<RequiredPrivilege>SeChangeNotifyPrivilege</RequiredPrivilege>
|
||||
</ServiceConfig>
|
||||
<ServiceConfig DelayedAutoStart="no" OnInstall="yes" OnReinstall="yes" ServiceSid="unrestricted"/>
|
||||
<util:ServiceConfig FirstFailureActionType="restart" SecondFailureActionType="restart" ThirdFailureActionType="restart" ResetPeriodInDays="1" RestartServiceDelayInSeconds="10"/>
|
||||
</ServiceInstall>
|
||||
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="MISS" Wait="yes" />
|
||||
</Component>
|
||||
@@ -60,6 +81,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"
|
||||
@@ -71,5 +129,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>
|
||||
|
||||
+141
-53
@@ -12,6 +12,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "relay.h"
|
||||
#include "..\version.h"
|
||||
|
||||
#pragma comment(lib, "miniupnpc.lib")
|
||||
@@ -38,6 +39,7 @@ bool PCPMapPort(PSOCKADDR_STORAGE localAddr, int localAddrLen, PSOCKADDR_STORAGE
|
||||
#define POLLING_DELAY_SEC 120
|
||||
#define PORT_MAPPING_DURATION_SEC 3600
|
||||
#define UPNP_DISCOVERY_DELAY_MS 5000
|
||||
#define GAA_INITIAL_SIZE 8192
|
||||
|
||||
static struct port_entry {
|
||||
int proto;
|
||||
@@ -53,9 +55,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 +97,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 +150,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 +178,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 +196,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);
|
||||
@@ -178,10 +234,7 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
|
||||
|
||||
bool GetIP4OnLinkPrefixLength(char* lanAddressString, int* prefixLength)
|
||||
{
|
||||
union {
|
||||
IP_ADAPTER_ADDRESSES addresses;
|
||||
char buffer[8192];
|
||||
};
|
||||
PIP_ADAPTER_ADDRESSES addresses;
|
||||
ULONG error;
|
||||
ULONG length;
|
||||
PIP_ADAPTER_ADDRESSES currentAdapter;
|
||||
@@ -190,22 +243,34 @@ bool GetIP4OnLinkPrefixLength(char* lanAddressString, int* prefixLength)
|
||||
|
||||
inet_pton(AF_INET, lanAddressString, &targetAddress);
|
||||
|
||||
// Get a list of all interfaces with IPv4 addresses on the system
|
||||
length = sizeof(buffer);
|
||||
error = GetAdaptersAddresses(AF_INET,
|
||||
GAA_FLAG_SKIP_ANYCAST |
|
||||
GAA_FLAG_SKIP_MULTICAST |
|
||||
GAA_FLAG_SKIP_DNS_SERVER |
|
||||
GAA_FLAG_SKIP_FRIENDLY_NAME,
|
||||
NULL,
|
||||
&addresses,
|
||||
&length);
|
||||
addresses = NULL;
|
||||
length = GAA_INITIAL_SIZE;
|
||||
do {
|
||||
free(addresses);
|
||||
addresses = (PIP_ADAPTER_ADDRESSES)malloc(length);
|
||||
if (addresses == NULL) {
|
||||
printf("malloc(%u) failed" NL, length);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a list of all interfaces with IPv4 addresses on the system
|
||||
error = GetAdaptersAddresses(AF_INET,
|
||||
GAA_FLAG_SKIP_ANYCAST |
|
||||
GAA_FLAG_SKIP_MULTICAST |
|
||||
GAA_FLAG_SKIP_DNS_SERVER |
|
||||
GAA_FLAG_SKIP_FRIENDLY_NAME,
|
||||
NULL,
|
||||
addresses,
|
||||
&length);
|
||||
} while (error == ERROR_BUFFER_OVERFLOW);
|
||||
|
||||
if (error != ERROR_SUCCESS) {
|
||||
printf("GetAdaptersAddresses() failed: %d" NL, error);
|
||||
free(addresses);
|
||||
return false;
|
||||
}
|
||||
|
||||
currentAdapter = &addresses;
|
||||
currentAdapter = addresses;
|
||||
currentAddress = nullptr;
|
||||
while (currentAdapter != nullptr) {
|
||||
currentAddress = currentAdapter->FirstUnicastAddress;
|
||||
@@ -216,6 +281,7 @@ bool GetIP4OnLinkPrefixLength(char* lanAddressString, int* prefixLength)
|
||||
|
||||
if (RtlEqualMemory(¤tAddrV4->sin_addr, &targetAddress, sizeof(targetAddress))) {
|
||||
*prefixLength = currentAddress->OnLinkPrefixLength;
|
||||
free(addresses);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -226,6 +292,7 @@ bool GetIP4OnLinkPrefixLength(char* lanAddressString, int* prefixLength)
|
||||
}
|
||||
|
||||
printf("No adapter found with IPv4 address: %s" NL, lanAddressString);
|
||||
free(addresses);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -279,8 +346,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 +372,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 +427,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);
|
||||
@@ -386,15 +469,6 @@ bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable, bool inde
|
||||
else if (response.pnu.newportmapping.mappedpublicport != port) {
|
||||
printf("CONFLICT" NL);
|
||||
|
||||
// Some buggy routers (Untangle) will change the *internal* port when
|
||||
// adjusting a port mapping request that collides. This is why we also
|
||||
// pass privateport back from the response and not from the port we originally
|
||||
// asked for. Warn in this case.
|
||||
if (response.pnu.newportmapping.privateport != port) {
|
||||
printf("Buggy router changed the internal port when handling NAT-PMP conflict! (%d -> %d)" NL,
|
||||
port, response.pnu.newportmapping.privateport);
|
||||
}
|
||||
|
||||
// It couldn't assign us the external port we requested and gave us an alternate external port.
|
||||
// We can't use this alternate mapping, so immediately release it.
|
||||
printf("Deleting unwanted NAT-PMP mapping for %s %d...", proto == IPPROTO_TCP ? "TCP" : "UDP", response.pnu.newportmapping.mappedpublicport);
|
||||
@@ -743,25 +817,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);
|
||||
@@ -798,13 +875,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);
|
||||
@@ -826,7 +914,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);
|
||||
@@ -835,13 +923,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);
|
||||
}
|
||||
@@ -902,7 +990,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;
|
||||
@@ -925,7 +1013,7 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
if (argc == 2 && !strcmp(argv[1], "exe")) {
|
||||
Run();
|
||||
Run(true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
#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;
|
||||
int error;
|
||||
|
||||
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
error = WSAGetLastError();
|
||||
printf("socket() failed: %d\n", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
error = WSAGetLastError();
|
||||
printf("bind() failed: %d\n", error);
|
||||
closesocket(sock);
|
||||
return error;
|
||||
}
|
||||
|
||||
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) {
|
||||
error = GetLastError();
|
||||
printf("CreateThread() failed: %d\n", error);
|
||||
closesocket(sock);
|
||||
return error;
|
||||
}
|
||||
|
||||
CloseHandle(thread);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#define RELAY_PORT_OFFSET -10000
|
||||
|
||||
int StartUdpRelay(unsigned short Port);
|
||||
+611
-156
@@ -5,13 +5,19 @@
|
||||
#include <assert.h>
|
||||
#include <shellapi.h>
|
||||
#include <objbase.h>
|
||||
#include <WinInet.h>
|
||||
#include <WinHttp.h>
|
||||
#include <wtsapi32.h>
|
||||
#include <powerbase.h>
|
||||
#include <VersionHelpers.h>
|
||||
#include <tlhelp32.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")
|
||||
#pragma comment(lib, "winhttp.lib")
|
||||
#pragma comment(lib, "wtsapi32.lib")
|
||||
#pragma comment(lib, "powrprof.lib")
|
||||
|
||||
#define MINIUPNP_STATICLIB
|
||||
#include <miniupnpc/miniupnpc.h>
|
||||
@@ -21,6 +27,10 @@
|
||||
#define NATPMP_STATICLIB
|
||||
#include <natpmp.h>
|
||||
|
||||
#define LOOPBACK_SERVER_PORT_OFFSET -10000
|
||||
|
||||
#define GAA_INITIAL_SIZE 8192
|
||||
|
||||
static struct port_entry {
|
||||
int proto;
|
||||
int port;
|
||||
@@ -178,6 +188,56 @@ void DisplayMessage(const char* message, const char* helpUrl = nullptr, MessageP
|
||||
}
|
||||
}
|
||||
|
||||
bool ExecuteCommand(PCSTR command, PCHAR outputBuffer, DWORD outputBufferLength)
|
||||
{
|
||||
SECURITY_ATTRIBUTES attribs;
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
HANDLE outReadHandle;
|
||||
DWORD bytesRead = 0;
|
||||
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
|
||||
ZeroMemory(&attribs, sizeof(attribs));
|
||||
attribs.nLength = sizeof(attribs);
|
||||
attribs.bInheritHandle = TRUE;
|
||||
|
||||
if (!CreatePipe(&outReadHandle, &si.hStdOutput, &attribs, 0))
|
||||
{
|
||||
fprintf(LOG_OUT, "CreatePipe() failed: %d\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
si.hStdError = si.hStdOutput;
|
||||
|
||||
if (!CreateProcess(NULL, (LPSTR)command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
|
||||
{
|
||||
fprintf(LOG_OUT, "CreateProcess() failed: %d\n", GetLastError());
|
||||
CloseHandle(si.hStdOutput);
|
||||
return false;
|
||||
}
|
||||
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
|
||||
CloseHandle(si.hStdOutput);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
if (!ReadFile(outReadHandle, outputBuffer, outputBufferLength, &bytesRead, NULL))
|
||||
{
|
||||
fprintf(LOG_OUT, "ReadFile() failed: %d\n", GetLastError());
|
||||
CloseHandle(outReadHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
outputBuffer[bytesRead] = 0;
|
||||
CloseHandle(outReadHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsGameStreamEnabled()
|
||||
{
|
||||
DWORD error;
|
||||
@@ -211,6 +271,123 @@ 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;
|
||||
DWORD sessionCount;
|
||||
DWORD level;
|
||||
bool ret;
|
||||
DWORD activeSessionId = WTSGetActiveConsoleSessionId();
|
||||
|
||||
if (activeSessionId == 0xFFFFFFFF) {
|
||||
fprintf(LOG_OUT, "No active console session detected\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
level = 1;
|
||||
if (!WTSEnumerateSessionsEx(WTS_CURRENT_SERVER_HANDLE, &level, 0, &sessionInfo, &sessionCount)) {
|
||||
fprintf(LOG_OUT, "WTSEnumerateSessionsEx() failed: %d\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = false;
|
||||
for (int i = 0; i < sessionCount; i++)
|
||||
{
|
||||
if (sessionInfo[i].SessionId == activeSessionId) {
|
||||
if (sessionInfo[i].pUserName != nullptr) {
|
||||
fprintf(LOG_OUT, "Session %d has active user\n", sessionInfo[i].SessionId);
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WTSFreeMemoryExW(WTSTypeSessionInfoLevel1, sessionInfo, sessionCount);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool IsSleepEnabled()
|
||||
{
|
||||
SYSTEM_POWER_POLICY powerPolicy;
|
||||
|
||||
if (CallNtPowerInformation(SystemPowerPolicyAc, NULL, 0, &powerPolicy, sizeof(powerPolicy)) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return powerPolicy.IdleTimeout != 0 && powerPolicy.Idle.Action == PowerActionSleep;
|
||||
}
|
||||
|
||||
bool IsHibernationEnabled()
|
||||
{
|
||||
SYSTEM_POWER_POLICY powerPolicy;
|
||||
|
||||
if (CallNtPowerInformation(SystemPowerPolicyAc, NULL, 0, &powerPolicy, sizeof(powerPolicy)) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return powerPolicy.DozeS4Timeout != 0 || (powerPolicy.IdleTimeout != 0 && powerPolicy.Idle.Action == PowerActionHibernate);
|
||||
}
|
||||
|
||||
bool IsLocalNetworkAccessBlocked()
|
||||
{
|
||||
MIB_IPFORWARDROW route;
|
||||
DWORD error;
|
||||
SOCKADDR_IN sin;
|
||||
SOCKET s;
|
||||
|
||||
error = GetBestRoute(0, 0, &route);
|
||||
if (error != NO_ERROR) {
|
||||
fprintf(LOG_OUT, "GetBestRoute() failed: %d\n", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (s == INVALID_SOCKET) {
|
||||
fprintf(LOG_OUT, "socket() failed: %d\n", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
RtlZeroMemory(&sin, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_addr.S_un.S_addr = route.dwForwardNextHop;
|
||||
sin.sin_port = htons(80);
|
||||
|
||||
if (connect(s, (PSOCKADDR)&sin, sizeof(sin)) == 0) {
|
||||
error = NO_ERROR;
|
||||
fprintf(LOG_OUT, "connect(%s, %d) successful\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
|
||||
}
|
||||
else {
|
||||
error = WSAGetLastError();
|
||||
fprintf(LOG_OUT, "connect(%s, %d) failed: %d\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), error);
|
||||
}
|
||||
|
||||
closesocket(s);
|
||||
|
||||
return error == WSAEACCES;
|
||||
}
|
||||
|
||||
bool IsZeroTierInstalled()
|
||||
{
|
||||
DWORD error;
|
||||
@@ -307,7 +484,7 @@ PortTestStatus TestPort(PSOCKADDR_STORAGE addr, int proto, int port, bool withSe
|
||||
sizeof(SOCKADDR_IN) : sizeof(SOCKADDR_IN6);
|
||||
|
||||
RtlCopyMemory(&sin6, addr, addrLen);
|
||||
sin6.sin6_port = htons(port);
|
||||
sin6.sin6_port = htons(port + (isLoopbackRelay ? LOOPBACK_SERVER_PORT_OFFSET : 0));
|
||||
|
||||
if (proto == IPPROTO_TCP) {
|
||||
err = connect(clientSock, (struct sockaddr*)&sin6, addrLen);
|
||||
@@ -397,64 +574,100 @@ PortTestStatus TestPort(PSOCKADDR_STORAGE addr, int proto, int port, bool withSe
|
||||
}
|
||||
}
|
||||
|
||||
PortTestStatus TestHttpPort(PSOCKADDR_STORAGE addr, int port)
|
||||
PortTestStatus TestHttpPort(PSOCKADDR_STORAGE addr, int port, bool isLoopbackRelay)
|
||||
{
|
||||
HINTERNET hInternet;
|
||||
HINTERNET hRequest;
|
||||
char url[1024];
|
||||
HINTERNET hSession = nullptr;
|
||||
HINTERNET hConnection = nullptr;
|
||||
HINTERNET hRequest = nullptr;
|
||||
PortTestStatus result;
|
||||
|
||||
hInternet = InternetOpenA("MIST", 0, nullptr, nullptr, 0);
|
||||
if (hInternet == nullptr) {
|
||||
fprintf(LOG_OUT, "InternetOpen() failed: %d\n", GetLastError());
|
||||
return PortTestError;
|
||||
hSession = WinHttpOpen(L"MIST", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
if (hSession == nullptr) {
|
||||
fprintf(LOG_OUT, "WinHttpOpen() failed: %d\n", GetLastError());
|
||||
result = PortTestError;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
char addrStr[INET6_ADDRSTRLEN + 2];
|
||||
// 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()) {
|
||||
DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 |
|
||||
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 |
|
||||
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
|
||||
|
||||
if (!WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols, sizeof(protocols))) {
|
||||
fprintf(LOG_OUT, "WinHttpSetOption(WINHTTP_OPTION_SECURE_PROTOCOLS) failed: %d\n", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
WCHAR urlEscapedAddr[INET6_ADDRSTRLEN + 2];
|
||||
if (addr->ss_family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((struct sockaddr_in*)addr)->sin_addr, addrStr, sizeof(addrStr));
|
||||
InetNtopW(AF_INET, &((struct sockaddr_in*)addr)->sin_addr, urlEscapedAddr, ARRAYSIZE(urlEscapedAddr));
|
||||
}
|
||||
else {
|
||||
// The address string must be escaped for usage in URLs
|
||||
addrStr[0] = '[';
|
||||
inet_ntop(AF_INET6, &((struct sockaddr_in6*)addr)->sin6_addr, &addrStr[1], INET6_ADDRSTRLEN);
|
||||
strcat_s(addrStr, "]");
|
||||
urlEscapedAddr[0] = L'[';
|
||||
InetNtopW(AF_INET6, &((struct sockaddr_in6*)addr)->sin6_addr, &urlEscapedAddr[1], INET6_ADDRSTRLEN);
|
||||
wcscat_s(urlEscapedAddr, L"]");
|
||||
}
|
||||
|
||||
sprintf(url, "%s://%s:%d/",
|
||||
port == 47989 ? "http" : "https",
|
||||
addrStr,
|
||||
port);
|
||||
hConnection = WinHttpConnect(hSession, urlEscapedAddr, port + (isLoopbackRelay ? LOOPBACK_SERVER_PORT_OFFSET : 0), NULL);
|
||||
if (hConnection == nullptr) {
|
||||
fprintf(LOG_OUT, "WinHttpConnect() failed: %d\n", GetLastError());
|
||||
result = PortTestError;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
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) {
|
||||
hRequest = WinHttpOpenRequest(hConnection, L"GET", L"/", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
|
||||
port == 47984 ? WINHTTP_FLAG_SECURE : 0);
|
||||
if (hConnection == nullptr) {
|
||||
fprintf(LOG_OUT, "WinHttpOpenRequest() failed: %d\n", GetLastError());
|
||||
result = PortTestError;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) || !WinHttpReceiveResponse(hRequest, NULL)) {
|
||||
if (GetLastError() == ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED) {
|
||||
// This is expected for our HTTPS connection
|
||||
fprintf(LOG_OUT, "Success\n");
|
||||
result = PortTestOk;
|
||||
}
|
||||
else {
|
||||
// CANNOT_CONNECT is the "expected" error
|
||||
if (GetLastError() == ERROR_INTERNET_CANNOT_CONNECT) {
|
||||
if (GetLastError() == ERROR_WINHTTP_CANNOT_CONNECT) {
|
||||
fprintf(LOG_OUT, "Failed\n");
|
||||
}
|
||||
else {
|
||||
fprintf(LOG_OUT, "Failed: %d\n", GetLastError());
|
||||
}
|
||||
|
||||
InternetCloseHandle(hInternet);
|
||||
return PortTestError;
|
||||
result = PortTestError;
|
||||
}
|
||||
|
||||
goto Exit;
|
||||
}
|
||||
else {
|
||||
fprintf(LOG_OUT, "Success\n");
|
||||
InternetCloseHandle(hRequest);
|
||||
result = PortTestOk;
|
||||
}
|
||||
|
||||
Exit:
|
||||
if (hRequest != nullptr) {
|
||||
WinHttpCloseHandle(hRequest);
|
||||
}
|
||||
if (hConnection != nullptr) {
|
||||
WinHttpCloseHandle(hConnection);
|
||||
}
|
||||
if (hSession != nullptr) {
|
||||
WinHttpCloseHandle(hSession);
|
||||
}
|
||||
|
||||
InternetCloseHandle(hInternet);
|
||||
|
||||
return PortTestOk;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TestAllPorts(PSOCKADDR_STORAGE addr, char* portMsg, int portMsgLen, bool isLoopbackRelay, bool consolePrint, bool* allPortsFailed = nullptr)
|
||||
@@ -466,9 +679,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",
|
||||
@@ -476,15 +687,18 @@ 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);
|
||||
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) {
|
||||
@@ -511,6 +725,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;
|
||||
@@ -524,9 +813,16 @@ bool FindLocalInterfaceIPAddress(int family, PSOCKADDR_STORAGE addr)
|
||||
hint.ai_socktype = SOCK_STREAM;
|
||||
hint.ai_protocol = IPPROTO_TCP;
|
||||
hint.ai_flags = AI_ADDRCONFIG;
|
||||
err = getaddrinfo("moonlight-stream.org", "https", &hint, &result);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -570,31 +866,40 @@ bool FindLocalInterfaceIPAddress(int family, PSOCKADDR_STORAGE addr)
|
||||
|
||||
bool FindZeroTierInterfaceAddress(PSOCKADDR_STORAGE addr)
|
||||
{
|
||||
union {
|
||||
IP_ADAPTER_ADDRESSES addresses;
|
||||
char buffer[8192];
|
||||
};
|
||||
PIP_ADAPTER_ADDRESSES addresses;
|
||||
ULONG error;
|
||||
ULONG length;
|
||||
PIP_ADAPTER_ADDRESSES currentAdapter;
|
||||
PIP_ADAPTER_UNICAST_ADDRESS currentAddress;
|
||||
|
||||
// Get all IPv4 interfaces
|
||||
length = sizeof(buffer);
|
||||
error = GetAdaptersAddresses(AF_INET,
|
||||
GAA_FLAG_SKIP_ANYCAST |
|
||||
GAA_FLAG_SKIP_MULTICAST |
|
||||
GAA_FLAG_SKIP_DNS_SERVER |
|
||||
GAA_FLAG_SKIP_FRIENDLY_NAME,
|
||||
NULL,
|
||||
&addresses,
|
||||
&length);
|
||||
addresses = NULL;
|
||||
length = GAA_INITIAL_SIZE;
|
||||
do {
|
||||
free(addresses);
|
||||
addresses = (PIP_ADAPTER_ADDRESSES)malloc(length);
|
||||
if (addresses == NULL) {
|
||||
fprintf(LOG_OUT, "malloc(%u) failed\n", length);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get all IPv4 interfaces
|
||||
error = GetAdaptersAddresses(AF_INET,
|
||||
GAA_FLAG_SKIP_ANYCAST |
|
||||
GAA_FLAG_SKIP_MULTICAST |
|
||||
GAA_FLAG_SKIP_DNS_SERVER |
|
||||
GAA_FLAG_SKIP_FRIENDLY_NAME,
|
||||
NULL,
|
||||
addresses,
|
||||
&length);
|
||||
} while (error == ERROR_BUFFER_OVERFLOW);
|
||||
|
||||
if (error != ERROR_SUCCESS) {
|
||||
fprintf(LOG_OUT, "GetAdaptersAddresses() failed: %d\n", WSAGetLastError());
|
||||
fprintf(LOG_OUT, "GetAdaptersAddresses() failed: %d\n", error);
|
||||
free(addresses);
|
||||
return false;
|
||||
}
|
||||
|
||||
currentAdapter = &addresses;
|
||||
currentAdapter = addresses;
|
||||
while (currentAdapter != NULL) {
|
||||
// Look for ones that correspond to a ZeroTier device
|
||||
if (wcsstr(currentAdapter->Description, L"ZeroTier")) {
|
||||
@@ -603,6 +908,7 @@ bool FindZeroTierInterfaceAddress(PSOCKADDR_STORAGE addr)
|
||||
while (currentAddress != NULL) {
|
||||
if (currentAddress->Address.lpSockaddr->sa_family == AF_INET) {
|
||||
RtlCopyMemory(addr, currentAddress->Address.lpSockaddr, currentAddress->Address.iSockaddrLength);
|
||||
free(addresses);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -613,9 +919,71 @@ bool FindZeroTierInterfaceAddress(PSOCKADDR_STORAGE addr)
|
||||
currentAdapter = currentAdapter->Next;
|
||||
}
|
||||
|
||||
free(addresses);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FindDuplicateDefaultInterfaces(void)
|
||||
{
|
||||
PIP_ADAPTER_ADDRESSES addresses;
|
||||
ULONG error;
|
||||
ULONG length;
|
||||
MIB_IPFORWARDROW defaultRoute;
|
||||
PIP_ADAPTER_ADDRESSES currentAdapter;
|
||||
DWORD matchingInterfaces = 0;
|
||||
|
||||
error = GetBestRoute(0, 0, &defaultRoute);
|
||||
if (error != NO_ERROR) {
|
||||
fprintf(LOG_OUT, "GetBestRoute() failed: %d\n", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
addresses = NULL;
|
||||
length = GAA_INITIAL_SIZE;
|
||||
do {
|
||||
free(addresses);
|
||||
addresses = (PIP_ADAPTER_ADDRESSES)malloc(length);
|
||||
if (addresses == NULL) {
|
||||
fprintf(LOG_OUT, "malloc(%u) failed\n", length);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get all IPv4 interfaces
|
||||
error = GetAdaptersAddresses(AF_INET,
|
||||
GAA_FLAG_SKIP_UNICAST |
|
||||
GAA_FLAG_SKIP_ANYCAST |
|
||||
GAA_FLAG_SKIP_MULTICAST |
|
||||
GAA_FLAG_SKIP_DNS_SERVER |
|
||||
GAA_FLAG_INCLUDE_GATEWAYS |
|
||||
GAA_FLAG_SKIP_FRIENDLY_NAME,
|
||||
NULL,
|
||||
addresses,
|
||||
&length);
|
||||
} while (error == ERROR_BUFFER_OVERFLOW);
|
||||
|
||||
if (error != ERROR_SUCCESS) {
|
||||
fprintf(LOG_OUT, "GetAdaptersAddresses() failed: %d\n", error);
|
||||
free(addresses);
|
||||
return false;
|
||||
}
|
||||
|
||||
currentAdapter = addresses;
|
||||
while (currentAdapter != NULL) {
|
||||
if (currentAdapter->OperStatus == IfOperStatusUp &&
|
||||
currentAdapter->FirstGatewayAddress != NULL &&
|
||||
currentAdapter->FirstGatewayAddress->Address.iSockaddrLength == sizeof(SOCKADDR_IN)) {
|
||||
if (((PSOCKADDR_IN)currentAdapter->FirstGatewayAddress->Address.lpSockaddr)->sin_addr.S_un.S_addr == defaultRoute.dwForwardNextHop) {
|
||||
matchingInterfaces++;
|
||||
}
|
||||
}
|
||||
|
||||
currentAdapter = currentAdapter->Next;
|
||||
}
|
||||
|
||||
free(addresses);
|
||||
return matchingInterfaces > 1;
|
||||
}
|
||||
|
||||
enum UPnPPortStatus {
|
||||
NOT_FOUND,
|
||||
OK,
|
||||
@@ -657,7 +1025,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 {
|
||||
@@ -772,7 +1140,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);
|
||||
@@ -799,7 +1167,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);
|
||||
|
||||
@@ -813,16 +1181,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;
|
||||
}
|
||||
|
||||
@@ -885,6 +1249,64 @@ 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.",
|
||||
"https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#display-locked-error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
fprintf(CONSOLE_OUT, "Checking power settings...\n");
|
||||
|
||||
if (IsSleepEnabled()) {
|
||||
DisplayMessage("This computer has sleep mode enabled. Sleep mode may prevent this PC from being available for streaming.\n\n"
|
||||
"Please ensure sleep is disabled in Power Options so this PC is always ready for streaming. Click the Help button for more information.",
|
||||
"https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#sleep-mode-enabled-warning", MpWarn, false);
|
||||
}
|
||||
|
||||
if (IsHibernationEnabled()) {
|
||||
DisplayMessage("This computer has hibernation enabled. Hibernation may prevent this PC from being available for streaming.\n\n"
|
||||
"Please ensure hibernation is disabled in Power Options so this PC is always ready for streaming. Click the Help button for more information.",
|
||||
"https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#hibernation-enabled-warning", MpWarn, false);
|
||||
}
|
||||
|
||||
fprintf(CONSOLE_OUT, "Checking network connections...\n");
|
||||
|
||||
if (IsLocalNetworkAccessBlocked()) {
|
||||
DisplayMessage("Local network access appears to be blocked by another application installed on this PC.\n\n"
|
||||
"If you have firewall or VPN software installed, make sure it is configured to allow applications to access the local network. Click the Help button for guidance on fixing this issue.",
|
||||
"https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#local-network-access-blocked-error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (FindDuplicateDefaultInterfaces()) {
|
||||
DisplayMessage("This computer appears to have more than one connection to the same network (like both WiFi and Ethernet, for example).\n\n"
|
||||
"Please disconnect the extra connection(s) to ensure hosting works reliably over the Internet. If you are connected via WiFi and Ethernet, try disabling your WiFi connection.",
|
||||
"https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#multiple-connections-error", MpWarn, false);
|
||||
}
|
||||
|
||||
fprintf(CONSOLE_OUT, "Checking for anti-virus and firewall software...\n");
|
||||
|
||||
char wmicBuf[8192];
|
||||
if (ExecuteCommand("WMIC /Node:localhost /Namespace:\\\\root\\SecurityCenter2 Path AntiVirusProduct Get displayName", wmicBuf, sizeof(wmicBuf))) {
|
||||
fprintf(LOG_OUT, "AV products:\n%s", wmicBuf);
|
||||
}
|
||||
if (ExecuteCommand("WMIC /Node:localhost /Namespace:\\\\root\\SecurityCenter2 Path FirewallProduct Get displayName", wmicBuf, sizeof(wmicBuf))) {
|
||||
fprintf(LOG_OUT, "Firewall products:\n%s", wmicBuf);
|
||||
if (strstr(wmicBuf, "displayName")) {
|
||||
DisplayMessage("Detected anti-virus and/or firewall software installed on this system. This software may interfere with NVIDIA GameStream.\n\n"
|
||||
"Please try temporarily disabling your anti-virus or firewall software if you experience connection issues with Moonlight.",
|
||||
"https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting#known-application-compatibility-issues", MpInfo, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
union {
|
||||
SOCKADDR_STORAGE ss;
|
||||
SOCKADDR_IN sin;
|
||||
@@ -950,9 +1372,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");
|
||||
@@ -968,7 +1405,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;
|
||||
@@ -979,8 +1416,6 @@ int main(int argc, char* argv[])
|
||||
return -1;
|
||||
}
|
||||
|
||||
fprintf(CONSOLE_OUT, "Testing GameStream connectivity over the Internet...\n");
|
||||
|
||||
// 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) {
|
||||
fprintf(LOG_OUT, "Testing GameStream ports via UPnP/NAT-PMP reported WAN address\n");
|
||||
@@ -998,101 +1433,121 @@ int main(int argc, char* argv[])
|
||||
// Go directly to the relay check if we have only IPv6 connectivity
|
||||
igdDisconnected = false;
|
||||
locallyReportedWanAddr = {};
|
||||
goto RelayCheck;
|
||||
}
|
||||
|
||||
// Try to connect via WAN IPv4 address
|
||||
fprintf(LOG_OUT, "Testing GameStream ports via STUN-reported WAN address\n");
|
||||
if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf), false, true)) {
|
||||
RelayCheck:
|
||||
struct addrinfo hint = {};
|
||||
struct addrinfo* result;
|
||||
struct addrinfo hint = {};
|
||||
struct addrinfo* result;
|
||||
|
||||
// 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.
|
||||
hint.ai_family = AF_UNSPEC;
|
||||
hint.ai_flags = AI_ADDRCONFIG;
|
||||
err = getaddrinfo("loopback-v2.moonlight-stream.org", NULL, &hint, &result);
|
||||
if (err != 0 || result == NULL) {
|
||||
fprintf(LOG_OUT, "getaddrinfo() failed: %d\n", err);
|
||||
}
|
||||
else {
|
||||
bool testServerWasReachable = false;
|
||||
bool allPortsFailedOnV4 = true;
|
||||
|
||||
hint.ai_family = AF_UNSPEC;
|
||||
hint.ai_flags = AI_ADDRCONFIG;
|
||||
err = getaddrinfo("loopback.moonlight-stream.org", NULL, &hint, &result);
|
||||
if (err != 0 || result == NULL) {
|
||||
fprintf(LOG_OUT, "getaddrinfo() failed: %d\n", err);
|
||||
// First try the relay server over IPv4. If this passes, it's considered a full success
|
||||
fprintf(LOG_OUT, "Testing GameStream ports via IPv4 loopback server\n");
|
||||
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"
|
||||
"For the easiest setup, you should pair Moonlight to your gaming PC from your home network before trying to stream over the Internet.\n\n"
|
||||
"If you can't, you can type the following address into Moonlight's Add PC dialog: %s", wanAddrStr);
|
||||
DisplayMessage(msgBuf, nullptr, MpInfo);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
// Tested against a working server and it failed
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
bool allPortsFailedOnV4 = true;
|
||||
|
||||
// First try the relay server over IPv4. If this passes, it's considered a full success
|
||||
fprintf(LOG_OUT, "Testing GameStream ports via IPv4 loopback server\n");
|
||||
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 (TestAllPorts((PSOCKADDR_STORAGE)current->ai_addr, portMsgBuf, sizeof(portMsgBuf), true, true, &allPortsFailedOnV4)) {
|
||||
// If that fails, try the relay server over IPv6. If this passes, it will be a partial success
|
||||
fprintf(LOG_OUT, "Testing GameStream ports via IPv6 loopback server\n");
|
||||
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,
|
||||
ss.ss_family == AF_INET6 ? portMsgBuf : NULL,
|
||||
ss.ss_family == AF_INET6 ? sizeof(portMsgBuf) : 0, true, true)) {
|
||||
// We will terminate the test at the IPv6 limited connectivity warning in the following cases:
|
||||
// 1) Double-NAT/CGN - indicates the connection is fundamentally limited to IPv6 for end-to-end connectivity
|
||||
// 2) IPv6-only - indicates the connection is fundamentally limited to IPv6 for all connectivity
|
||||
// 3) All ports failed the test with our IPv4 relay - This is one final heuristic to weed out IPv4 misconfigurations. If we have some ports open,
|
||||
// it clearly indicates IPv4 support is possible, just currently misconfigured.
|
||||
//
|
||||
// Our last (implicit) heuristic is that we actually managed to establish an IPv6 connection. This means that there was either
|
||||
// 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 || 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 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);
|
||||
goto AllTestsPassed;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Tested against a IPv6 working server
|
||||
break;
|
||||
}
|
||||
|
||||
// If that fails, try the relay server over IPv6. If this passes, it will be a partial success
|
||||
fprintf(LOG_OUT, "Testing GameStream ports via IPv6 loopback server\n");
|
||||
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");
|
||||
// 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,
|
||||
ss.ss_family == AF_INET6 ? portMsgBuf : NULL,
|
||||
ss.ss_family == AF_INET6 ? sizeof(portMsgBuf) : 0, true, true)) {
|
||||
// We will terminate the test at the IPv6 limited connectivity warning in the following cases:
|
||||
// 1) Double-NAT/CGN - indicates the connection is fundamentally limited to IPv6 for end-to-end connectivity
|
||||
// 2) IPv6-only - indicates the connection is fundamentally limited to IPv6 for all connectivity
|
||||
// 3) All ports failed the test with our IPv4 relay - This is one final heuristic to weed out IPv4 misconfigurations. If we have some ports open,
|
||||
// it clearly indicates IPv4 support is possible, just currently misconfigured.
|
||||
//
|
||||
// Our last (implicit) heuristic is that we actually managed to establish an IPv6 connection. This means that there was either
|
||||
// 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) {
|
||||
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.");
|
||||
DisplayMessage(msgBuf, "https://github.com/moonlight-stream/moonlight-docs/wiki/Internet-Streaming-Errors#limited-connectivity-for-hosting-error", MpWarn);
|
||||
freeaddrinfo(result);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
|
||||
// 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.\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"
|
||||
"Check that UPnP is enabled in your router settings. For more information and workarounds, click the Help button.\n\n"
|
||||
"The 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");
|
||||
}
|
||||
freeaddrinfo(result);
|
||||
|
||||
return -1;
|
||||
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.
|
||||
//
|
||||
// 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 {
|
||||
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"
|
||||
"The 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");
|
||||
}
|
||||
|
||||
AllTestsPassed:
|
||||
snprintf(msgBuf, sizeof(msgBuf), "This PC is ready to host over the Internet!\n\n"
|
||||
"For the easiest setup, you should pair Moonlight to your gaming PC from your home network before trying to stream over the Internet.\n\n"
|
||||
"If you can't, you can type the following address into Moonlight's Add PC dialog: %s", wanAddrStr);
|
||||
DisplayMessage(msgBuf, nullptr, MpInfo);
|
||||
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<!-- Windows Vista -->
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
@@ -100,6 +100,9 @@
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>..\libs\x86\Debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<AdditionalManifestFiles>mist.exe.manifest</AdditionalManifestFiles>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
@@ -115,6 +118,9 @@
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<AdditionalManifestFiles>mist.exe.manifest</AdditionalManifestFiles>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
@@ -138,6 +144,9 @@
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>..\libs\x86\Release;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<AdditionalManifestFiles>mist.exe.manifest</AdditionalManifestFiles>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
@@ -157,6 +166,9 @@
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<AdditionalManifestFiles>mist.exe.manifest</AdditionalManifestFiles>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="mist.cpp" />
|
||||
@@ -169,6 +181,9 @@
|
||||
<ClInclude Include="..\version.h" />
|
||||
<ClInclude Include="mist.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="mist.exe.manifest" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
|
||||
@@ -35,4 +35,9 @@
|
||||
<Filter>Source Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="mist.exe.manifest">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Manifest>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
+57
-43
@@ -4,7 +4,7 @@
|
||||
|
||||
#define STUN_PORT 3478
|
||||
|
||||
#define STUN_RECV_TIMEOUT_SEC 3
|
||||
#define STUN_RECV_TIMEOUT_SEC 5
|
||||
|
||||
#define STUN_MESSAGE_BINDING_REQUEST 0x0001
|
||||
#define STUN_MESSAGE_BINDING_SUCCESS 0x0101
|
||||
@@ -44,18 +44,21 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
STUN_MESSAGE reqMsg;
|
||||
int i;
|
||||
int bytesRead;
|
||||
int tries;
|
||||
int timeout;
|
||||
int err;
|
||||
bool ret;
|
||||
PSTUN_ATTRIBUTE_HEADER attribute;
|
||||
PSTUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE ipv4Attrib;
|
||||
struct addrinfo* result;
|
||||
struct addrinfo hints;
|
||||
struct sockaddr_in bindAddr;
|
||||
union {
|
||||
STUN_MESSAGE hdr;
|
||||
char buf[1024];
|
||||
} resp;
|
||||
|
||||
sock = INVALID_SOCKET;
|
||||
ret = false;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
@@ -70,18 +73,15 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
fprintf(LOG_OUT, "socket() failed: %d\n", WSAGetLastError());
|
||||
freeaddrinfo(result);
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
struct sockaddr_in bindAddr = {};
|
||||
bindAddr = {};
|
||||
bindAddr.sin_family = hints.ai_family;
|
||||
bindAddr.sin_port = htons(localPort);
|
||||
if (bind(sock, (struct sockaddr*)&bindAddr, sizeof(bindAddr)) == SOCKET_ERROR) {
|
||||
fprintf(LOG_OUT, "bind() failed: %d\n", WSAGetLastError());
|
||||
closesocket(sock);
|
||||
freeaddrinfo(result);
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
reqMsg.messageType = htons(STUN_MESSAGE_BINDING_REQUEST);
|
||||
@@ -100,57 +100,60 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
}
|
||||
}
|
||||
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
for (;;) {
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 1;
|
||||
tv.tv_usec = 0;
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 1;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
int selectRes = select(0, &fds, nullptr, nullptr, &tv);
|
||||
if (selectRes == 0) {
|
||||
// Timeout - continue looping
|
||||
continue;
|
||||
int selectRes = select(0, &fds, nullptr, nullptr, &tv);
|
||||
if (selectRes == 0) {
|
||||
// Timeout - break to the enclosing loop
|
||||
break;
|
||||
}
|
||||
else if (selectRes == SOCKET_ERROR) {
|
||||
fprintf(LOG_OUT, "select() failed: %d\n", WSAGetLastError());
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
// recvfrom() will return WSAECONNRESET if the socket has received an ICMP Port Unreachable
|
||||
// message from one of the IP addresses we've attempted communication with. The socket is still
|
||||
// operable (and may even contain queued messages to receive). We should ignore that error and
|
||||
// continue reading, otherwise exit the loop.
|
||||
bytesRead = recvfrom(sock, resp.buf, sizeof(resp.buf), 0, NULL, NULL);
|
||||
if (bytesRead != SOCKET_ERROR || WSAGetLastError() != WSAECONNRESET) {
|
||||
goto RecvDone;
|
||||
}
|
||||
}
|
||||
else if (selectRes == SOCKET_ERROR) {
|
||||
fprintf(LOG_OUT, "select() failed: %d\n", WSAGetLastError());
|
||||
closesocket(sock);
|
||||
freeaddrinfo(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Error handling is below
|
||||
bytesRead = recvfrom(sock, resp.buf, sizeof(resp.buf), 0, NULL, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
closesocket(sock);
|
||||
|
||||
RecvDone:
|
||||
if (bytesRead == 0) {
|
||||
fprintf(LOG_OUT, "No response from STUN server\n");
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (bytesRead == SOCKET_ERROR) {
|
||||
fprintf(LOG_OUT, "Failed to read STUN binding response: %d\n", WSAGetLastError());
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (bytesRead < sizeof(resp.hdr)) {
|
||||
fprintf(LOG_OUT, "STUN message truncated: %d\n", bytesRead);
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (htonl(resp.hdr.magicCookie) != STUN_MESSAGE_COOKIE) {
|
||||
fprintf(LOG_OUT, "Bad STUN cookie value: %x\n", htonl(resp.hdr.magicCookie));
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (memcmp(reqMsg.transactionId, resp.hdr.transactionId, sizeof(reqMsg.transactionId))) {
|
||||
fprintf(LOG_OUT, "STUN transaction ID mismatch\n");
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (htons(resp.hdr.messageType) != STUN_MESSAGE_BINDING_SUCCESS) {
|
||||
fprintf(LOG_OUT, "STUN message type mismatch: %x\n", htons(resp.hdr.messageType));
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
attribute = (PSTUN_ATTRIBUTE_HEADER)(&resp.hdr + 1);
|
||||
@@ -158,7 +161,7 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
while (bytesRead > sizeof(*attribute)) {
|
||||
if (bytesRead < sizeof(*attribute) + htons(attribute->length)) {
|
||||
fprintf(LOG_OUT, "STUN attribute out of bounds: %d\n", htons(attribute->length));
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
// Mask off the comprehension bit
|
||||
else if ((htons(attribute->type) & 0x7FFF) != STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS) {
|
||||
@@ -171,11 +174,11 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
ipv4Attrib = (PSTUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE)attribute;
|
||||
if (htons(ipv4Attrib->hdr.length) != 8) {
|
||||
fprintf(LOG_OUT, "STUN address length mismatch: %d\n", htons(ipv4Attrib->hdr.length));
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
else if (ipv4Attrib->addressFamily != 1) {
|
||||
fprintf(LOG_OUT, "STUN address family mismatch: %x\n", ipv4Attrib->addressFamily);
|
||||
return false;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
*wanAddr = {};
|
||||
@@ -185,9 +188,20 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||
wanAddr->sin_port = ipv4Attrib->port ^ (short)resp.hdr.magicCookie;
|
||||
wanAddr->sin_addr.S_un.S_addr = ipv4Attrib->address ^ resp.hdr.magicCookie;
|
||||
|
||||
return true;
|
||||
ret = true;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
fprintf(LOG_OUT, "No XOR mapped address found in STUN response!\n");
|
||||
return false;
|
||||
|
||||
Exit:
|
||||
if (sock != INVALID_SOCKET) {
|
||||
closesocket(sock);
|
||||
}
|
||||
|
||||
if (result != NULL) {
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#define VER_VERSION 4,2,0,0
|
||||
#define VER_VERSION_STR "4.2.0.0"
|
||||
#define VER_VERSION 5,4,0,0
|
||||
#define VER_VERSION_STR "5.4.0.0"
|
||||
|
||||
#define VER_COMPANYNAME_STR "Moonlight Game Streaming Project"
|
||||
#define VER_PRODUCTNAME_STR "Moonlight Internet Hosting Tool"
|
||||
|
||||
Reference in New Issue
Block a user