Compare commits

...
86 Commits
Author SHA1 Message Date
Cameron Gutman ddd86d91eb Update GSv6Fwd to 2.6r2 2023-08-26 00:54:15 -05:00
Cameron Gutman 546e30f363 Version 5.6.1 2023-08-26 00:04:30 -05:00
Cameron Gutman 432c2e226c Monitor for name changes too to detect deletion of the NvStream subkey 2023-08-25 23:33:54 -05:00
Cameron Gutman cc98c2cd17 Version 5.6 2023-05-27 22:34:23 -05:00
Cameron Gutman c8a5fe2efa Update VCRedist 2023-05-27 22:27:41 -05:00
Cameron Gutman 6d5d83d420 Avoid deleting Sunshine's UPnP port mappings 2023-05-27 22:26:08 -05:00
Cameron Gutman 1e1d347052 Add limited support for testing Sunshine 2023-05-27 22:18:31 -05:00
Cameron Gutman 307623914b Update VCRedist 2022-08-09 20:28:40 -05:00
Cameron Gutman 8f011440b4 Redirect stdout to NUL if the log file can't be opened 2022-04-16 21:28:28 -05:00
Cameron Gutman 771e34f199 Revert "Don't crash if the log file can't be opened"
We don't have a stdout to _dup2() into when run as a service.

This reverts commit e9ddf91157.
2022-04-16 21:27:11 -05:00
Cameron Gutman c8e8af7d4b Version 5.5.4 2022-04-16 13:32:35 -05:00
Cameron Gutman 18b914d9f6 Update MiniUPnP and libnatpmp
MiniUPnP  -> 689d1fd
libnatpmp -> 724dc69
2022-04-16 13:30:13 -05:00
Cameron Gutman e9ddf91157 Don't crash if the log file can't be opened 2022-04-16 12:47:33 -05:00
Cameron Gutman 28a5935ee7 Get rid of NL macro 2022-04-16 11:56:26 -05:00
Cameron Gutman ce3b9c3d31 Update Visual C++ Runtime 2022-04-16 11:56:07 -05:00
Cameron Gutman 0f3506a92c Update toolset to VS 2022 2022-04-16 11:39:16 -05:00
Cameron Gutman 1e6337b9b0 Update Visual C++ Runtime installer 2021-08-15 12:30:21 -05:00
Cameron Gutman 363b62f69f Version 5.5.3 2021-03-06 13:12:09 -06:00
Cameron Gutman 3175b1c4fa Update VCRedist version for VC 16.9 2021-03-06 13:09:09 -06:00
Cameron Gutman 404af38d10 Version 5.5.2 2021-02-02 18:16:27 -06:00
Cameron Gutman dd72f65ffb Fix inaccurate test results on IPv6 when a temporary address is in use and no pinholes are created for it 2021-02-01 19:59:41 -06:00
Cameron Gutman 6589dc4853 Update GSv6Fwd with CET and CFG 2021-02-01 19:53:52 -06:00
Cameron Gutman 9772139229 Print UPnP root desc URL in logs 2021-02-01 19:41:14 -06:00
Cameron Gutman 03c89e4022 Update MiniUPnPc to 36cc66edda92093b24e051e38bd16a64900272e9
Built with CFG and CET enabled
2021-02-01 19:40:44 -06:00
Cameron Gutman d1fe5f6a9b Rebuilt libnatpmp with CET and CFG
Commit 4536032ae32268a45c073a4d5e91bbab4534773a
2021-02-01 19:40:24 -06:00
Cameron Gutman a88b8d0d04 Enable CFG and CET for MISS and MIST 2021-02-01 19:26:14 -06:00
Cameron Gutman fa5ae73abb Version 5.5.1 2020-11-29 23:38:14 -06:00
Cameron Gutman 4d197cae02 Improve performance and reliability when stopping MISS 2020-11-29 22:25:27 -06:00
Cameron Gutman 624af65b55 Fix minor installer build warnings 2020-11-29 22:06:02 -06:00
Cameron Gutman 6b1a0856cc Update miniupnpc to ba130320f4180f8f9a54c48c0634e0ea0118fa38
This adds DLL version resources for Windows Installer
2020-11-29 22:05:43 -06:00
Cameron Gutman 01a1012e74 Use preprocessor variables to improve readability 2020-11-29 20:21:37 -06:00
Cameron Gutman 66b36684ec Only download the CRT redistributable if it's newer than what is installed 2020-11-29 17:23:48 -06:00
Cameron Gutman e7db5242c9 Update installer for new DLLs 2020-11-29 15:58:20 -06:00
Cameron Gutman 5e09217af4 Update GSv6Fwd to use DLLs instead of static build 2020-11-29 15:57:10 -06:00
Cameron Gutman c21a64c874 Remove x64 solution target 2020-11-29 14:51:56 -06:00
Cameron Gutman ebe91d6799 Update project build files for the new DLLs 2020-11-29 14:51:41 -06:00
Cameron Gutman 077c9814e0 Sign pre-built libraries 2020-11-29 14:49:37 -06:00
Cameron Gutman 393dfb70a7 Update miniupnpc to 677e0db63649ad2aeb536783eb745c651f2fd5c2
Rebuild as DLL to prevent MSVC version incompatibility
2020-11-29 14:35:02 -06:00
Cameron Gutman 88fb77083f Update libnatpmp to 4536032ae32268a45c073a4d5e91bbab4534773a
Rebuild as a DLL to prevent MSVC version compatibility issues
2020-11-29 14:27:53 -06:00
Cameron Gutman 38514d08c1 Version 5.5 2020-10-10 13:58:23 -05:00
Cameron Gutman 5d75b11a35 Fix success text typos 2020-10-10 13:56:10 -05:00
Cameron Gutman f511b830ea Handle deletion of the 'HKLM\Software\NVIDIA Corporation' key 2020-10-10 13:35:05 -05:00
Cameron Gutman 8a45ea2066 Test for MTU problems 2020-10-10 12:39:48 -05:00
Cameron Gutman 3844ea59de Allow mDNS traffic through the firewall for autodiscovery 2020-10-10 12:07:42 -05:00
Cameron Gutman a19b95f259 RFC6887 says PCP max packet size is 1100 bytes 2020-10-10 12:00:48 -05:00
Cameron Gutman ae81d4305d Improve success message with more information 2020-10-10 11:57:26 -05:00
Cameron Gutman 8066cf4be1 Avoid extra GameStream state prints when the state isn't changing 2020-10-10 11:37:42 -05:00
Cameron Gutman 021474cdd2 Version 5.4 2020-09-07 11:15:52 -07:00
Cameron Gutman 308cd0dd00 Use dynamically allocated buffer for GetAdaptersAddresses() 2020-09-07 10:14:54 -07:00
Cameron Gutman 83360d775b Ignore ICMP Port Unreachable messages during STUN 2020-09-06 15:12:16 -07:00
Cameron Gutman cdbaa0aff3 Version 5.3.1 2020-08-30 13:47:35 -07:00
Cameron Gutman 36ea86faaa Configure SCM to restart our service if it crashes 2020-08-29 22:43:41 -07:00
Cameron Gutman 8590a0813b Stop testing after the first IPv6 relay server 2020-08-28 18:46:56 -07:00
Cameron Gutman 51ae1c8770 Fix socket errors being clobbered by closesocket() 2020-08-28 18:46:36 -07:00
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
Cameron Gutman e4a4d42ece Version 5.1 2020-08-08 16:57:06 -07:00
Cameron Gutman 189e861362 Fix systems where Windows Firewall has been improperly disabled 2020-08-08 16:37:38 -07:00
Cameron Gutman fd022b67d3 Only test on the first reachable IPv4 and IPv6 relay servers 2020-08-07 21:58:36 -07:00
Cameron Gutman 14d27b4cf5 Use WinHTTP timeouts to avoid having to test HTTP ports twice 2020-08-04 02:03:14 -07:00
Cameron Gutman 17cb084968 Improve heuristic for distinguishing CGN vs double-NAT 2020-08-04 01:13:21 -07:00
Cameron Gutman ad8ef228d5 Fix double-close of connection handle on failure 2020-07-19 10:08:26 -07:00
Cameron Gutman 8d2252c4ac Version 5.0 2020-07-18 17:59:54 -07:00
Cameron Gutman 365b3ccd61 Remove redundant print 2020-07-18 17:55:45 -07:00
Cameron Gutman 74140a8511 Switch to the new loopback server protocol 2020-07-18 17:12:35 -07:00
Cameron Gutman a9bf4ffd70 Always run the loopback server test
It appears that in rare cases NAT reflection can work where real WAN traffic fails
2020-07-18 16:13:56 -07:00
Cameron Gutman 164d8b5467 Fix testing on Windows 7 with GFE 3.20.4 2020-07-18 15:53:47 -07:00
Cameron Gutman f1ebefa59c Add missing success print 2020-07-18 15:53:08 -07:00
Cameron Gutman 3e060fae54 Add supportedOS entries to the embedded manifest 2020-07-18 15:47:39 -07:00
Cameron Gutman f97c04a8cc Switch to WinHTTP instead of WinInet for HTTP testing 2020-07-18 13:27:52 -07:00
Cameron Gutman 85cd20005c Pass the port number directly rather than "https" to getaddrinfo()
Apparently, the services file in drivers\etc can be deleted and GFE still works.
2020-04-10 17:40:30 -07:00
37 changed files with 1397 additions and 402 deletions
+3 -3
View File
@@ -1,9 +1,9 @@
/* $Id: miniupnpc.h,v 1.53 2018/05/07 11:05:16 nanard Exp $ */
/* $Id: miniupnpc.h,v 1.59 2021/09/28 21:39:17 nanard Exp $ */
/* vim: tabstop=4 shiftwidth=4 noexpandtab
* Project: miniupnp
* http://miniupnp.free.fr/
* Author: Thomas Bernard
* Copyright (c) 2005-2018 Thomas Bernard
* Copyright (c) 2005-2021 Thomas Bernard
* This software is subjects to the conditions detailed
* in the LICENCE file provided within this distribution */
#ifndef MINIUPNPC_H_INCLUDED
@@ -20,7 +20,7 @@
#define UPNPDISCOVER_MEMORY_ERROR (-102)
/* versions : */
#define MINIUPNPC_VERSION "2.1"
#define MINIUPNPC_VERSION "2.2.3"
#define MINIUPNPC_API_VERSION 17
/* Source port:
+1 -1
View File
@@ -7,7 +7,7 @@
#ifndef MINIUPNPC_SOCKETDEF_H_INCLUDED
#define MINIUPNPC_SOCKETDEF_H_INCLUDED
#ifdef _MSC_VER
#ifdef _WIN32
#define ISINVALID(s) (INVALID_SOCKET==(s))
+4 -2
View File
@@ -1,13 +1,15 @@
/* $Id: miniupnpctypes.h,v 1.1 2011/02/15 11:10:40 nanard Exp $ */
/* Miniupnp project : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org
* Author : Thomas Bernard
* Copyright (c) 2011 Thomas Bernard
* Copyright (c) 2021 Thomas Bernard
* This software is subject to the conditions detailed in the
* LICENCE file provided within this distribution */
#ifndef MINIUPNPCTYPES_H_INCLUDED
#define MINIUPNPCTYPES_H_INCLUDED
#if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)
/* Use unsigned long long when available :
* strtoull is C99 */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#define UNSIGNED_INTEGER unsigned long long
#define STRTOUI strtoull
#else
+65
View File
@@ -0,0 +1,65 @@
/* $Id: portlistingparse.h,v 1.10 2014/11/01 10:37:32 nanard Exp $ */
/* MiniUPnP project
* http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
* (c) 2011-2015 Thomas Bernard
* This software is subject to the conditions detailed
* in the LICENCE file provided within the distribution */
#ifndef PORTLISTINGPARSE_H_INCLUDED
#define PORTLISTINGPARSE_H_INCLUDED
#include "miniupnpc_declspec.h"
/* for the definition of UNSIGNED_INTEGER */
#include "miniupnpctypes.h"
#ifdef __cplusplus
extern "C" {
#endif
/* sample of PortMappingEntry :
<p:PortMappingEntry>
<p:NewRemoteHost>202.233.2.1</p:NewRemoteHost>
<p:NewExternalPort>2345</p:NewExternalPort>
<p:NewProtocol>TCP</p:NewProtocol>
<p:NewInternalPort>2345</p:NewInternalPort>
<p:NewInternalClient>192.168.1.137</p:NewInternalClient>
<p:NewEnabled>1</p:NewEnabled>
<p:NewDescription>dooom</p:NewDescription>
<p:NewLeaseTime>345</p:NewLeaseTime>
</p:PortMappingEntry>
*/
typedef enum { PortMappingEltNone,
PortMappingEntry, NewRemoteHost,
NewExternalPort, NewProtocol,
NewInternalPort, NewInternalClient,
NewEnabled, NewDescription,
NewLeaseTime } portMappingElt;
struct PortMapping {
struct PortMapping * l_next; /* list next element */
UNSIGNED_INTEGER leaseTime;
unsigned short externalPort;
unsigned short internalPort;
char remoteHost[64];
char internalClient[64];
char description[64];
char protocol[4];
unsigned char enabled;
};
struct PortMappingParserData {
struct PortMapping * l_head; /* list head */
portMappingElt curelt;
};
MINIUPNP_LIBSPEC void
ParsePortListing(const char * buffer, int bufsize,
struct PortMappingParserData * pdata);
MINIUPNP_LIBSPEC void
FreePortListing(struct PortMappingParserData * pdata);
#ifdef __cplusplus
}
#endif
#endif
+12 -4
View File
@@ -1,8 +1,8 @@
/* $Id: upnpdev.h,v 1.1 2015/08/28 12:14:19 nanard Exp $ */
/* $Id: upnpdev.h,v 1.4 2021/08/21 09:45:01 nanard Exp $ */
/* Project : miniupnp
* Web : http://miniupnp.free.fr/
* Web : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
* Author : Thomas BERNARD
* copyright (c) 2005-2018 Thomas Bernard
* copyright (c) 2005-2021 Thomas Bernard
* This software is subjet to the conditions detailed in the
* provided LICENSE file. */
#ifndef UPNPDEV_H_INCLUDED
@@ -20,7 +20,15 @@ struct UPNPDev {
char * st;
char * usn;
unsigned int scope_id;
char buffer[3];
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
/* C99 flexible array member */
char buffer[];
#elif defined(__GNUC__)
char buffer[0];
#else
/* Fallback to a hack */
char buffer[1];
#endif
};
/* freeUPNPDevlist()
+63
View File
@@ -0,0 +1,63 @@
/* $Id: upnpreplyparse.h,v 1.19 2014/10/27 16:33:19 nanard Exp $ */
/* MiniUPnP project
* http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
* (c) 2006-2013 Thomas Bernard
* This software is subject to the conditions detailed
* in the LICENCE file provided within the distribution */
#ifndef UPNPREPLYPARSE_H_INCLUDED
#define UPNPREPLYPARSE_H_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif
struct NameValue {
struct NameValue * l_next;
char name[64];
char value[128];
};
struct NameValueParserData {
struct NameValue * l_head;
char curelt[64];
char * portListing;
int portListingLength;
int topelt;
const char * cdata;
int cdatalen;
};
/* ParseNameValue() */
void
ParseNameValue(const char * buffer, int bufsize,
struct NameValueParserData * data);
/* ClearNameValueList() */
void
ClearNameValueList(struct NameValueParserData * pdata);
/* GetValueFromNameValueList() */
char *
GetValueFromNameValueList(struct NameValueParserData * pdata,
const char * Name);
#if 0
/* GetValueFromNameValueListIgnoreNS() */
char *
GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata,
const char * Name);
#endif
/* DisplayNameValueList() */
#ifdef DEBUG
void
DisplayNameValueList(char * buffer, int bufsize);
#endif
#ifdef __cplusplus
}
#endif
#endif
+7
View File
@@ -51,6 +51,13 @@ typedef unsigned short uint16_t;
#endif /* _WIN32 */
#include "natpmp_declspec.h"
/* Set to 9 by https://tools.ietf.org/html/rfc6886#section-3.1 which leads to a
* maximum timeout of 127.75 seconds, due to the initial 250 ms timeout doubling
* each time, so we allow a compile-time modification here.*/
#ifndef NATPMP_MAX_RETRIES
#define NATPMP_MAX_RETRIES (9)
#endif
typedef struct {
int s; /* socket */
in_addr_t gateway; /* default gateway (IPv4) */
+2 -2
View File
@@ -1,5 +1,5 @@
#ifndef DECLSPEC_H_INCLUDED
#define DECLSPEC_H_INCLUDED
#ifndef NATPMP_DECLSPEC_H_INCLUDED
#define NATPMP_DECLSPEC_H_INCLUDED
#if defined(_WIN32) && !defined(NATPMP_STATICLIB)
/* for windows dll */
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+33 -1
View File
@@ -1,6 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?define VCREDIST_VER = "14.36.32532.0" ?>
<?define VCREDIST_X86_SIZE = "13837672" ?>
<?define VCREDIST_X86_SHA1 = "C9B5B7969E499A4FD9E580EF4187322778E1936A" ?>
<?define VCREDIST_X86_URL = "https://download.visualstudio.microsoft.com/download/pr/eaab1f82-787d-4fd7-8c73-f782341a0c63/5365A927487945ECB040E143EA770ADBB296074ECE4021B1D14213BDE538C490/VC_redist.x86.exe" ?>
<?define VCREDIST_X86_UPGRADE_CODE = "65E5BD06-6392-3027-8C26-853107D3CF1A" ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Bundle Name="Moonlight Internet Hosting Tool" Manufacturer="Moonlight Game Streaming Project" Version="!(bind.packageVersion.mish)" UpgradeCode="a2ce5056-1114-44b9-b79b-952ef46d3d50">
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
<bal:WixStandardBootstrapperApplication
@@ -12,7 +20,31 @@
<WixVariable Id="WixUILicenseRtf" Value="" />
<util:ProductSearch Id="VCREDIST_14_x86"
UpgradeCode="$(var.VCREDIST_X86_UPGRADE_CODE)"
Result="version"
Variable="VCREDIST_14_x86" />
<Chain>
<ExePackage Name="Microsoft Visual C++ 2015-2022 Redistributable - x86"
Cache="no"
Compressed="no"
PerMachine="yes"
Permanent="yes"
Vital="yes"
InstallCommand="/install /quiet /norestart"
DownloadUrl="$(var.VCREDIST_X86_URL)"
DetectCondition="VCREDIST_14_x86 &gt;= v$(var.VCREDIST_VER)">
<RemotePayload Description="Microsoft Visual C++ 2015-2022 Redistributable - x86"
ProductName="Microsoft Visual C++ 2015-2022 Redistributable - x86"
Size="$(var.VCREDIST_X86_SIZE)"
Version="$(var.VCREDIST_VER)"
Hash="$(var.VCREDIST_X86_SHA1)"/>
<!-- Newer version installed is fine -->
<ExitCode Value="1638" Behavior="success" />
</ExePackage>
<MsiPackage Id="mish" SourceFile="$(var.mish.TargetPath)" />
<MsiPackage SourceFile="$(var.GSv6FwdSetup.TargetPath)" />
</Chain>
+4
View File
@@ -26,6 +26,10 @@
<HintPath>$(WixExtDir)\WixBalExtension.dll</HintPath>
<Name>WixBalExtension</Name>
</WixExtension>
<WixExtension Include="WixUtilExtension">
<HintPath>$(WixExtDir)\WixUtilExtension.dll</HintPath>
<Name>WixUtilExtension</Name>
</WixExtension>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GS-IPv6-Forwarder\GSv6FwdSetup\GSv6FwdSetup.wixproj">
-20
View File
@@ -17,52 +17,32 @@ Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "GSv6FwdSetup", "GS-IPv6-For
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B71C4E7F-0D92-4E19-BBD2-D33F0EF879B1}.Debug|x64.ActiveCfg = Debug|x64
{B71C4E7F-0D92-4E19-BBD2-D33F0EF879B1}.Debug|x64.Build.0 = Debug|x64
{B71C4E7F-0D92-4E19-BBD2-D33F0EF879B1}.Debug|x86.ActiveCfg = Debug|Win32
{B71C4E7F-0D92-4E19-BBD2-D33F0EF879B1}.Debug|x86.Build.0 = Debug|Win32
{B71C4E7F-0D92-4E19-BBD2-D33F0EF879B1}.Release|x64.ActiveCfg = Release|x64
{B71C4E7F-0D92-4E19-BBD2-D33F0EF879B1}.Release|x64.Build.0 = Release|x64
{B71C4E7F-0D92-4E19-BBD2-D33F0EF879B1}.Release|x86.ActiveCfg = Release|Win32
{B71C4E7F-0D92-4E19-BBD2-D33F0EF879B1}.Release|x86.Build.0 = Release|Win32
{8F78D8D2-A837-489C-BAC5-81494C9CEF7A}.Debug|x64.ActiveCfg = Debug|x64
{8F78D8D2-A837-489C-BAC5-81494C9CEF7A}.Debug|x64.Build.0 = Debug|x64
{8F78D8D2-A837-489C-BAC5-81494C9CEF7A}.Debug|x86.ActiveCfg = Debug|Win32
{8F78D8D2-A837-489C-BAC5-81494C9CEF7A}.Debug|x86.Build.0 = Debug|Win32
{8F78D8D2-A837-489C-BAC5-81494C9CEF7A}.Release|x64.ActiveCfg = Release|x64
{8F78D8D2-A837-489C-BAC5-81494C9CEF7A}.Release|x64.Build.0 = Release|x64
{8F78D8D2-A837-489C-BAC5-81494C9CEF7A}.Release|x86.ActiveCfg = Release|Win32
{8F78D8D2-A837-489C-BAC5-81494C9CEF7A}.Release|x86.Build.0 = Release|Win32
{F0DEE5F3-4B62-47A3-B00B-61C614C924FD}.Debug|x64.ActiveCfg = Debug|x86
{F0DEE5F3-4B62-47A3-B00B-61C614C924FD}.Debug|x86.ActiveCfg = Debug|x86
{F0DEE5F3-4B62-47A3-B00B-61C614C924FD}.Debug|x86.Build.0 = Debug|x86
{F0DEE5F3-4B62-47A3-B00B-61C614C924FD}.Release|x64.ActiveCfg = Release|x86
{F0DEE5F3-4B62-47A3-B00B-61C614C924FD}.Release|x86.ActiveCfg = Release|x86
{F0DEE5F3-4B62-47A3-B00B-61C614C924FD}.Release|x86.Build.0 = Release|x86
{A2CE5056-1114-44B9-B79B-952EF46D3D50}.Debug|x64.ActiveCfg = Debug|x86
{A2CE5056-1114-44B9-B79B-952EF46D3D50}.Debug|x86.ActiveCfg = Debug|x86
{A2CE5056-1114-44B9-B79B-952EF46D3D50}.Debug|x86.Build.0 = Debug|x86
{A2CE5056-1114-44B9-B79B-952EF46D3D50}.Release|x64.ActiveCfg = Release|x86
{A2CE5056-1114-44B9-B79B-952EF46D3D50}.Release|x86.ActiveCfg = Release|x86
{A2CE5056-1114-44B9-B79B-952EF46D3D50}.Release|x86.Build.0 = Release|x86
{87DEAE49-7638-4CDB-88EB-054B1F3CB0D2}.Debug|x64.ActiveCfg = Debug|x64
{87DEAE49-7638-4CDB-88EB-054B1F3CB0D2}.Debug|x64.Build.0 = Debug|x64
{87DEAE49-7638-4CDB-88EB-054B1F3CB0D2}.Debug|x86.ActiveCfg = Debug|Win32
{87DEAE49-7638-4CDB-88EB-054B1F3CB0D2}.Debug|x86.Build.0 = Debug|Win32
{87DEAE49-7638-4CDB-88EB-054B1F3CB0D2}.Release|x64.ActiveCfg = Release|x64
{87DEAE49-7638-4CDB-88EB-054B1F3CB0D2}.Release|x64.Build.0 = Release|x64
{87DEAE49-7638-4CDB-88EB-054B1F3CB0D2}.Release|x86.ActiveCfg = Release|Win32
{87DEAE49-7638-4CDB-88EB-054B1F3CB0D2}.Release|x86.Build.0 = Release|Win32
{F8171B99-F5F9-4ABF-9FE5-6753539611AF}.Debug|x64.ActiveCfg = Debug|x86
{F8171B99-F5F9-4ABF-9FE5-6753539611AF}.Debug|x86.ActiveCfg = Debug|x86
{F8171B99-F5F9-4ABF-9FE5-6753539611AF}.Debug|x86.Build.0 = Debug|x86
{F8171B99-F5F9-4ABF-9FE5-6753539611AF}.Release|x64.ActiveCfg = Release|x86
{F8171B99-F5F9-4ABF-9FE5-6753539611AF}.Release|x86.ActiveCfg = Release|x86
{F8171B99-F5F9-4ABF-9FE5-6753539611AF}.Release|x86.Build.0 = Release|x86
EndGlobalSection
+80
View File
@@ -14,6 +14,28 @@
</Feature>
</Product>
<!-- Enable the Windows Firewall service if it is disabled. Disabling the Windows Firewall service
will paradoxically block *all* incoming network traffic and prevent GameStream, MIST, MISS,
and other local servers from working at all. The proper way to disable Windows Firewall is via
the Windows Firewall control panel applet. -->
<Fragment>
<Property Id="MPSSVC_START">
<RegistrySearch Id="MpsSvcStart"
Root="HKLM"
Key="System\CurrentControlSet\Services\MpsSvc"
Name="Start"
Type="raw" />
</Property>
<SetProperty Id="EnableMpsSvc" Value='"[SystemFolder]sc.exe" config MpsSvc start= auto' Sequence="execute" Before="InstallInitialize"/>
<CustomAction Id="EnableMpsSvc" BinaryKey="WixCA" DllEntry="WixQuietExec"
Execute="deferred" Return="ignore" Impersonate="no"/>
<SetProperty Id="StartMpsSvc" Value='"[SystemFolder]net.exe" start MpsSvc' Sequence="execute" Before="InstallInitialize"/>
<CustomAction Id="StartMpsSvc" BinaryKey="WixCA" DllEntry="WixQuietExec"
Execute="deferred" Return="ignore" Impersonate="no"/>
</Fragment>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
@@ -30,6 +52,12 @@
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="MiniupnpcDll">
<File Source="$(var.miss.TargetDir)miniupnpc.dll" KeyPath="yes"/>
</Component>
<Component Id="LibnatpmpDll">
<File Source="$(var.miss.TargetDir)libnatpmp.dll" KeyPath="yes"/>
</Component>
<Component Id="MISS">
<File Source="$(var.miss.TargetPath)" KeyPath="yes">
<fire:FirewallException Id="MISSFwException"
@@ -48,6 +76,7 @@
ErrorControl="ignore"
Interactive="no">
<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>
@@ -58,6 +87,48 @@
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"/>
<fire:FirewallException Id="MdnsFwException"
Scope="any"
Port="5353"
Protocol="udp"
Name="Moonlight - mDNS"/>
</Component>
<Component Id="Shortcuts" Guid="*">
<Shortcut Id="StartMenuShortcut"
Name="Moonlight Internet Streaming Tester"
@@ -69,5 +140,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>
+334 -126
View File
@@ -12,6 +12,7 @@
#include <assert.h>
#include <stdlib.h>
#include "relay.h"
#include "..\version.h"
#pragma comment(lib, "miniupnpc.lib")
@@ -31,13 +32,12 @@ bool getHopsIP4(IN_ADDR* hopAddress, int* hopAddressCount);
struct UPNPDev* getUPnPDevicesByAddress(IN_ADDR address);
bool PCPMapPort(PSOCKADDR_STORAGE localAddr, int localAddrLen, PSOCKADDR_STORAGE pcpAddr, int pcpAddrLen, int proto, int port, bool enable, bool indefinite);
#define NL "\n"
#define SERVICE_NAME "MISS"
#define UPNP_SERVICE_NAME "Moonlight"
#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 +53,12 @@ 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)
static HANDLE s_StopEvent;
static CRITICAL_SECTION s_PortMappingUpdateLock;
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];
@@ -94,16 +97,41 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
intClient, intPort, desc, enabled, leaseDuration);
if (err == 714) {
// NoSuchEntryInArray
printf("NOT FOUND" NL);
printf("NOT FOUND\n");
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\n");
// 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)\n", 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)\n", leaseDuration, intPort);
}
// If we're just validating, we found an entry, so we're done.
if (validationPass) {
return true;
}
if (!enable) {
@@ -111,17 +139,22 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
printf("Deleting UPnP mapping for %s %s -> %s...", protoStr, portStr, myAddr);
err = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, portStr, protoStr, nullptr);
if (err == UPNPCOMMAND_SUCCESS) {
printf("OK" NL);
printf("OK\n");
}
else {
printf("ERROR %d" NL, err);
printf("ERROR %d\n", err);
}
return true;
}
}
else {
printf("CONFLICT: %s %s" NL, intClient, desc);
printf("CONFLICT: %s %s\n", 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,
@@ -131,21 +164,25 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
printf("Trying to delete conflicting UPnP mapping for %s %s -> %s...", protoStr, portStr, intClient);
err = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, portStr, protoStr, nullptr);
if (err == UPNPCOMMAND_SUCCESS) {
printf("OK" NL);
printf("OK\n");
}
else if (err == 606) {
printf("UNAUTHORIZED" NL);
printf("UNAUTHORIZED\n");
return false;
}
else {
printf("ERROR %d" NL, err);
printf("ERROR %d\n", err);
return false;
}
}
}
}
else {
printf("ERROR %d (%s)" NL, err, strupnperror(err));
printf("ERROR %d (%s)\n", 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,28 +197,45 @@ 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);
printf("OK\n");
return true;
}
else {
printf("ERROR %d (%s)" NL, err, strupnperror(err));
printf("ERROR %d (%s)\n", err, strupnperror(err));
return false;
}
}
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 +244,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\n", 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);
printf("GetAdaptersAddresses() failed: %d\n", error);
free(addresses);
return false;
}
currentAdapter = &addresses;
currentAdapter = addresses;
currentAddress = nullptr;
while (currentAdapter != nullptr) {
currentAddress = currentAdapter->FirstUnicastAddress;
@@ -216,6 +282,7 @@ bool GetIP4OnLinkPrefixLength(char* lanAddressString, int* prefixLength)
if (RtlEqualMemory(&currentAddrV4->sin_addr, &targetAddress, sizeof(targetAddress))) {
*prefixLength = currentAddress->OnLinkPrefixLength;
free(addresses);
return true;
}
@@ -225,7 +292,8 @@ bool GetIP4OnLinkPrefixLength(char* lanAddressString, int* prefixLength)
currentAdapter = currentAdapter->Next;
}
printf("No adapter found with IPv4 address: %s" NL, lanAddressString);
printf("No adapter found with IPv4 address: %s\n", lanAddressString);
free(addresses);
return false;
}
@@ -240,31 +308,31 @@ bool UPnPHandleDeviceList(struct UPNPDev* list, bool enable, char* lanAddrOverri
int ret = UPNP_GetValidIGD(list, &urls, &data, localAddress, sizeof(localAddress));
if (ret == 0) {
printf("No UPnP device found!" NL);
printf("No UPnP device found!\n");
return false;
}
else if (ret == 3) {
printf("No UPnP IGD found!" NL);
printf("No UPnP IGD found!\n");
FreeUPNPUrls(&urls);
return false;
}
else if (ret == 1) {
printf("Found a connected UPnP IGD" NL);
printf("Found a connected UPnP IGD (%s)\n", urls.rootdescURL);
}
else if (ret == 2) {
printf("Found a disconnected UPnP IGD (!)" NL);
printf("Found a disconnected (!) UPnP IGD (%s)\n", urls.rootdescURL);
// Even if we are able to add forwarding entries, go ahead and try NAT-PMP
success = false;
}
else {
printf("UPNP_GetValidIGD() failed: %d" NL, ret);
printf("UPNP_GetValidIGD() failed: %d\n", ret);
return false;
}
ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, wanAddr);
if (ret == UPNPCOMMAND_SUCCESS) {
printf("UPnP IGD WAN address is: %s" NL, wanAddr);
printf("UPnP IGD WAN address is: %s\n", wanAddr);
}
else {
// Empty string
@@ -279,8 +347,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 +373,32 @@ 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 or the stop event to be set
printf("Waiting before UPnP port validation...");
if (WaitForSingleObject(s_StopEvent, 10000) == WAIT_TIMEOUT) {
printf("done\n");
// 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;
}
}
}
else {
printf("aborted\n");
}
}
@@ -344,13 +432,13 @@ 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);
int err = sendnewportmappingrequest(natpmp, natPmpProto, port, enable ? port : 0, lifetime);
if (err < 0) {
printf("ERROR %d" NL, err);
printf("ERROR %d\n", err);
return false;
}
@@ -366,7 +454,7 @@ bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable, bool inde
err = getnatpmprequesttimeout(natpmp, &timeout);
if (err != 0) {
assert(err == 0);
printf("WAIT FAILED: %d" NL, err);
printf("WAIT FAILED: %d\n", err);
return false;
}
@@ -376,22 +464,22 @@ bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable, bool inde
} while (err == NATPMP_TRYAGAIN);
if (err != 0) {
printf("FAILED %d" NL, err);
printf("FAILED %d\n", err);
return false;
}
else if (response.pnu.newportmapping.lifetime == 0 && !enable) {
printf("DELETED" NL);
printf("DELETED\n");
return true;
}
else if (response.pnu.newportmapping.mappedpublicport != port) {
printf("CONFLICT" NL);
printf("CONFLICT\n");
// 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);
err = sendnewportmappingrequest(natpmp, natPmpProto, response.pnu.newportmapping.privateport, 0, 0);
if (err < 0) {
printf("ERROR %d" NL, err);
printf("ERROR %d\n", err);
return false;
}
else {
@@ -405,7 +493,7 @@ bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable, bool inde
err = getnatpmprequesttimeout(natpmp, &timeout);
if (err != 0) {
assert(err == 0);
printf("WAIT FAILED: %d" NL, err);
printf("WAIT FAILED: %d\n", err);
return false;
}
@@ -415,17 +503,17 @@ bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable, bool inde
} while (err == NATPMP_TRYAGAIN);
if (err == 0) {
printf("OK" NL);
printf("OK\n");
return false;
}
else {
printf("FAILED %d" NL, err);
printf("FAILED %d\n", err);
return false;
}
}
}
else {
printf("OK (%d seconds remaining)" NL, response.pnu.newportmapping.lifetime);
printf("OK (%d seconds remaining)\n", response.pnu.newportmapping.lifetime);
return true;
}
}
@@ -447,17 +535,47 @@ bool IsGameStreamEnabled()
error = RegQueryValueExA(key, "EnableStreaming", nullptr, nullptr, (LPBYTE)&enabled, &len);
RegCloseKey(key);
if (error != ERROR_SUCCESS) {
printf("RegQueryValueExA() failed: %d" NL, error);
printf("RegQueryValueExA() failed: %d\n", error);
return false;
}
else if (!enabled) {
printf("GameStream is OFF!" NL);
return enabled != 0;
}
bool IsAlternateHostSoftwareRunning()
{
int err;
PMIB_TCPTABLE tcp_table = nullptr;
ULONG table_size = 0;
do {
// Query all open TCPv4 sockets
err = GetTcpTable(tcp_table, &table_size, false);
if (err == ERROR_INSUFFICIENT_BUFFER) {
free(tcp_table);
tcp_table = (PMIB_TCPTABLE)malloc(table_size);
}
} while (err == ERROR_INSUFFICIENT_BUFFER);
if (!tcp_table || err != NO_ERROR) {
printf("GetTcpTable() failed: %d\n", err);
free(tcp_table);
return false;
}
else {
printf("GameStream is ON!" NL);
return true;
bool result = false;
for (DWORD i = 0; i < tcp_table->dwNumEntries; i++) {
auto& entry = tcp_table->table[i];
// Look for TCP 47989 port in the listening state
if (entry.dwLocalPort == _byteswap_ushort(47989) && entry.dwState == MIB_TCP_STATE_LISTEN) {
result = true;
break;
}
}
free(tcp_table);
return result;
}
void UpdatePortMappingsForTarget(bool enable, char* targetAddressIP4, char* internalAddressIP4, char* upstreamAddressIP4)
@@ -468,18 +586,18 @@ void UpdatePortMappingsForTarget(bool enable, char* targetAddressIP4, char* inte
char upstreamAddrNatPmp[128] = {};
char upstreamAddrUPnP[128] = {};
printf("Starting port mapping update on %s to %s..." NL,
printf("Starting port mapping update on %s to %s...\n",
targetAddressIP4 ? targetAddressIP4 : "default gateway",
internalAddressIP4 ? internalAddressIP4 : "local machine");
int natPmpErr = initnatpmp(&natpmp, targetAddressIP4 ? 1 : 0, targetAddressIP4 ? inet_addr(targetAddressIP4) : 0);
if (natPmpErr != 0) {
printf("initnatpmp() failed: %d" NL, natPmpErr);
printf("initnatpmp() failed: %d\n", natPmpErr);
}
else {
natPmpErr = sendpublicaddressrequest(&natpmp);
if (natPmpErr < 0) {
printf("sendpublicaddressrequest() failed: %d" NL, natPmpErr);
printf("sendpublicaddressrequest() failed: %d\n", natPmpErr);
closenatpmp(&natpmp);
}
}
@@ -493,7 +611,7 @@ void UpdatePortMappingsForTarget(bool enable, char* targetAddressIP4, char* inte
if (targetAddressIP4 == nullptr) {
// If we have no target, use discovery to find the first hop
ipv4Devs = upnpDiscoverAll(UPNP_DISCOVERY_DELAY_MS, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 0, 2, &upnpErr);
printf("UPnP IPv4 IGD discovery completed with error code: %d" NL, upnpErr);
printf("UPnP IPv4 IGD discovery completed with error code: %d\n", upnpErr);
}
else {
// We have a specified target, so do discovery against that directly (may be outside our subnet in case of double-NAT)
@@ -502,23 +620,29 @@ void UpdatePortMappingsForTarget(bool enable, char* targetAddressIP4, char* inte
ipv4Devs = getUPnPDevicesByAddress(addr);
}
// Abort if this is an add/update request and we're stopping
if (enable && WaitForSingleObject(s_StopEvent, 0) == WAIT_OBJECT_0) {
printf("Aborting port mapping update due to stop request\n");
goto Exit;
}
// Use the delay of discovery to also allow the NAT-PMP endpoint time to respond
if (natPmpErr >= 0) {
natpmpresp_t response;
natPmpErr = readnatpmpresponseorretry(&natpmp, &response);
if (natPmpErr == 0) {
inet_ntop(AF_INET, &response.pnu.publicaddress.addr, upstreamAddrNatPmp, sizeof(upstreamAddrNatPmp));
printf("NAT-PMP upstream address is: %s" NL, upstreamAddrNatPmp);
printf("NAT-PMP upstream address is: %s\n", upstreamAddrNatPmp);
}
else {
printf("NAT-PMP public address request failed: %d" NL, natPmpErr);
printf("NAT-PMP public address request failed: %d\n", natPmpErr);
closenatpmp(&natpmp);
}
}
// Don't try NAT-PMP if UPnP succeeds
if (UPnPHandleDeviceList(ipv4Devs, enable, internalAddressIP4, upstreamAddrUPnP)) {
printf("UPnP IPv4 port mapping successful" NL);
printf("UPnP IPv4 port mapping successful\n");
if (enable) {
// We still want to try NAT-PMP if we're removing
// rules to ensure any NAT-PMP rules get cleaned up
@@ -537,7 +661,7 @@ void UpdatePortMappingsForTarget(bool enable, char* targetAddressIP4, char* inte
// if we created the rules we'd be deleting. Since we don't have that, we can't
// safely remove mappings that could be shared by another machine behind a double NAT.
if (!enable && targetAddressIP4 != nullptr) {
printf("Not removing upstream NAT-PMP mappings on non-default gateway device" NL);
printf("Not removing upstream NAT-PMP mappings on non-default gateway device\n");
tryNatPmp = false;
}
@@ -573,7 +697,7 @@ void UpdatePortMappingsForTarget(bool enable, char* targetAddressIP4, char* inte
}
if (success) {
printf("NAT-PMP IPv4 port mapping successful" NL);
printf("NAT-PMP IPv4 port mapping successful\n");
// Always try all possibilities when disabling to ensure
// we completely clean up
@@ -605,7 +729,7 @@ void UpdatePortMappingsForTarget(bool enable, char* targetAddressIP4, char* inte
targetAddr.sin_addr.S_un.S_addr = route.dwForwardNextHop;
}
else {
printf("GetBestRoute() failed: %d" NL, error);
printf("GetBestRoute() failed: %d\n", error);
goto Exit;
}
}
@@ -633,22 +757,22 @@ void UpdatePortMappingsForTarget(bool enable, char* targetAddressIP4, char* inte
}
if (success) {
printf("PCP IPv4 port mapping successful" NL);
printf("PCP IPv4 port mapping successful\n");
}
}
Exit:
// Write this at the end to avoid clobbering an input parameter
if (upstreamAddrNatPmp[0] != 0 && inet_addr(upstreamAddrNatPmp) != 0) {
printf("Using NAT-PMP upstream IPv4 address: %s" NL, upstreamAddrNatPmp);
printf("Using NAT-PMP upstream IPv4 address: %s\n", upstreamAddrNatPmp);
strcpy(upstreamAddressIP4, upstreamAddrNatPmp);
}
else if (upstreamAddrUPnP[0] != 0 && inet_addr(upstreamAddrUPnP) != 0) {
printf("Using UPnP upstream IPv4 address: %s" NL, upstreamAddrUPnP);
printf("Using UPnP upstream IPv4 address: %s\n", upstreamAddrUPnP);
strcpy(upstreamAddressIP4, upstreamAddrUPnP);
}
else {
printf("No valid upstream IPv4 address found!" NL);
printf("No valid upstream IPv4 address found!\n");
upstreamAddressIP4[0] = 0;
}
}
@@ -684,12 +808,12 @@ void UpdatePortMappings(bool enable)
char upstreamAddrStr[128];
unsigned long upstreamAddr;
printf("Finding upstream IPv4 hops via traceroute..." NL);
printf("Finding upstream IPv4 hops via traceroute...\n");
if (!getHopsIP4(hops, &hopCount)) {
hopCount = 0;
}
else {
printf("Found %d hops" NL, hopCount);
printf("Found %d hops\n", hopCount);
}
// getHopsIP4() already skips the default gateway, so 0
@@ -701,10 +825,10 @@ void UpdatePortMappings(bool enable)
while (upstreamAddrStr[0] != 0 && (upstreamAddr = inet_addr(upstreamAddrStr)) != 0) {
// We got an upstream address. Let's check if this is a NAT
if (IsLikelyNAT(upstreamAddr)) {
printf("Upstream address %s is likely a NAT" NL, upstreamAddrStr);
printf("Upstream address %s is likely a NAT\n", upstreamAddrStr);
if (nextHopIndex >= hopCount) {
printf("Traceroute didn't reach this hop! Aborting!" NL);
printf("Traceroute didn't reach this hop! Aborting!\n");
break;
}
@@ -718,7 +842,7 @@ void UpdatePortMappings(bool enable)
}
else {
// If we reach a proper public IP address, we're done
printf("Reached the Internet at hop %d" NL, nextHopIndex);
printf("Reached the Internet at hop %d\n", nextHopIndex);
break;
}
@@ -734,68 +858,105 @@ 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
if (freopen(currentLogFilePath, "w", stdout) == NULL) {
// If we couldn't create a log file, just redirect stdout to NUL.
// We have to open _something_ or printf() will crash.
freopen("NUL", "w", stdout);
}
}
// Print a log header
printf("Moonlight Internet Streaming Service v" VER_VERSION_STR NL);
printf("Moonlight Internet Streaming Service v" VER_VERSION_STR "\n");
// Print the current time
GetSystemTime(&time);
GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &time, "hh':'mm':'ss tt", timeString, ARRAYSIZE(timeString));
printf("The current UTC time is: %s" NL, timeString);
printf("The current UTC time is: %s\n", timeString);
}
DWORD WINAPI GameStreamStateChangeThread(PVOID Context)
{
HKEY key;
DWORD err;
// We're watching this key that way we can still detect GameStream turning on
// if GFE wasn't even installed when our service started
DWORD err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\NVIDIA Corporation", 0, KEY_READ | KEY_WOW64_64KEY, &key);
if (err != ERROR_SUCCESS) {
printf("RegOpenKeyExA() failed: %d" NL, err);
return err;
}
do {
// We're watching this key that way we can still detect GameStream turning on
// if GFE wasn't even installed when our service started
do {
err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\NVIDIA Corporation", 0, KEY_READ | KEY_WOW64_64KEY, &key);
if (err != ERROR_SUCCESS) {
// Wait 10 seconds and try again
Sleep(10000);
}
} while (err != ERROR_SUCCESS);
// Notify the main thread when the GameStream state changes
bool lastGameStreamState = IsGameStreamEnabled();
while ((err = RegNotifyChangeKeyValue(key, true, REG_NOTIFY_CHANGE_LAST_SET, nullptr, false)) == ERROR_SUCCESS) {
bool currentGameStreamState = IsGameStreamEnabled();
if (lastGameStreamState != currentGameStreamState) {
SetEvent((HANDLE)Context);
// Notify the main thread when the GameStream state changes
bool lastGameStreamState = IsGameStreamEnabled();
while ((err = RegNotifyChangeKeyValue(key, true, REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET, nullptr, false)) == ERROR_SUCCESS) {
bool currentGameStreamState = IsGameStreamEnabled();
if (lastGameStreamState != currentGameStreamState) {
SetEvent((HANDLE)Context);
}
lastGameStreamState = currentGameStreamState;
}
lastGameStreamState = currentGameStreamState;
}
printf("RegNotifyChangeKeyValue() failed: %d" NL, err);
// If the key is deleted (by DDU or similar), we will hit this code path and poll until it comes back.
RegCloseKey(key);
} while (err == ERROR_KEY_DELETED);
return err;
}
int Run()
int Initialize()
{
// Create the stop event
s_StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (s_StopEvent == NULL) {
return GetLastError();
}
InitializeCriticalSection(&s_PortMappingUpdateLock);
return 0;
}
int Run(bool standaloneExe)
{
HANDLE ifaceChangeEvent = CreateEvent(nullptr, true, false, nullptr);
HANDLE gsChangeEvent = CreateEvent(nullptr, true, false, nullptr);
HANDLE events[2] = { ifaceChangeEvent, gsChangeEvent };
HANDLE events[3] = { ifaceChangeEvent, gsChangeEvent, s_StopEvent };
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);
@@ -807,34 +968,66 @@ int Run()
for (;;) {
ResetEvent(gsChangeEvent);
ResetEvent(ifaceChangeEvent);
UpdatePortMappings(IsGameStreamEnabled());
bool gameStreamEnabled = IsGameStreamEnabled();
if (gameStreamEnabled) {
printf("GFE GameStream is ON!\n");
}
else {
printf("GFE GameStream is OFF!\n");
if (IsAlternateHostSoftwareRunning()) {
printf("Sunshine is RUNNING!\n");
gameStreamEnabled = true;
}
else {
printf("Sunshine is NOT RUNNING!\n");
}
}
// Acquire the mapping lock and update port mappings
if (TryEnterCriticalSection(&s_PortMappingUpdateLock)) {
// If the stop event is set, bail out now
if (WaitForSingleObject(s_StopEvent, 0) == WAIT_OBJECT_0) {
LeaveCriticalSection(&s_PortMappingUpdateLock);
return 0;
}
UpdatePortMappings(gameStreamEnabled);
LeaveCriticalSection(&s_PortMappingUpdateLock);
}
// Refresh when half the duration is expired or if an IP interface
// change event occurs.
printf("Going to sleep..." NL);
printf("Going to sleep...\n");
fflush(stdout);
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,
printf("Woke up for interface change notification after %lld seconds\n",
(GetTickCount64() - beforeSleepTime) / 1000);
// Wait a little bit for the interface to settle down (DHCP, RA, etc)
Sleep(10000);
}
else if (ret == WAIT_OBJECT_0 + 1) {
ResetLogFile();
ResetLogFile(standaloneExe);
printf("Woke up for GameStream state change notification after %lld seconds" NL,
printf("Woke up for GameStream state change notification after %lld seconds\n",
(GetTickCount64() - beforeSleepTime) / 1000);
}
else if (ret == WAIT_OBJECT_0 + 2) {
printf("Woke up for stop notification\n");
return 0;
}
else {
ResetLogFile();
ResetLogFile(standaloneExe);
printf("Woke up for periodic refresh" NL);
printf("Woke up for periodic refresh\n");
}
}
}
@@ -852,14 +1045,21 @@ HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpConte
return NO_ERROR;
case SERVICE_CONTROL_STOP:
// Stop future port mapping updates
SetEvent(s_StopEvent);
ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
ServiceStatus.dwControlsAccepted = 0;
ServiceStatus.dwWaitHint = 120 * 1000; // 2 minutes
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
// Remove existing port mappings
EnterCriticalSection(&s_PortMappingUpdateLock);
printf("Removing UPnP/NAT-PMP/PCP rules after service stop request\n");
UpdatePortMappings(false);
LeaveCriticalSection(&s_PortMappingUpdateLock);
printf("The service is stopping\n");
printf("The service is stopping now\n");
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
return NO_ERROR;
@@ -877,7 +1077,15 @@ ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
ServiceStatusHandle = RegisterServiceCtrlHandlerEx(SERVICE_NAME, HandlerEx, NULL);
if (ServiceStatusHandle == NULL) {
fprintf(stderr, "RegisterServiceCtrlHandlerEx() failed: %d" NL, GetLastError());
fprintf(stderr, "RegisterServiceCtrlHandlerEx() failed: %d\n", GetLastError());
return;
}
err = Initialize();
if (err != 0) {
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = err;
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
return;
}
@@ -893,7 +1101,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,8 +1124,8 @@ int main(int argc, char* argv[])
}
if (argc == 2 && !strcmp(argv[1], "exe")) {
Run();
return 0;
Initialize();
return Run(true);
}
return StartServiceCtrlDispatcher(ServiceTable);
+30 -6
View File
@@ -29,26 +29,26 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
@@ -93,13 +93,19 @@
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>..\libs\include;..\libs\include\miniupnpc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<ControlFlowGuard>Guard</ControlFlowGuard>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>..\libs\x86\Debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<CETCompat>true</CETCompat>
</Link>
<PostBuildEvent>
<Command>copy /Y "$(SolutionDir)libs\x86\Debug\*" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
@@ -111,11 +117,17 @@
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<ControlFlowGuard>Guard</ControlFlowGuard>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<CETCompat>true</CETCompat>
</Link>
<PostBuildEvent>
<Command>copy /Y "$(SolutionDir)libs\x86\Debug\*" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
@@ -130,7 +142,8 @@
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>..\libs\include;..\libs\include\miniupnpc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<ControlFlowGuard>Guard</ControlFlowGuard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -138,7 +151,11 @@
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>..\libs\x86\Release;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<CETCompat>true</CETCompat>
</Link>
<PostBuildEvent>
<Command>copy /Y "$(SolutionDir)libs\x86\Release\*" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
@@ -152,17 +169,23 @@
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<ControlFlowGuard>Guard</ControlFlowGuard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<CETCompat>true</CETCompat>
</Link>
<PostBuildEvent>
<Command>copy /Y "$(SolutionDir)libs\x86\Release\*" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="miss.cpp" />
<ClCompile Include="pcp.cpp" />
<ClCompile Include="relay.cpp" />
<ClCompile Include="tracer.cpp" />
</ItemGroup>
<ItemGroup>
@@ -170,6 +193,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>
+1 -1
View File
@@ -118,7 +118,7 @@ bool PCPMapPort(PSOCKADDR_STORAGE localAddr, int localAddrLen, PSOCKADDR_STORAGE
int bytesRead;
union {
PCP_MAP_RESPONSE hdr;
char buf[1024];
char buf[1100];
} resp;
int lifetime;
+110
View File
@@ -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;
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#define RELAY_PORT_OFFSET -10000
int StartUdpRelay(unsigned short Port);
+513 -187
View File
@@ -5,15 +5,17 @@
#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")
@@ -25,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;
@@ -232,7 +238,28 @@ bool ExecuteCommand(PCSTR command, PCHAR outputBuffer, DWORD outputBufferLength)
return true;
}
bool IsGameStreamEnabled()
bool IsSunshineRunning()
{
bool ret = false;
HANDLE processSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 procEntry;
procEntry.dwSize = sizeof(procEntry);
Process32First(processSnapshot, &procEntry);
do {
if (_stricmp(procEntry.szExeFile, "sunshine.exe") == 0) {
ret = true;
break;
}
} while (Process32Next(processSnapshot, &procEntry));
CloseHandle(processSnapshot);
return ret;
}
bool IsGameStreamEnabled(bool sunshineRunning)
{
DWORD error;
DWORD enabled;
@@ -242,8 +269,10 @@ bool IsGameStreamEnabled()
error = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\NVIDIA Corporation\\NvStream", 0, KEY_READ | KEY_WOW64_64KEY, &key);
if (error != ERROR_SUCCESS) {
fprintf(LOG_OUT, "RegOpenKeyEx() failed: %d\n", error);
DisplayMessage("GeForce Experience was not detected on this PC. Make sure you're installing this utility on your GeForce GameStream-compatible PC, not the device running Moonlight.",
"https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
if (!sunshineRunning) {
DisplayMessage("Neither GeForce Experience nor Sunshine are running on this PC. Make sure you're installing this utility on your host PC, not the device running Moonlight.",
"https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
}
return false;
}
@@ -255,8 +284,10 @@ bool IsGameStreamEnabled()
if (error != ERROR_SUCCESS) {
fprintf(LOG_OUT, "RegQueryValueExA() failed: %d\n", error);
}
DisplayMessage("GameStream is not enabled in GeForce Experience. Please open GeForce Experience settings, navigate to the Shield tab, and turn GameStream on.",
"https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
if (!sunshineRunning) {
DisplayMessage("GameStream is not enabled in GeForce Experience. Please open GeForce Experience settings, navigate to the Shield tab, and turn GameStream on.",
"https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
}
return false;
}
else {
@@ -265,6 +296,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;
@@ -374,12 +427,42 @@ bool IsZeroTierInstalled()
return true;
}
PIP_ADAPTER_ADDRESSES
AllocAndGetAdaptersAddresses(ULONG Family, ULONG Flags)
{
PIP_ADAPTER_ADDRESSES addresses;
ULONG length;
ULONG error;
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 NULL;
}
error = GetAdaptersAddresses(Family, Flags, NULL, addresses, &length);
} while (error == ERROR_BUFFER_OVERFLOW);
if (error != ERROR_SUCCESS) {
fprintf(LOG_OUT, "GetAdaptersAddresses() failed: %d\n", error);
free(addresses);
return NULL;
}
return addresses;
}
enum PortTestStatus {
PortTestOk,
PortTestError,
PortTestUnknown
};
PortTestStatus TestPort(PSOCKADDR_STORAGE addr, int proto, int port, bool withServer, bool isLoopbackRelay)
PortTestStatus TestPort(PSOCKADDR_STORAGE addr, PSOCKADDR_STORAGE localBindAddr, int proto, int port, bool withServer, bool isLoopbackRelay, bool mtuTest)
{
SOCKET clientSock = INVALID_SOCKET, serverSock = INVALID_SOCKET;
int err;
@@ -440,6 +523,21 @@ PortTestStatus TestPort(PSOCKADDR_STORAGE addr, int proto, int port, bool withSe
}
}
// Perform the explicit bind if a local address was provided
if (localBindAddr != nullptr) {
SOCKADDR_IN6 bind6;
int addrLen = localBindAddr->ss_family == AF_INET ?
sizeof(SOCKADDR_IN) : sizeof(SOCKADDR_IN6);
RtlCopyMemory(&bind6, localBindAddr, addrLen);
bind6.sin6_port = 0;
err = bind(clientSock, (struct sockaddr*)&bind6, addrLen);
if (err == SOCKET_ERROR) {
fprintf(LOG_OUT, "bind() failed: %d\n", WSAGetLastError());
}
}
ULONG nbIo = 1;
err = ioctlsocket(clientSock, FIONBIO, &nbIo);
if (err == SOCKET_ERROR) {
@@ -456,7 +554,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);
@@ -504,11 +602,14 @@ PortTestStatus TestPort(PSOCKADDR_STORAGE addr, int proto, int port, bool withSe
return err == 1 ? PortTestOk : PortTestError;
}
else {
const char testMsg[] = "moonlight-test";
// Video packets are up to 1040 bytes.
const char testMsg[1040] = "moonlight-test";
// Send several test packets to ensure a random lost packet doesn't make the test fail
for (int i = 0; i < 5; i++) {
err = sendto(clientSock, testMsg, sizeof(testMsg), 0, (struct sockaddr*)&sin6, addrLen);
// Send the full payload for MTU tests and the truncated payload for other tests.
err = sendto(clientSock, testMsg,
mtuTest ? sizeof(testMsg) : strlen(testMsg), 0, (struct sockaddr*)&sin6, addrLen);
if (err == SOCKET_ERROR) {
fprintf(LOG_OUT, "sendto() failed: %d\n", WSAGetLastError());
closesocket(clientSock);
@@ -546,67 +647,103 @@ 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)
bool TestAllPorts(PSOCKADDR_STORAGE addr, PSOCKADDR_STORAGE localBindAddr, char* portMsg, int portMsgLen, bool isLoopbackRelay, bool consolePrint, bool* allPortsFailed = nullptr)
{
bool ret = true;
@@ -615,9 +752,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",
@@ -625,15 +760,22 @@ 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 && (localBindAddr == nullptr || localBindAddr->ss_family == AF_INET)) {
// 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.
// NB: We can't do this with IPv6 because we're not guaranteed that our outbound traffic
// will go out on the correct local IPv6 address, preventing a response from making
// it back.
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, localBindAddr, k_Ports[i].proto, k_Ports[i].port,
k_Ports[i].withServer, isLoopbackRelay, k_Ports[i].port == 47998);
}
if (status != PortTestOk) {
@@ -660,6 +802,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;
@@ -673,9 +890,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;
}
@@ -710,6 +934,71 @@ bool FindLocalInterfaceIPAddress(int family, PSOCKADDR_STORAGE addr)
inet_ntop(AF_INET, &((struct sockaddr_in*)addr)->sin_addr, addrStr, sizeof(addrStr));
}
else {
// There may be multiple IPv6 addresses on this interface, and chances are that the
// outbound address is a temporary IPv6 address which is not meant to receive traffic.
// Let's look up a suitable address by finding the correct interface and looking for
// an address with a non-random suffix.
PIP_ADAPTER_ADDRESSES addresses;
PIP_ADAPTER_ADDRESSES currentAdapter;
PIP_ADAPTER_UNICAST_ADDRESS currentAddress;
addresses = AllocAndGetAdaptersAddresses(AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST |
GAA_FLAG_SKIP_MULTICAST |
GAA_FLAG_SKIP_DNS_SERVER |
GAA_FLAG_SKIP_FRIENDLY_NAME);
if (addresses == NULL) {
return false;
}
// First, find the interface that owns the incoming address
currentAdapter = addresses;
while (currentAdapter != NULL) {
// Check if this interface has the IPv6 address we want
currentAddress = currentAdapter->FirstUnicastAddress;
while (currentAddress != NULL) {
if (currentAddress->Address.lpSockaddr->sa_family == AF_INET6) {
PSOCKADDR_IN6 ifaceAddrV6 = (PSOCKADDR_IN6)currentAddress->Address.lpSockaddr;
if (RtlEqualMemory(&((struct sockaddr_in6*)addr)->sin6_addr, &ifaceAddrV6->sin6_addr, sizeof(IN6_ADDR))) {
break;
}
}
currentAddress = currentAddress->Next;
}
if (currentAddress != NULL) {
// It does, bail out
break;
}
currentAdapter = currentAdapter->Next;
}
// Check if we found the incoming interface. If not, we'll
// just return the IP address as given from getsockname().
if (currentAdapter != NULL) {
// Now find a non-privacy/non-LL IPv6 address on this interface
currentAddress = currentAdapter->FirstUnicastAddress;
while (currentAddress != NULL) {
if (currentAddress->Address.lpSockaddr->sa_family == AF_INET6) {
// Exclude link-local and privacy addresses and deprecated addresses
PSOCKADDR_IN6 currentAddrV6 = (PSOCKADDR_IN6)currentAddress->Address.lpSockaddr;
if (currentAddrV6->sin6_scope_id == 0 &&
currentAddress->SuffixOrigin != IpSuffixOriginRandom &&
currentAddress->DadState != IpDadStateDeprecated) {
RtlCopyMemory(addr, currentAddress->Address.lpSockaddr, currentAddress->Address.iSockaddrLength);
break;
}
}
currentAddress = currentAddress->Next;
}
}
free(addresses);
inet_ntop(AF_INET6, &((struct sockaddr_in6*)addr)->sin6_addr, addrStr, sizeof(addrStr));
}
fprintf(LOG_OUT, "%s\n", addrStr);
@@ -719,31 +1008,21 @@ bool FindLocalInterfaceIPAddress(int family, PSOCKADDR_STORAGE addr)
bool FindZeroTierInterfaceAddress(PSOCKADDR_STORAGE addr)
{
union {
IP_ADAPTER_ADDRESSES addresses;
char buffer[8192];
};
ULONG error;
ULONG length;
PIP_ADAPTER_ADDRESSES addresses;
PIP_ADAPTER_ADDRESSES currentAdapter;
PIP_ADAPTER_UNICAST_ADDRESS currentAddress;
// Get all IPv4 interfaces
length = sizeof(buffer);
error = GetAdaptersAddresses(AF_INET,
addresses = AllocAndGetAdaptersAddresses(AF_INET,
GAA_FLAG_SKIP_ANYCAST |
GAA_FLAG_SKIP_MULTICAST |
GAA_FLAG_SKIP_DNS_SERVER |
GAA_FLAG_SKIP_FRIENDLY_NAME,
NULL,
&addresses,
&length);
if (error != ERROR_SUCCESS) {
fprintf(LOG_OUT, "GetAdaptersAddresses() failed: %d\n", error);
GAA_FLAG_SKIP_FRIENDLY_NAME);
if (addresses == nullptr) {
return false;
}
currentAdapter = &addresses;
currentAdapter = addresses;
while (currentAdapter != NULL) {
// Look for ones that correspond to a ZeroTier device
if (wcsstr(currentAdapter->Description, L"ZeroTier")) {
@@ -752,6 +1031,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;
}
@@ -762,17 +1042,14 @@ bool FindZeroTierInterfaceAddress(PSOCKADDR_STORAGE addr)
currentAdapter = currentAdapter->Next;
}
free(addresses);
return false;
}
bool FindDuplicateDefaultInterfaces(void)
{
union {
IP_ADAPTER_ADDRESSES addresses;
char buffer[8192];
};
PIP_ADAPTER_ADDRESSES addresses;
ULONG error;
ULONG length;
MIB_IPFORWARDROW defaultRoute;
PIP_ADAPTER_ADDRESSES currentAdapter;
DWORD matchingInterfaces = 0;
@@ -784,23 +1061,18 @@ bool FindDuplicateDefaultInterfaces(void)
}
// Get all IPv4 interfaces
length = sizeof(buffer);
error = GetAdaptersAddresses(AF_INET,
addresses = AllocAndGetAdaptersAddresses(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);
if (error != ERROR_SUCCESS) {
fprintf(LOG_OUT, "GetAdaptersAddresses() failed: %d\n", error);
GAA_FLAG_SKIP_FRIENDLY_NAME);
if (addresses == nullptr) {
return false;
}
currentAdapter = &addresses;
currentAdapter = addresses;
while (currentAdapter != NULL) {
if (currentAdapter->OperStatus == IfOperStatusUp &&
currentAdapter->FirstGatewayAddress != NULL &&
@@ -813,6 +1085,7 @@ bool FindDuplicateDefaultInterfaces(void)
currentAdapter = currentAdapter->Next;
}
free(addresses);
return matchingInterfaces > 1;
}
@@ -857,7 +1130,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 {
@@ -972,7 +1245,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);
@@ -999,7 +1272,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);
@@ -1013,16 +1286,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;
}
@@ -1080,12 +1349,28 @@ int main(int argc, char* argv[])
fprintf(CONSOLE_OUT, "Checking if GameStream is enabled...\n");
// First check if GameStream is enabled
if (!IsGameStreamEnabled()) {
bool sunshineRunning = IsSunshineRunning();
bool gfeGameStreamRunning = true;
if (!IsGameStreamEnabled(sunshineRunning)) {
if (sunshineRunning) {
DisplayMessage("The Moonlight Internet Hosting Tool is not designed for use with Sunshine.\n\n"
"To stream over the Internet with Sunshine, simply enable the UPnP option in the Sunshine Web UI.\n\n"
"Test results WILL be inaccurate if the Port option in Sunshine has been adjusted from the default value of 47989!",
nullptr, MpWarn, false);
gfeGameStreamRunning = false;
}
else {
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()) {
if (gfeGameStreamRunning && !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");
@@ -1152,9 +1437,16 @@ int main(int argc, char* argv[])
sin.sin_family = AF_INET;
sin.sin_addr = in4addr_loopback;
fprintf(LOG_OUT, "Testing GameStream ports via loopback\n");
if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf), false, true)) {
snprintf(msgBuf, sizeof(msgBuf),
"Local GameStream connectivity check failed.\n\nFirst, try reinstalling GeForce Experience. If that doesn't resolve the problem, try temporarily disabling your antivirus and firewall.");
if (!TestAllPorts(&ss, nullptr, portMsgBuf, sizeof(portMsgBuf), false, true)) {
if (gfeGameStreamRunning) {
snprintf(msgBuf, sizeof(msgBuf),
"Local GameStream connectivity check failed.\n\nFirst, try reinstalling GeForce Experience. If that doesn't resolve the problem, try temporarily disabling your antivirus and firewall.");
}
else {
snprintf(msgBuf, sizeof(msgBuf),
"Local GameStream connectivity check failed.\n\nFirst, try restarting Sunshine. If that doesn't resolve the problem, try temporarily disabling your antivirus and firewall.\n\nNOTE: Sunshine must be configured to use the default 47989 port to test with this tool.");
}
DisplayMessage(msgBuf, "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
return -1;
}
@@ -1186,7 +1478,7 @@ int main(int argc, char* argv[])
// Try to connect via ZeroTier address
fprintf(CONSOLE_OUT, "Testing GameStream connectivity using ZeroTier...\n");
fprintf(LOG_OUT, "Testing GameStream ports via ZeroTier\n");
if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf), false, true)) {
if (!TestAllPorts(&ss, nullptr, portMsgBuf, sizeof(portMsgBuf), false, true)) {
snprintf(msgBuf, sizeof(msgBuf),
"ZeroTier connectivity check failed. This is almost always caused by a firewall on your computer blocking the connection.\n\nTry temporarily disabling your antivirus and firewall.");
DisplayMessage(msgBuf, "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
@@ -1202,16 +1494,30 @@ 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");
// Try to connect via LAN address
fprintf(LOG_OUT, "Testing GameStream ports via local network\n");
if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf), false, true)) {
if (!TestAllPorts(&ss, nullptr, portMsgBuf, sizeof(portMsgBuf), false, true)) {
snprintf(msgBuf, sizeof(msgBuf),
"Local network GameStream connectivity check failed. This is almost always caused by a firewall on your computer blocking the connection.\n\nTry temporarily disabling your antivirus and firewall.");
DisplayMessage(msgBuf, "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
@@ -1220,7 +1526,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;
@@ -1231,15 +1537,13 @@ 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");
// We don't actually care about the outcome here but it's nice to have in logs
// to determine whether solving the double NAT will actually make Moonlight work.
TestAllPorts((PSOCKADDR_STORAGE)&locallyReportedWanAddr, portMsgBuf, sizeof(portMsgBuf), false, false);
TestAllPorts((PSOCKADDR_STORAGE)&locallyReportedWanAddr, &ss, portMsgBuf, sizeof(portMsgBuf), false, false);
fprintf(LOG_OUT, "Detected inconsistency between UPnP/NAT-PMP and STUN reported WAN addresses!\n");
}
@@ -1250,101 +1554,123 @@ 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, &v4, portMsgBuf, sizeof(portMsgBuf), true, true, &allPortsFailedOnV4)) {
freeaddrinfo(result);
snprintf(msgBuf, sizeof(msgBuf), "This PC is ready to host over the Internet!\n\n"
"Do not uninstall the Moonlight Internet Hosting Tool, unless you no longer want to stream over the Internet. It needs to remain installed on your PC to maintain the port forwarding entries on your router.\n\n"
"For the easiest setup, you can simply pair Moonlight to your gaming PC while they are both on the same network and Internet streaming will work automatically.\n\n"
"If your client is not connected to the same network as your gaming PC, 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, &v6,
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"
"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");
}
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;
}
+17
View File
@@ -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>
+43 -6
View File
@@ -29,26 +29,26 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
@@ -93,13 +93,22 @@
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>..\libs\include;..\libs\include\miniupnpc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<ControlFlowGuard>Guard</ControlFlowGuard>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>..\libs\x86\Debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<CETCompat>true</CETCompat>
</Link>
<Manifest>
<AdditionalManifestFiles>mist.exe.manifest</AdditionalManifestFiles>
</Manifest>
<PostBuildEvent>
<Command>copy /Y "$(SolutionDir)libs\x86\Debug\*" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
@@ -110,11 +119,20 @@
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<ControlFlowGuard>Guard</ControlFlowGuard>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<CETCompat>true</CETCompat>
</Link>
<Manifest>
<AdditionalManifestFiles>mist.exe.manifest</AdditionalManifestFiles>
</Manifest>
<PostBuildEvent>
<Command>copy /Y "$(SolutionDir)libs\x86\Debug\*" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
@@ -129,7 +147,8 @@
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>..\libs\include;..\libs\include\miniupnpc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<ControlFlowGuard>Guard</ControlFlowGuard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -137,7 +156,14 @@
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>..\libs\x86\Release;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<CETCompat>true</CETCompat>
</Link>
<Manifest>
<AdditionalManifestFiles>mist.exe.manifest</AdditionalManifestFiles>
</Manifest>
<PostBuildEvent>
<Command>copy /Y "$(SolutionDir)libs\x86\Release\*" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
@@ -150,13 +176,21 @@
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<ControlFlowGuard>Guard</ControlFlowGuard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<CETCompat>true</CETCompat>
</Link>
<Manifest>
<AdditionalManifestFiles>mist.exe.manifest</AdditionalManifestFiles>
</Manifest>
<PostBuildEvent>
<Command>copy /Y "$(SolutionDir)libs\x86\Release\*" "$(TargetDir)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="mist.cpp" />
@@ -169,6 +203,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>
+5
View File
@@ -35,4 +35,9 @@
<Filter>Source Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Manifest Include="mist.exe.manifest">
<Filter>Resource Files</Filter>
</Manifest>
</ItemGroup>
</Project>
+56 -40
View File
@@ -45,15 +45,20 @@ bool getExternalAddressPortIP4(unsigned short localPort, PSOCKADDR_IN wanAddr)
int i;
int bytesRead;
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;
@@ -68,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);
@@ -98,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);
@@ -156,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) {
@@ -169,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 = {};
@@ -183,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;
}
+2 -2
View File
@@ -1,7 +1,7 @@
#pragma once
#define VER_VERSION 4,4,0,0
#define VER_VERSION_STR "4.4.0.0"
#define VER_VERSION 5,6,1,0
#define VER_VERSION_STR "5.6.1.0"
#define VER_COMPANYNAME_STR "Moonlight Game Streaming Project"
#define VER_PRODUCTNAME_STR "Moonlight Internet Hosting Tool"