Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b496b90888 | ||
|
|
e7b5b07c4a | ||
|
|
732b430da8 | ||
|
|
5aea5aedab | ||
|
|
5325a57e20 | ||
|
|
92eeadadca | ||
|
|
e7e37b8dfb | ||
|
|
3906de1a3a | ||
|
|
c47014887a | ||
|
|
9dd169ba02 | ||
|
|
4ee5b9d28a | ||
|
|
176a1762d6 | ||
|
|
2ba42e6dd5 | ||
|
|
852ee8df8d | ||
|
|
1b3fef5408 | ||
|
|
1137825a4f | ||
|
|
c964830213 | ||
|
|
1b8f15e259 | ||
|
|
d379a4ee74 | ||
|
|
1c9ecbfb78 | ||
|
|
2899843900 | ||
|
|
81ca65d7d4 | ||
|
|
e62ed6ccc5 | ||
|
|
774878ed17 | ||
|
|
aeb288b62b | ||
|
|
d9320bed7d | ||
|
|
e8c39d1919 | ||
|
|
1efebca1bd | ||
|
|
222ed5a568 | ||
|
|
5da85a1fb2 |
+410
-42
@@ -1,4 +1,5 @@
|
|||||||
#define _CRT_SECURE_NO_WARNINGS
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
@@ -11,6 +12,8 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "..\version.h"
|
||||||
|
|
||||||
#pragma comment(lib, "miniupnpc.lib")
|
#pragma comment(lib, "miniupnpc.lib")
|
||||||
#pragma comment(lib, "libnatpmp.lib")
|
#pragma comment(lib, "libnatpmp.lib")
|
||||||
#pragma comment(lib, "ws2_32.lib")
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
@@ -24,8 +27,11 @@
|
|||||||
#define NATPMP_STATICLIB
|
#define NATPMP_STATICLIB
|
||||||
#include <natpmp.h>
|
#include <natpmp.h>
|
||||||
|
|
||||||
|
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 NL "\n"
|
||||||
#define STR(x) #x
|
|
||||||
|
|
||||||
#define SERVICE_NAME "MISS"
|
#define SERVICE_NAME "MISS"
|
||||||
#define UPNP_SERVICE_NAME "Moonlight"
|
#define UPNP_SERVICE_NAME "Moonlight"
|
||||||
@@ -47,6 +53,8 @@ static struct port_entry {
|
|||||||
{IPPROTO_UDP, 48010}
|
{IPPROTO_UDP, 48010}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const int k_WolPorts[] = { 7, 9 };
|
||||||
|
|
||||||
void UPnPCreatePinholeForPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const char* myAddr, int port)
|
void UPnPCreatePinholeForPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const char* myAddr, int port)
|
||||||
{
|
{
|
||||||
char uniqueId[8];
|
char uniqueId[8];
|
||||||
@@ -68,7 +76,7 @@ void UPnPCreatePinholeForPort(struct UPNPUrls* urls, struct IGDdatas* data, int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const char* myAddr, int port, bool enable)
|
bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const char* myAddr, int port, bool enable, bool indefinite)
|
||||||
{
|
{
|
||||||
char intClient[16];
|
char intClient[16];
|
||||||
char intPort[6];
|
char intPort[6];
|
||||||
@@ -133,10 +141,27 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// UPnP IGDs won't let unauthenticated clients delete other conflicting port mappings
|
|
||||||
// for security reasons, so we have to give up in this case.
|
|
||||||
printf("CONFLICT: %s %s" NL, intClient, desc);
|
printf("CONFLICT: %s %s" NL, intClient, desc);
|
||||||
return false;
|
|
||||||
|
// Some UPnP IGDs won't let unauthenticated clients delete other conflicting port mappings
|
||||||
|
// for security reasons, but we will give it a try anyway. If GameStream is not enabled,
|
||||||
|
// we will leave the conflicting entry alone to avoid disturbing another PC's port forwarding
|
||||||
|
// (especially if we're double NATed).
|
||||||
|
if (enable) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
else if (err == 606) {
|
||||||
|
printf("UNAUTHORIZED" NL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("ERROR %d" NL, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -149,11 +174,13 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create or update the expiration time of an existing mapping
|
// Create or update the expiration time of an existing mapping
|
||||||
|
snprintf(leaseDuration, sizeof(leaseDuration), "%d",
|
||||||
|
indefinite ? 0 : PORT_MAPPING_DURATION_SEC);
|
||||||
printf("Updating UPnP port mapping for %s %s -> %s...", protoStr, portStr, myAddr);
|
printf("Updating UPnP port mapping for %s %s -> %s...", protoStr, portStr, myAddr);
|
||||||
err = UPNP_AddPortMapping(
|
err = UPNP_AddPortMapping(
|
||||||
urls->controlURL, data->first.servicetype, portStr,
|
urls->controlURL, data->first.servicetype, portStr,
|
||||||
portStr, myAddr, myDesc, protoStr, nullptr, STR(PORT_MAPPING_DURATION_SEC));
|
portStr, myAddr, myDesc, protoStr, nullptr, leaseDuration);
|
||||||
if (err == 725) { // OnlyPermanentLeasesSupported
|
if (err == 725 && !indefinite) { // OnlyPermanentLeasesSupported
|
||||||
err = UPNP_AddPortMapping(
|
err = UPNP_AddPortMapping(
|
||||||
urls->controlURL, data->first.servicetype, portStr,
|
urls->controlURL, data->first.servicetype, portStr,
|
||||||
portStr, myAddr, myDesc, protoStr, nullptr, "0");
|
portStr, myAddr, myDesc, protoStr, nullptr, "0");
|
||||||
@@ -256,16 +283,69 @@ bool ResolveStableIP6Address(char* tmpAddr)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UPnPHandleDeviceList(struct UPNPDev* list, bool ipv6, bool enable)
|
bool GetIP4OnLinkPrefixLength(char* lanAddressString, int* prefixLength)
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
IP_ADAPTER_ADDRESSES addresses;
|
||||||
|
char buffer[8192];
|
||||||
|
};
|
||||||
|
ULONG error;
|
||||||
|
ULONG length;
|
||||||
|
PIP_ADAPTER_ADDRESSES currentAdapter;
|
||||||
|
PIP_ADAPTER_UNICAST_ADDRESS currentAddress;
|
||||||
|
in_addr targetAddress;
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (error != ERROR_SUCCESS) {
|
||||||
|
printf("GetAdaptersAddresses() failed: %d" NL, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentAdapter = &addresses;
|
||||||
|
currentAddress = nullptr;
|
||||||
|
while (currentAdapter != nullptr) {
|
||||||
|
currentAddress = currentAdapter->FirstUnicastAddress;
|
||||||
|
while (currentAddress != nullptr) {
|
||||||
|
assert(currentAddress->Address.lpSockaddr->sa_family == AF_INET);
|
||||||
|
|
||||||
|
PSOCKADDR_IN currentAddrV4 = (PSOCKADDR_IN)currentAddress->Address.lpSockaddr;
|
||||||
|
|
||||||
|
if (RtlEqualMemory(¤tAddrV4->sin_addr, &targetAddress, sizeof(targetAddress))) {
|
||||||
|
*prefixLength = currentAddress->OnLinkPrefixLength;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentAddress = currentAddress->Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentAdapter = currentAdapter->Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("No adapter found with IPv4 address: %s" NL, lanAddressString);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UPnPHandleDeviceList(struct UPNPDev* list, bool ipv6, bool enable, char* lanAddrOverride, char* wanAddr)
|
||||||
{
|
{
|
||||||
struct UPNPUrls urls;
|
struct UPNPUrls urls;
|
||||||
struct IGDdatas data;
|
struct IGDdatas data;
|
||||||
char myAddr[128];
|
char localAddress[128];
|
||||||
char wanAddr[128];
|
char* portMappingInternalAddress;
|
||||||
int pinholeAllowed = false;
|
int pinholeAllowed = false;
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
int ret = UPNP_GetValidIGD(list, &urls, &data, myAddr, sizeof(myAddr));
|
int ret = UPNP_GetValidIGD(list, &urls, &data, localAddress, sizeof(localAddress));
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
printf("No UPnP device found!" NL);
|
printf("No UPnP device found!" NL);
|
||||||
return false;
|
return false;
|
||||||
@@ -292,19 +372,25 @@ bool UPnPHandleDeviceList(struct UPNPDev* list, bool ipv6, bool enable)
|
|||||||
if (ipv6) {
|
if (ipv6) {
|
||||||
// Convert what is likely a IPv6 temporary address into
|
// Convert what is likely a IPv6 temporary address into
|
||||||
// the stable IPv6 address for the same interface.
|
// the stable IPv6 address for the same interface.
|
||||||
if (ResolveStableIP6Address(myAddr)) {
|
if (ResolveStableIP6Address(localAddress)) {
|
||||||
printf("Stable global IPv6 address is: %s" NL, myAddr);
|
printf("Stable global IPv6 address is: %s" NL, localAddress);
|
||||||
|
|
||||||
int firewallEnabled;
|
// Don't try IPv6FC without a control URL
|
||||||
ret = UPNP_GetFirewallStatus(urls.controlURL_6FC, data.IPv6FC.servicetype, &firewallEnabled, &pinholeAllowed);
|
if (data.IPv6FC.controlurl[0] != 0) {
|
||||||
if (ret == UPNPCOMMAND_SUCCESS) {
|
int firewallEnabled;
|
||||||
printf("UPnP IPv6 firewall control available. Firewall is %s, pinhole is %s" NL,
|
ret = UPNP_GetFirewallStatus(urls.controlURL_6FC, data.IPv6FC.servicetype, &firewallEnabled, &pinholeAllowed);
|
||||||
firewallEnabled ? "enabled" : "disabled",
|
if (ret == UPNPCOMMAND_SUCCESS) {
|
||||||
pinholeAllowed ? "allowed" : "disallowed");
|
printf("UPnP IPv6 firewall control available. Firewall is %s, pinhole is %s" NL,
|
||||||
|
firewallEnabled ? "enabled" : "disabled",
|
||||||
|
pinholeAllowed ? "allowed" : "disallowed");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("UPnP IPv6 firewall control is unavailable with error %d (%s)" NL, ret, strupnperror(ret));
|
||||||
|
pinholeAllowed = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
printf("UPnP IPv6 firewall control is unavailable with error %d (%s)" NL, ret, strupnperror(ret));
|
printf("IPv6 firewall control not supported by UPnP IGD!" NL);
|
||||||
pinholeAllowed = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,16 +399,59 @@ bool UPnPHandleDeviceList(struct UPNPDev* list, bool ipv6, bool enable)
|
|||||||
if (ret == UPNPCOMMAND_SUCCESS) {
|
if (ret == UPNPCOMMAND_SUCCESS) {
|
||||||
printf("UPnP IGD WAN address is: %s" NL, wanAddr);
|
printf("UPnP IGD WAN address is: %s" NL, wanAddr);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Empty string
|
||||||
|
*wanAddr = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We may be mapping on behalf of another device
|
||||||
|
if (lanAddrOverride != nullptr) {
|
||||||
|
portMappingInternalAddress = lanAddrOverride;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
portMappingInternalAddress = localAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
|
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
|
||||||
if (!ipv6) {
|
if (!ipv6) {
|
||||||
if (!UPnPMapPort(&urls, &data, k_Ports[i].proto, myAddr, k_Ports[i].port, enable)) {
|
if (!UPnPMapPort(&urls, &data, k_Ports[i].proto, portMappingInternalAddress, k_Ports[i].port, enable, false)) {
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pinholeAllowed) {
|
if (pinholeAllowed) {
|
||||||
UPnPCreatePinholeForPort(&urls, &data, k_Ports[i].proto, myAddr, k_Ports[i].port);
|
UPnPCreatePinholeForPort(&urls, &data, k_Ports[i].proto, portMappingInternalAddress, k_Ports[i].port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a best-effort for IPv4 Wake-on-LAN broadcast mappings
|
||||||
|
if (!ipv6) {
|
||||||
|
for (int i = 0; i < ARRAYSIZE(k_WolPorts); i++) {
|
||||||
|
if (lanAddrOverride == nullptr) {
|
||||||
|
// Map the port to the broadcast address (may not work on all routers). This
|
||||||
|
// ensures delivery even after the ARP entry for this PC times out on the router.
|
||||||
|
int onLinkPrefixLen;
|
||||||
|
if (GetIP4OnLinkPrefixLength(localAddress, &onLinkPrefixLen)) {
|
||||||
|
int netmask = 0;
|
||||||
|
for (int j = 0; j < onLinkPrefixLen; j++) {
|
||||||
|
netmask |= (1 << j);
|
||||||
|
}
|
||||||
|
|
||||||
|
in_addr broadcastAddr;
|
||||||
|
broadcastAddr.S_un.S_addr = inet_addr(localAddress);
|
||||||
|
broadcastAddr.S_un.S_addr |= ~netmask;
|
||||||
|
|
||||||
|
char broadcastAddrStr[128];
|
||||||
|
inet_ntop(AF_INET, &broadcastAddr, broadcastAddrStr, sizeof(broadcastAddrStr));
|
||||||
|
|
||||||
|
UPnPMapPort(&urls, &data, IPPROTO_UDP, broadcastAddrStr, k_WolPorts[i], enable, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,7 +459,7 @@ bool UPnPHandleDeviceList(struct UPNPDev* list, bool ipv6, bool enable)
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable)
|
bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable, bool indefinite)
|
||||||
{
|
{
|
||||||
int natPmpProto;
|
int natPmpProto;
|
||||||
|
|
||||||
@@ -347,8 +476,20 @@ bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int lifetime;
|
||||||
|
|
||||||
|
if (!enable) {
|
||||||
|
lifetime = 0;
|
||||||
|
}
|
||||||
|
else if (indefinite) {
|
||||||
|
lifetime = 604800; // 1 week
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lifetime = 3600;
|
||||||
|
}
|
||||||
|
|
||||||
printf("Updating NAT-PMP port mapping for %s %d...", proto == IPPROTO_TCP ? "TCP" : "UDP", port);
|
printf("Updating NAT-PMP port mapping for %s %d...", proto == IPPROTO_TCP ? "TCP" : "UDP", port);
|
||||||
int err = sendnewportmappingrequest(natpmp, natPmpProto, port, port, enable ? PORT_MAPPING_DURATION_SEC : 0);
|
int err = sendnewportmappingrequest(natpmp, natPmpProto, port, enable ? port : 0, lifetime);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
printf("ERROR %d" NL, err);
|
printf("ERROR %d" NL, err);
|
||||||
return false;
|
return false;
|
||||||
@@ -385,10 +526,53 @@ bool NATPMPMapPort(natpmp_t* natpmp, int proto, int port, bool enable)
|
|||||||
}
|
}
|
||||||
else if (response.pnu.newportmapping.mappedpublicport != port) {
|
else if (response.pnu.newportmapping.mappedpublicport != port) {
|
||||||
printf("CONFLICT" NL);
|
printf("CONFLICT" NL);
|
||||||
|
|
||||||
|
// Some buggy routers (Untangle) will change the *internal* port when
|
||||||
|
// adjusting a port mapping request that collides. This is why we also
|
||||||
|
// pass privateport back from the response and not from the port we originally
|
||||||
|
// asked for. Warn in this case.
|
||||||
|
if (response.pnu.newportmapping.privateport != port) {
|
||||||
|
printf("Buggy router changed the internal port when handling NAT-PMP conflict! (%d -> %d)" NL,
|
||||||
|
port, response.pnu.newportmapping.privateport);
|
||||||
|
}
|
||||||
|
|
||||||
// It couldn't assign us the external port we requested and gave us an alternate external port.
|
// 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.
|
// We can't use this alternate mapping, so immediately release it.
|
||||||
sendnewportmappingrequest(natpmp, natPmpProto, port, response.pnu.newportmapping.mappedpublicport, 0);
|
printf("Deleting unwanted NAT-PMP mapping for %s %d...", proto == IPPROTO_TCP ? "TCP" : "UDP", response.pnu.newportmapping.mappedpublicport);
|
||||||
return false;
|
err = sendnewportmappingrequest(natpmp, natPmpProto, response.pnu.newportmapping.privateport, 0, 0);
|
||||||
|
if (err < 0) {
|
||||||
|
printf("ERROR %d" NL, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
do {
|
||||||
|
fd_set fds;
|
||||||
|
struct timeval timeout;
|
||||||
|
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(natpmp->s, &fds);
|
||||||
|
|
||||||
|
err = getnatpmprequesttimeout(natpmp, &timeout);
|
||||||
|
if (err != 0) {
|
||||||
|
assert(err == 0);
|
||||||
|
printf("WAIT FAILED: %d" NL, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
select(0, &fds, nullptr, nullptr, &timeout);
|
||||||
|
|
||||||
|
err = readnatpmpresponseorretry(natpmp, &response);
|
||||||
|
} while (err == NATPMP_TRYAGAIN);
|
||||||
|
|
||||||
|
if (err == 0) {
|
||||||
|
printf("OK" NL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("FAILED %d" NL, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
printf("OK (%d seconds remaining)" NL, response.pnu.newportmapping.lifetime);
|
printf("OK (%d seconds remaining)" NL, response.pnu.newportmapping.lifetime);
|
||||||
@@ -426,14 +610,19 @@ bool IsGameStreamEnabled()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePortMappings(bool enable)
|
void UpdatePortMappingsForTarget(bool enable, char* targetAddressIP4, char* internalAddressIP4, char* upstreamAddressIP4)
|
||||||
{
|
{
|
||||||
natpmp_t natpmp;
|
natpmp_t natpmp;
|
||||||
bool tryNatPmp = true;
|
bool tryNatPmp = true;
|
||||||
|
bool tryPcp = true;
|
||||||
|
char upstreamAddrNatPmp[128] = {};
|
||||||
|
char upstreamAddrUPnP[128] = {};
|
||||||
|
|
||||||
printf("Starting port mapping update..." NL);
|
printf("Starting port mapping update on %s to %s..." NL,
|
||||||
|
targetAddressIP4 ? targetAddressIP4 : "default gateway",
|
||||||
|
internalAddressIP4 ? internalAddressIP4 : "local machine");
|
||||||
|
|
||||||
int natPmpErr = initnatpmp(&natpmp, 0, 0);
|
int natPmpErr = initnatpmp(&natpmp, targetAddressIP4 ? 1 : 0, targetAddressIP4 ? inet_addr(targetAddressIP4) : 0);
|
||||||
if (natPmpErr != 0) {
|
if (natPmpErr != 0) {
|
||||||
printf("initnatpmp() failed: %d" NL, natPmpErr);
|
printf("initnatpmp() failed: %d" NL, natPmpErr);
|
||||||
}
|
}
|
||||||
@@ -449,18 +638,27 @@ void UpdatePortMappings(bool enable)
|
|||||||
|
|
||||||
{
|
{
|
||||||
int upnpErr;
|
int upnpErr;
|
||||||
struct UPNPDev* ipv4Devs = upnpDiscoverAll(UPNP_DISCOVERY_DELAY_MS, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 0, 2, &upnpErr);
|
struct UPNPDev* ipv4Devs;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We have a specified target, so do discovery against that directly (may be outside our subnet in case of double-NAT)
|
||||||
|
struct in_addr addr;
|
||||||
|
addr.S_un.S_addr = inet_addr(targetAddressIP4);
|
||||||
|
ipv4Devs = getUPnPDevicesByAddress(addr);
|
||||||
|
}
|
||||||
|
|
||||||
printf("UPnP IPv4 IGD discovery completed with error code: %d" NL, upnpErr);
|
// Use the delay of discovery to also allow the NAT-PMP endpoint time to respond
|
||||||
|
|
||||||
// Use the delay of upnpDiscoverAll() to also allow the NAT-PMP endpoint time to respond
|
|
||||||
if (natPmpErr >= 0) {
|
if (natPmpErr >= 0) {
|
||||||
natpmpresp_t response;
|
natpmpresp_t response;
|
||||||
natPmpErr = readnatpmpresponseorretry(&natpmp, &response);
|
natPmpErr = readnatpmpresponseorretry(&natpmp, &response);
|
||||||
if (natPmpErr == 0) {
|
if (natPmpErr == 0) {
|
||||||
char addrStr[64];
|
inet_ntop(AF_INET, &response.pnu.publicaddress.addr, upstreamAddrNatPmp, sizeof(upstreamAddrNatPmp));
|
||||||
inet_ntop(AF_INET, &response.pnu.publicaddress.addr, addrStr, sizeof(addrStr));
|
printf("NAT-PMP upstream address is: %s" NL, upstreamAddrNatPmp);
|
||||||
printf("NAT-PMP WAN address is: %s" NL, addrStr);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
printf("NAT-PMP public address request failed: %d" NL, natPmpErr);
|
printf("NAT-PMP public address request failed: %d" NL, natPmpErr);
|
||||||
@@ -469,12 +667,13 @@ void UpdatePortMappings(bool enable)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't try NAT-PMP if UPnP succeeds
|
// Don't try NAT-PMP if UPnP succeeds
|
||||||
if (UPnPHandleDeviceList(ipv4Devs, false, enable)) {
|
if (UPnPHandleDeviceList(ipv4Devs, false, enable, internalAddressIP4, upstreamAddrUPnP)) {
|
||||||
printf("UPnP IPv4 port mapping successful" NL);
|
printf("UPnP IPv4 port mapping successful" NL);
|
||||||
if (enable) {
|
if (enable) {
|
||||||
// We still want to try NAT-PMP if we're removing
|
// We still want to try NAT-PMP if we're removing
|
||||||
// rules to ensure any NAT-PMP rules get cleaned up
|
// rules to ensure any NAT-PMP rules get cleaned up
|
||||||
tryNatPmp = false;
|
tryNatPmp = false;
|
||||||
|
tryPcp = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,14 +682,17 @@ void UpdatePortMappings(bool enable)
|
|||||||
|
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
{
|
// Only run IPv6 UPnP discovery on the first hop
|
||||||
|
if (targetAddressIP4 == nullptr) {
|
||||||
int upnpErr;
|
int upnpErr;
|
||||||
struct UPNPDev* ipv6Devs = upnpDiscoverAll(UPNP_DISCOVERY_DELAY_MS, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 1, 2, &upnpErr);
|
struct UPNPDev* ipv6Devs = upnpDiscoverAll(UPNP_DISCOVERY_DELAY_MS, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 1, 2, &upnpErr);
|
||||||
|
char ipv6WanAddr[128] = {};
|
||||||
|
|
||||||
printf("UPnP IPv6 IGD discovery completed with error code: %d" NL, upnpErr);
|
printf("UPnP IPv6 IGD discovery completed with error code: %d" NL, upnpErr);
|
||||||
|
|
||||||
// Ignore whether IPv6 succeeded when decided to use NAT-PMP
|
// Ignore whether IPv6 succeeded when decided to use NAT-PMP
|
||||||
UPnPHandleDeviceList(ipv6Devs, true, enable);
|
|
||||||
|
UPnPHandleDeviceList(ipv6Devs, true, enable, nullptr, ipv6WanAddr);
|
||||||
|
|
||||||
freeUPNPDevlist(ipv6Devs);
|
freeUPNPDevlist(ipv6Devs);
|
||||||
}
|
}
|
||||||
@@ -498,21 +700,184 @@ void UpdatePortMappings(bool enable)
|
|||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
if (natPmpErr == 0) {
|
if (natPmpErr == 0) {
|
||||||
|
// NAT-PMP has no description field or other token that we can use to determine
|
||||||
|
// 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);
|
||||||
|
tryNatPmp = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't try with NAT-PMP if the UPnP attempt for the same gateway failed due to being
|
||||||
|
// disconnected or some other error. This will avoid overwriting UPnP rules on a disconnected IGD
|
||||||
|
// with duplicate NAT-PMP rules. We want to allow deletion of NAT-PMP rules in any case though.
|
||||||
|
if (enable && !strcmp(upstreamAddrNatPmp, upstreamAddrUPnP)) {
|
||||||
|
printf("Not attempting to use NAT-PMP/PCP to talk to the same UPnP gateway\n");
|
||||||
|
tryNatPmp = false;
|
||||||
|
|
||||||
|
// We have both UPnP and NAT-PMP on the same upstream gateway, so let's
|
||||||
|
// assume PCP is on the same box too.
|
||||||
|
tryPcp = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (tryNatPmp) {
|
if (tryNatPmp) {
|
||||||
bool success = true;
|
bool success = true;
|
||||||
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
|
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
|
||||||
if (!NATPMPMapPort(&natpmp, k_Ports[i].proto, k_Ports[i].port, enable)) {
|
if (!NATPMPMapPort(&natpmp, k_Ports[i].proto, k_Ports[i].port, enable, false)) {
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We can only map ports for the non-default gateway case because
|
||||||
|
// it will use our LAN address as the internal client address, which
|
||||||
|
// doesn't work (needs to be broadcast) for the last hop.
|
||||||
|
if (targetAddressIP4 != nullptr) {
|
||||||
|
// Best effort, don't care if we fail for WOL
|
||||||
|
for (int i = 0; i < ARRAYSIZE(k_WolPorts); i++) {
|
||||||
|
// Indefinite mapping since we may not be awake to refresh it
|
||||||
|
NATPMPMapPort(&natpmp, IPPROTO_UDP, k_WolPorts[i], enable, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
printf("NAT-PMP IPv4 port mapping successful" NL);
|
printf("NAT-PMP IPv4 port mapping successful" NL);
|
||||||
|
|
||||||
|
// Always try all possibilities when disabling to ensure
|
||||||
|
// we completely clean up
|
||||||
|
if (enable) {
|
||||||
|
tryPcp = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closenatpmp(&natpmp);
|
closenatpmp(&natpmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try PCP for IPv4 if UPnP and NAT-PMP have both failed for the non-default gateway router.
|
||||||
|
// This may be the case for CGN that only supports PCP.
|
||||||
|
if (tryPcp && targetAddressIP4 != nullptr && internalAddressIP4 != nullptr) {
|
||||||
|
SOCKADDR_IN targetAddr = {};
|
||||||
|
SOCKADDR_IN internalAddr = {};
|
||||||
|
targetAddr.sin_family = AF_INET;
|
||||||
|
targetAddr.sin_addr.S_un.S_addr = inet_addr(targetAddressIP4);
|
||||||
|
internalAddr.sin_family = AF_INET;
|
||||||
|
internalAddr.sin_addr.S_un.S_addr = inet_addr(internalAddressIP4);
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
for (int i = 0; i < ARRAYSIZE(k_Ports); i++) {
|
||||||
|
if (!PCPMapPort((PSOCKADDR_STORAGE)&internalAddr, sizeof(internalAddr),
|
||||||
|
(PSOCKADDR_STORAGE)&targetAddr, sizeof(targetAddr),
|
||||||
|
k_Ports[i].proto, k_Ports[i].port, enable, false)) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can only map ports for the non-default gateway case because
|
||||||
|
// it will use our internal address as the internal client address, which
|
||||||
|
// doesn't work (needs to be broadcast) for the last hop.
|
||||||
|
if (internalAddressIP4 != nullptr) {
|
||||||
|
// Best effort, don't care if we fail for WOL
|
||||||
|
for (int i = 0; i < ARRAYSIZE(k_WolPorts); i++) {
|
||||||
|
// Indefinite mapping since we may not be awake to refresh it
|
||||||
|
PCPMapPort((PSOCKADDR_STORAGE)&internalAddr, sizeof(internalAddr),
|
||||||
|
(PSOCKADDR_STORAGE)&targetAddr, sizeof(targetAddr),
|
||||||
|
IPPROTO_UDP, k_WolPorts[i], enable, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
printf("PCP IPv4 port mapping successful" NL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
strcpy(upstreamAddressIP4, upstreamAddrNatPmp);
|
||||||
|
}
|
||||||
|
else if (upstreamAddrUPnP[0] != 0 && inet_addr(upstreamAddrUPnP) != 0) {
|
||||||
|
printf("Using UPnP upstream IPv4 address: %s" NL, upstreamAddrUPnP);
|
||||||
|
strcpy(upstreamAddressIP4, upstreamAddrUPnP);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("No valid upstream IPv4 address found!" NL);
|
||||||
|
upstreamAddressIP4[0] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsLikelyNAT(unsigned long netByteOrderAddr)
|
||||||
|
{
|
||||||
|
DWORD addr = htonl(netByteOrderAddr);
|
||||||
|
|
||||||
|
// 10.0.0.0/8
|
||||||
|
if ((addr & 0xFF000000) == 0x0A000000) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 172.16.0.0/12
|
||||||
|
else if ((addr & 0xFFF00000) == 0xAC100000) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 192.168.0.0/16
|
||||||
|
else if ((addr & 0xFFFF0000) == 0xC0A80000) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 100.64.0.0/10 - RFC6598 official CGN address
|
||||||
|
else if ((addr & 0xFFC00000) == 0x64400000) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdatePortMappings(bool enable)
|
||||||
|
{
|
||||||
|
IN_ADDR hops[4];
|
||||||
|
int hopCount = ARRAYSIZE(hops);
|
||||||
|
char upstreamAddrStr[128];
|
||||||
|
unsigned long upstreamAddr;
|
||||||
|
|
||||||
|
printf("Finding upstream IPv4 hops via traceroute..." NL);
|
||||||
|
if (!getHopsIP4(hops, &hopCount)) {
|
||||||
|
hopCount = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("Found %d hops" NL, hopCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHopsIP4() already skips the default gateway, so 0
|
||||||
|
// is actually the first hop after the default gateway
|
||||||
|
int nextHopIndex = 0;
|
||||||
|
|
||||||
|
// Start by probing for the default gateway
|
||||||
|
UpdatePortMappingsForTarget(enable, nullptr, nullptr, upstreamAddrStr);
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (nextHopIndex >= hopCount) {
|
||||||
|
printf("Traceroute didn't reach this hop! Aborting!" NL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char targetAddress[128];
|
||||||
|
inet_ntop(AF_INET, &hops[nextHopIndex], targetAddress, sizeof(targetAddress));
|
||||||
|
|
||||||
|
// It's a NAT, so let's direct our UPnP/NAT-PMP messages to it.
|
||||||
|
// The internal IP address for the new mapping will be the upstream address of the last one.
|
||||||
|
// The target IP address to which to send the UPnP/NAT-PMP is the next hop of the traceroute.
|
||||||
|
UpdatePortMappingsForTarget(enable, targetAddress, upstreamAddrStr, upstreamAddrStr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If we reach a proper public IP address, we're done
|
||||||
|
printf("Reached the Internet at hop %d" NL, nextHopIndex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next hop
|
||||||
|
nextHopIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,6 +902,9 @@ void ResetLogFile()
|
|||||||
|
|
||||||
// Redirect stdout to this new file
|
// Redirect stdout to this new file
|
||||||
freopen(currentLogFilePath, "w", stdout);
|
freopen(currentLogFilePath, "w", stdout);
|
||||||
|
|
||||||
|
// Print a log header
|
||||||
|
printf("Moonlight Internet Streaming Service v" VER_VERSION_STR NL);
|
||||||
}
|
}
|
||||||
|
|
||||||
DWORD WINAPI GameStreamStateChangeThread(PVOID Context)
|
DWORD WINAPI GameStreamStateChangeThread(PVOID Context)
|
||||||
@@ -632,7 +1000,7 @@ HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpConte
|
|||||||
ServiceStatus.dwControlsAccepted = 0;
|
ServiceStatus.dwControlsAccepted = 0;
|
||||||
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
|
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
|
||||||
|
|
||||||
printf("Removing UPnP/NAT-PMP rules after service stop request\n");
|
printf("Removing UPnP/NAT-PMP/PCP rules after service stop request\n");
|
||||||
UpdatePortMappings(false);
|
UpdatePortMappings(false);
|
||||||
|
|
||||||
printf("The service is stopping\n");
|
printf("The service is stopping\n");
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
#include "../version.h"
|
#include "../version.h"
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION VER_FILEVERSION
|
FILEVERSION VER_VERSION
|
||||||
BEGIN
|
BEGIN
|
||||||
BLOCK "StringFileInfo"
|
BLOCK "StringFileInfo"
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -10,7 +10,7 @@ BEGIN
|
|||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", VER_COMPANYNAME_STR
|
VALUE "CompanyName", VER_COMPANYNAME_STR
|
||||||
VALUE "FileDescription", "Moonlight Internet Streaming Service"
|
VALUE "FileDescription", "Moonlight Internet Streaming Service"
|
||||||
VALUE "FileVersion", VER_FILEVERSION_STR
|
VALUE "ProductName", VER_PRODUCTNAME_STR
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,8 @@
|
|||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="miss.cpp" />
|
<ClCompile Include="miss.cpp" />
|
||||||
|
<ClCompile Include="pcp.cpp" />
|
||||||
|
<ClCompile Include="tracer.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="miss.rc" />
|
<ResourceCompile Include="miss.rc" />
|
||||||
|
|||||||
@@ -18,6 +18,12 @@
|
|||||||
<ClCompile Include="miss.cpp">
|
<ClCompile Include="miss.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="tracer.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="pcp.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="miss.rc">
|
<ResourceCompile Include="miss.rc">
|
||||||
|
|||||||
+296
@@ -0,0 +1,296 @@
|
|||||||
|
#define _CRT_RAND_S
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <WinSock2.h>
|
||||||
|
#include <WS2tcpip.h>
|
||||||
|
|
||||||
|
#pragma comment(lib, "Shlwapi.lib")
|
||||||
|
#include <shlwapi.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define RECV_TIMEOUT_SEC 3
|
||||||
|
|
||||||
|
#define PCP_VERSION 2
|
||||||
|
#define OPCODE_MAP_REQUEST 0x01
|
||||||
|
#define OPCODE_MAP_RESPONSE 0x81
|
||||||
|
|
||||||
|
#define CODE_PREFER_FAILURE 2
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
typedef struct _PCP_REQUEST_HEADER {
|
||||||
|
unsigned char version;
|
||||||
|
unsigned char opcode;
|
||||||
|
unsigned short reserved;
|
||||||
|
unsigned int lifetime;
|
||||||
|
unsigned char localAddress[16];
|
||||||
|
} PCP_REQUEST_HEADER, *PPCP_REQUEST_HEADER;
|
||||||
|
|
||||||
|
typedef struct _PCP_RESPONSE_HEADER {
|
||||||
|
unsigned char version;
|
||||||
|
unsigned char opcode;
|
||||||
|
unsigned char reserved;
|
||||||
|
unsigned char result;
|
||||||
|
unsigned int lifetime;
|
||||||
|
unsigned int epoch;
|
||||||
|
unsigned char reserved2[12];
|
||||||
|
} PCP_RESPONSE_HEADER, *PPCP_RESPONSE_HEADER;
|
||||||
|
|
||||||
|
typedef struct _PCP_OPTION_HEADER {
|
||||||
|
unsigned char code;
|
||||||
|
unsigned char reserved;
|
||||||
|
unsigned short length;
|
||||||
|
} PCP_OPTION_HEADER, *PPCP_OPTION_HEADER;
|
||||||
|
|
||||||
|
typedef struct _PCP_MAP_REQUEST {
|
||||||
|
PCP_REQUEST_HEADER hdr;
|
||||||
|
|
||||||
|
unsigned char mappingNonce[12];
|
||||||
|
unsigned char protocol;
|
||||||
|
unsigned char reserved[3];
|
||||||
|
unsigned short internalPort;
|
||||||
|
unsigned short externalPort;
|
||||||
|
unsigned char externalAddress[16];
|
||||||
|
|
||||||
|
// We send PREFER_FAILURE too for MAP requests
|
||||||
|
PCP_OPTION_HEADER preferFailureOption;
|
||||||
|
} PCP_MAP_REQUEST, *PPCP_MAP_REQUEST;
|
||||||
|
|
||||||
|
typedef struct _PCP_MAP_RESPONSE {
|
||||||
|
PCP_RESPONSE_HEADER hdr;
|
||||||
|
|
||||||
|
unsigned char mappingNonce[12];
|
||||||
|
unsigned char protocol;
|
||||||
|
unsigned char reserved[3];
|
||||||
|
unsigned short internalPort;
|
||||||
|
unsigned short externalPort;
|
||||||
|
unsigned char externalAddress[16];
|
||||||
|
} PCP_MAP_RESPONSE, *PPCP_MAP_RESPONSE;
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
static void populateMappingNonce(PPCP_MAP_REQUEST request, PSOCKADDR_STORAGE pcpAddr, int pcpAddrLen)
|
||||||
|
{
|
||||||
|
struct {
|
||||||
|
unsigned short port;
|
||||||
|
unsigned char localAddress[16];
|
||||||
|
SOCKADDR_STORAGE targetAddress;
|
||||||
|
} dataToHash;
|
||||||
|
|
||||||
|
assert(request->internalPort != 0);
|
||||||
|
|
||||||
|
dataToHash.port = request->internalPort;
|
||||||
|
memcpy(dataToHash.localAddress, request->hdr.localAddress, sizeof(dataToHash.localAddress));
|
||||||
|
memcpy(&dataToHash.targetAddress, pcpAddr, pcpAddrLen);
|
||||||
|
|
||||||
|
HashData((BYTE*)&dataToHash, 18 + pcpAddrLen, request->mappingNonce, sizeof(request->mappingNonce));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void populateAddressFromSockAddr(PSOCKADDR_STORAGE sockAddr, unsigned char* address)
|
||||||
|
{
|
||||||
|
if (sockAddr->ss_family == AF_INET) {
|
||||||
|
PSOCKADDR_IN sin = (PSOCKADDR_IN)sockAddr;
|
||||||
|
memset(&address[0], 0, 10);
|
||||||
|
memset(&address[10], 0xFF, 2);
|
||||||
|
memcpy(&address[12], &sin->sin_addr, 4);
|
||||||
|
}
|
||||||
|
else if (sockAddr->ss_family == AF_INET6) {
|
||||||
|
PSOCKADDR_IN6 sin6 = (PSOCKADDR_IN6)sockAddr;
|
||||||
|
memcpy(address, &sin6->sin6_addr, 16);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PCPMapPort(PSOCKADDR_STORAGE localAddr, int localAddrLen, PSOCKADDR_STORAGE pcpAddr, int pcpAddrLen, int proto, int port, bool enable, bool indefinite)
|
||||||
|
{
|
||||||
|
SOCKET sock;
|
||||||
|
PCP_MAP_REQUEST reqMsg;
|
||||||
|
int reqMsgLen;
|
||||||
|
int i;
|
||||||
|
int bytesRead;
|
||||||
|
union {
|
||||||
|
PCP_MAP_RESPONSE hdr;
|
||||||
|
char buf[1024];
|
||||||
|
} resp;
|
||||||
|
int lifetime;
|
||||||
|
|
||||||
|
if (!enable) {
|
||||||
|
lifetime = 0;
|
||||||
|
}
|
||||||
|
else if (indefinite) {
|
||||||
|
lifetime = 604800; // 1 week
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lifetime = 3600;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(localAddr->ss_family == pcpAddr->ss_family);
|
||||||
|
|
||||||
|
printf("Updating PCP port mapping for %s %d...", proto == IPPROTO_TCP ? "TCP" : "UDP", port);
|
||||||
|
|
||||||
|
sock = socket(localAddr->ss_family, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
printf("socket() failed: %d\n", WSAGetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localAddr->ss_family == AF_INET6) {
|
||||||
|
// Make sure we're sourcing from the correct IPv6 address to ensure the port
|
||||||
|
// is opened correctly and that the PCP server doesn't refuse our mapping.
|
||||||
|
((PSOCKADDR_IN6)localAddr)->sin6_port = 0;
|
||||||
|
if (bind(sock, (struct sockaddr*)localAddr, localAddrLen) == SOCKET_ERROR) {
|
||||||
|
printf("bind() failed: %d\n", WSAGetLastError());
|
||||||
|
closesocket(sock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
((PSOCKADDR_IN)pcpAddr)->sin_port = htons(5351);
|
||||||
|
if (connect(sock, (struct sockaddr*)pcpAddr, pcpAddrLen) == SOCKET_ERROR) {
|
||||||
|
printf("connect() failed: %d\n", WSAGetLastError());
|
||||||
|
closesocket(sock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reqMsg = {};
|
||||||
|
reqMsg.hdr.version = PCP_VERSION;
|
||||||
|
reqMsg.hdr.opcode = OPCODE_MAP_REQUEST;
|
||||||
|
reqMsg.hdr.lifetime = htonl(lifetime);
|
||||||
|
populateAddressFromSockAddr(localAddr, reqMsg.hdr.localAddress);
|
||||||
|
|
||||||
|
reqMsg.protocol = proto;
|
||||||
|
reqMsg.internalPort = htons(port);
|
||||||
|
reqMsg.externalPort = htons(port);
|
||||||
|
|
||||||
|
SOCKADDR_STORAGE noneAddr = {};
|
||||||
|
noneAddr.ss_family = localAddr->ss_family;
|
||||||
|
populateAddressFromSockAddr(&noneAddr, reqMsg.externalAddress);
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
// We don't want an alternate allocation if this fails
|
||||||
|
reqMsg.preferFailureOption.code = CODE_PREFER_FAILURE;
|
||||||
|
reqMsg.preferFailureOption.length = 0;
|
||||||
|
reqMsgLen = sizeof(reqMsg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We don't append PREFER_FAILURE for an unmap request
|
||||||
|
reqMsgLen = sizeof(reqMsg) - sizeof(reqMsg.preferFailureOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This must be done after the rest of the message is populated
|
||||||
|
populateMappingNonce(&reqMsg, pcpAddr, pcpAddrLen);
|
||||||
|
|
||||||
|
bytesRead = 0;
|
||||||
|
for (i = 0; i < RECV_TIMEOUT_SEC; i++) {
|
||||||
|
// Retransmit the request every second until the timeout elapses
|
||||||
|
if (send(sock, (char *)&reqMsg, reqMsgLen, 0) == SOCKET_ERROR) {
|
||||||
|
printf("send() failed: %d\n", WSAGetLastError());
|
||||||
|
closesocket(sock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd_set fds;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(sock, &fds);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
else if (selectRes == SOCKET_ERROR) {
|
||||||
|
printf("select() failed: %d\n", WSAGetLastError());
|
||||||
|
closesocket(sock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error handling is below
|
||||||
|
bytesRead = recv(sock, resp.buf, sizeof(resp.buf), 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesRead == 0) {
|
||||||
|
printf("NO RESPONSE\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
else if (bytesRead == SOCKET_ERROR) {
|
||||||
|
printf("Failed to read PCP response: %d\n", WSAGetLastError());
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
else if (bytesRead < sizeof(resp.hdr)) {
|
||||||
|
printf("PCP message truncated: %d\n", bytesRead);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
else if (resp.hdr.hdr.opcode != OPCODE_MAP_RESPONSE) {
|
||||||
|
printf("PCP message type mismatch: %x\n", resp.hdr.hdr.opcode);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
else if (resp.hdr.hdr.result != 0) {
|
||||||
|
switch (resp.hdr.hdr.result) {
|
||||||
|
case 1: // UNSUPP_VERSION
|
||||||
|
printf("UNSUPPORTED\n");
|
||||||
|
break;
|
||||||
|
case 2: // NOT_AUTHORIZED
|
||||||
|
printf("UNAUTHORIZED\n");
|
||||||
|
break;
|
||||||
|
case 11: // CANNOT_PROVIDE_EXTERNAL
|
||||||
|
printf("CONFLICT\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("ERROR: %d\n", resp.hdr.hdr.result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
else if (memcmp(reqMsg.mappingNonce, resp.hdr.mappingNonce, sizeof(reqMsg.mappingNonce))) {
|
||||||
|
printf("PCP mapping nonce mismatch\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
else if (reqMsg.protocol != resp.hdr.protocol) {
|
||||||
|
printf("PCP protocol mismatch: %d wanted %d\n", resp.hdr.protocol, reqMsg.protocol);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
else if (reqMsg.internalPort != resp.hdr.internalPort) {
|
||||||
|
printf("PCP internal port mismatch: %d wanted %d\n", htons(resp.hdr.internalPort), htons(reqMsg.internalPort));
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
else if (reqMsg.externalPort != resp.hdr.externalPort) {
|
||||||
|
printf("PCP returned different external port: %d wanted %d\n", htons(resp.hdr.externalPort), htons(reqMsg.externalPort));
|
||||||
|
if (enable) {
|
||||||
|
// Clear the port mapping by modifying and resending the old request (with the same nonce)
|
||||||
|
reqMsg.hdr.lifetime = 0;
|
||||||
|
reqMsg.externalPort = resp.hdr.externalPort;
|
||||||
|
reqMsgLen = sizeof(reqMsg) - sizeof(reqMsg.preferFailureOption);
|
||||||
|
if (send(sock, (char*)&reqMsg, reqMsgLen, 0) == SOCKET_ERROR) {
|
||||||
|
printf("Failed to unmap unexpected external port: %d\n", WSAGetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
printf("OK (%d seconds remaining)\n", ntohl(resp.hdr.hdr.lifetime));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("DELETED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
closesocket(sock);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
closesocket(sock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
+227
@@ -0,0 +1,227 @@
|
|||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#include <WinSock2.h>
|
||||||
|
#include <WS2tcpip.h>
|
||||||
|
#include <iphlpapi.h>
|
||||||
|
#include <icmpapi.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define MINIUPNP_STATICLIB
|
||||||
|
#include <miniupnpc/miniupnpc.h>
|
||||||
|
|
||||||
|
static const char* k_SsdpSearchFormatString =
|
||||||
|
"M-SEARCH * HTTP/1.1\r\n"
|
||||||
|
"HOST: %s:1900\r\n"
|
||||||
|
"ST: ssdp:all\r\n"
|
||||||
|
"MAN: \"ssdp:discover\"\r\n"
|
||||||
|
"MX: 5\r\n"
|
||||||
|
"\r\n";
|
||||||
|
|
||||||
|
struct UPNPDev* getUPnPDevicesByAddress(IN_ADDR address)
|
||||||
|
{
|
||||||
|
SOCKET s;
|
||||||
|
SOCKADDR_IN connAddr;
|
||||||
|
char searchBuffer[512];
|
||||||
|
int chars;
|
||||||
|
|
||||||
|
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
if (s == INVALID_SOCKET) {
|
||||||
|
printf("socket() failed: %d\n", WSAGetLastError());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
connAddr = {};
|
||||||
|
connAddr.sin_family = AF_INET;
|
||||||
|
connAddr.sin_port = htons(1900);
|
||||||
|
connAddr.sin_addr = address;
|
||||||
|
|
||||||
|
// Use connect() to ensure we don't get responses from other devices
|
||||||
|
if (connect(s, (struct sockaddr*)&connAddr, sizeof(connAddr)) == SOCKET_ERROR) {
|
||||||
|
printf("connect() failed: %d\n", WSAGetLastError());
|
||||||
|
closesocket(s);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the first search message with HOST set properly
|
||||||
|
chars = snprintf(searchBuffer, ARRAYSIZE(searchBuffer), k_SsdpSearchFormatString, inet_ntoa(address));
|
||||||
|
if (send(s, searchBuffer, chars, 0) == SOCKET_ERROR) {
|
||||||
|
printf("send() failed: %d\n", WSAGetLastError());
|
||||||
|
closesocket(s);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send another search message with HOST set to 239.255.255.250 to avoid issues
|
||||||
|
// on routers that explicitly check for that HOST value
|
||||||
|
chars = snprintf(searchBuffer, ARRAYSIZE(searchBuffer), k_SsdpSearchFormatString, "239.255.255.250");
|
||||||
|
if (send(s, searchBuffer, chars, 0) == SOCKET_ERROR) {
|
||||||
|
printf("send() failed: %d\n", WSAGetLastError());
|
||||||
|
closesocket(s);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sleep(5000);
|
||||||
|
|
||||||
|
// Switch to non-blocking mode to read the responses
|
||||||
|
u_long mode = 1;
|
||||||
|
ioctlsocket(s, FIONBIO, &mode);
|
||||||
|
|
||||||
|
char responseBuffer[2048];
|
||||||
|
struct UPNPDev* deviceList = nullptr;
|
||||||
|
for (;;) {
|
||||||
|
int bytesRead = recv(s, responseBuffer, sizeof(responseBuffer) - 1, 0);
|
||||||
|
if (bytesRead == SOCKET_ERROR) {
|
||||||
|
if (WSAGetLastError() == WSAEMSGSIZE) {
|
||||||
|
// Skip packets larger than our buffer
|
||||||
|
printf("recv() message too large\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (WSAGetLastError() != WSAEWOULDBLOCK) {
|
||||||
|
printf("recv() failed: %d\n", WSAGetLastError());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null-terminate the buffer
|
||||||
|
responseBuffer[bytesRead] = 0;
|
||||||
|
|
||||||
|
// Parse the first status line:
|
||||||
|
// HTTP/1.1 200 OK
|
||||||
|
char* protocol = strtok(responseBuffer, " ");
|
||||||
|
char* statusCodeStr = strtok(nullptr, " ");
|
||||||
|
char* statusMessage = strtok(nullptr, "\r");
|
||||||
|
|
||||||
|
// Check for a valid response header
|
||||||
|
if (protocol == nullptr) {
|
||||||
|
printf("Missing protocol in SSDP header\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (statusCodeStr == nullptr) {
|
||||||
|
printf("Missing status code in SSDP header\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// FIXME: Should we require statusMessage too?
|
||||||
|
else if (_stricmp(protocol, "HTTP/1.0") && _stricmp(protocol, "HTTP/1.1")) {
|
||||||
|
printf("Unexpected protocol: %s\n", protocol);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (atoi(statusCodeStr) != 200) {
|
||||||
|
printf("Unexpected status: %s %s\n", statusCodeStr, statusMessage);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the header options
|
||||||
|
// SERVER: FreeBSD/11.2-RELEASE-p2 UPnP/1.1 MiniUPnPd/2.0\r\n
|
||||||
|
char* location = nullptr;
|
||||||
|
char* st = nullptr;
|
||||||
|
while (char* headerName = strtok(nullptr, "\r\n:")) {
|
||||||
|
char* headerValue = strtok(nullptr, "\r");
|
||||||
|
if (headerValue == nullptr) {
|
||||||
|
printf("Unexpected end of SSDP header\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip leading spaces
|
||||||
|
while (*headerValue == ' ') headerValue++;
|
||||||
|
|
||||||
|
if (!_stricmp(headerName, "LOCATION")) {
|
||||||
|
location = headerValue;
|
||||||
|
}
|
||||||
|
else if (!_stricmp(headerName, "ST")) {
|
||||||
|
st = headerValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!location || location[0] == 0 || !st || st[0] == 0) {
|
||||||
|
printf("Required value missing: \"%s\" \"%s\"\n", location, st);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UPNPDev* newDev = (struct UPNPDev*)malloc(sizeof(*newDev) + strlen(location) + strlen(st) + 2);
|
||||||
|
|
||||||
|
newDev->pNext = deviceList;
|
||||||
|
newDev->usn = &newDev->buffer[0]; newDev->buffer[0] = 0;
|
||||||
|
newDev->descURL = strcpy(newDev->usn + strlen(newDev->usn) + 1, location);
|
||||||
|
newDev->st = strcpy(newDev->descURL + strlen(newDev->descURL) + 1, st);
|
||||||
|
newDev->scope_id = 0; // IPv6 only
|
||||||
|
|
||||||
|
deviceList = newDev;
|
||||||
|
}
|
||||||
|
|
||||||
|
closesocket(s);
|
||||||
|
|
||||||
|
return deviceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start at TTL 2 to skip contacting our default gateway
|
||||||
|
#define TTL_START 2
|
||||||
|
|
||||||
|
bool getHopsIP4(IN_ADDR* hopAddress, int* hopAddressCount)
|
||||||
|
{
|
||||||
|
HANDLE icmpFile;
|
||||||
|
struct hostent* host;
|
||||||
|
const char* requestBuffer = "Test";
|
||||||
|
union {
|
||||||
|
ICMP_ECHO_REPLY replies[ANYSIZE_ARRAY];
|
||||||
|
char replyBuffer[128];
|
||||||
|
};
|
||||||
|
|
||||||
|
host = gethostbyname("google.com");
|
||||||
|
if (host == nullptr) {
|
||||||
|
printf("gethostbyname() failed: %d\n", WSAGetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
icmpFile = IcmpCreateFile();
|
||||||
|
if (icmpFile == INVALID_HANDLE_VALUE) {
|
||||||
|
printf("IcmpCreateFile() failed: %d\n", GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ttl;
|
||||||
|
for (ttl = TTL_START; ttl - TTL_START < *hopAddressCount; ttl++)
|
||||||
|
{
|
||||||
|
IP_OPTION_INFORMATION ipOptions;
|
||||||
|
|
||||||
|
ipOptions.Ttl = ttl;
|
||||||
|
ipOptions.Tos = 0;
|
||||||
|
ipOptions.Flags = 0;
|
||||||
|
ipOptions.OptionsSize = 0;
|
||||||
|
|
||||||
|
DWORD replyCount = IcmpSendEcho(icmpFile,
|
||||||
|
*(IPAddr*)host->h_addr,
|
||||||
|
(LPVOID)requestBuffer, sizeof(requestBuffer),
|
||||||
|
&ipOptions,
|
||||||
|
replyBuffer, sizeof(replyBuffer),
|
||||||
|
3000);
|
||||||
|
if (replyCount == 0) {
|
||||||
|
printf("IcmpSendEcho() failed: %d\n", GetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (replyCount != 1) {
|
||||||
|
printf("Got extra replies: %d\n", replyCount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replies[0].Status == IP_TTL_EXPIRED_TRANSIT) {
|
||||||
|
// Get the IP address that responded to us
|
||||||
|
printf("Hop %d: %s\n", ttl - TTL_START, inet_ntoa(*(IN_ADDR*)&replies[0].Address));
|
||||||
|
hopAddress[ttl - TTL_START] = *(IN_ADDR*)&replies[0].Address;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Bail on anything else
|
||||||
|
printf("Hop %d: %s (error %d)\n", ttl - TTL_START, inet_ntoa(*(IN_ADDR*)&replies[0].Address), replies[0].Status);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IcmpCloseHandle(icmpFile);
|
||||||
|
|
||||||
|
*hopAddressCount = ttl - TTL_START;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
+26
-143
@@ -12,6 +12,8 @@
|
|||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
#include <objbase.h>
|
#include <objbase.h>
|
||||||
|
|
||||||
|
#include "..\version.h"
|
||||||
|
|
||||||
#pragma comment(lib, "miniupnpc.lib")
|
#pragma comment(lib, "miniupnpc.lib")
|
||||||
#pragma comment(lib, "libnatpmp.lib")
|
#pragma comment(lib, "libnatpmp.lib")
|
||||||
#pragma comment(lib, "ws2_32.lib")
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
@@ -25,28 +27,7 @@
|
|||||||
#define NATPMP_STATICLIB
|
#define NATPMP_STATICLIB
|
||||||
#include <natpmp.h>
|
#include <natpmp.h>
|
||||||
|
|
||||||
#define STUN_MESSAGE_BINDING_REQUEST 0x0001
|
bool getExternalAddressPortIP4(int proto, unsigned short localPort, PSOCKADDR_IN wanAddr);
|
||||||
#define STUN_MESSAGE_BINDING_SUCCESS 0x0101
|
|
||||||
#define STUN_MESSAGE_COOKIE 0x2112a442
|
|
||||||
|
|
||||||
#define STUN_ATTRIBUTE_MAPPED_ADDRESS 0x0001
|
|
||||||
#define STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS 0x0020
|
|
||||||
|
|
||||||
typedef struct _STUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE {
|
|
||||||
USHORT attributeType;
|
|
||||||
USHORT attributeLength;
|
|
||||||
UCHAR reserved;
|
|
||||||
UCHAR addressFamily;
|
|
||||||
USHORT port;
|
|
||||||
ULONG address;
|
|
||||||
} STUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE, *PSTUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE;
|
|
||||||
|
|
||||||
typedef struct _STUN_MESSAGE {
|
|
||||||
USHORT messageType;
|
|
||||||
USHORT messageLength;
|
|
||||||
UINT magicCookie;
|
|
||||||
UINT transactionId[3];
|
|
||||||
} STUN_MESSAGE, *PSTUN_MESSAGE;
|
|
||||||
|
|
||||||
static struct port_entry {
|
static struct port_entry {
|
||||||
int proto;
|
int proto;
|
||||||
@@ -339,9 +320,16 @@ bool TestAllPorts(PSOCKADDR_STORAGE addr, char* portMsg, int portMsgLen)
|
|||||||
bool FindLocalInterfaceIP4Address(PSOCKADDR_IN addr)
|
bool FindLocalInterfaceIP4Address(PSOCKADDR_IN addr)
|
||||||
{
|
{
|
||||||
SOCKET s;
|
SOCKET s;
|
||||||
|
struct hostent* host;
|
||||||
|
|
||||||
printf("Finding local IP address...");
|
printf("Finding local IP address...");
|
||||||
|
|
||||||
|
host = gethostbyname("google.com");
|
||||||
|
if (host == nullptr) {
|
||||||
|
printf("gethostbyname() failed: %d\n", WSAGetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
if (s == INVALID_SOCKET) {
|
if (s == INVALID_SOCKET) {
|
||||||
printf("socket() failed: %d\n", WSAGetLastError());
|
printf("socket() failed: %d\n", WSAGetLastError());
|
||||||
@@ -351,7 +339,7 @@ bool FindLocalInterfaceIP4Address(PSOCKADDR_IN addr)
|
|||||||
SOCKADDR_IN sin = {};
|
SOCKADDR_IN sin = {};
|
||||||
sin.sin_family = AF_INET;
|
sin.sin_family = AF_INET;
|
||||||
sin.sin_port = htons(443);
|
sin.sin_port = htons(443);
|
||||||
sin.sin_addr.S_un.S_addr = inet_addr("8.8.8.8");
|
sin.sin_addr = *(struct in_addr*)host->h_addr;
|
||||||
int err = connect(s, (struct sockaddr*)&sin, sizeof(sin));
|
int err = connect(s, (struct sockaddr*)&sin, sizeof(sin));
|
||||||
if (err == SOCKET_ERROR) {
|
if (err == SOCKET_ERROR) {
|
||||||
printf("connect() failed: %d\n", WSAGetLastError());
|
printf("connect() failed: %d\n", WSAGetLastError());
|
||||||
@@ -431,115 +419,6 @@ UPnPPortStatus UPnPCheckPort(struct UPNPUrls* urls, struct IGDdatas* data, int p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool STUNFindWanAddress(PSOCKADDR_IN wanAddr)
|
|
||||||
{
|
|
||||||
SOCKET s;
|
|
||||||
|
|
||||||
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
||||||
if (s == INVALID_SOCKET) {
|
|
||||||
printf("socket() failed: %d\n", WSAGetLastError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct hostent *host;
|
|
||||||
|
|
||||||
host = gethostbyname("stun.stunprotocol.org");
|
|
||||||
if (host == nullptr) {
|
|
||||||
printf("gethostbyname() failed\n");
|
|
||||||
closesocket(s);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SOCKADDR_IN sin = {};
|
|
||||||
sin.sin_family = AF_INET;
|
|
||||||
sin.sin_port = htons(3478);
|
|
||||||
sin.sin_addr = *(struct in_addr*)host->h_addr;
|
|
||||||
int err = connect(s, (struct sockaddr*)&sin, sizeof(sin));
|
|
||||||
if (err == SOCKET_ERROR) {
|
|
||||||
printf("connect() failed: %d\n", WSAGetLastError());
|
|
||||||
closesocket(s);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
STUN_MESSAGE reqMsg;
|
|
||||||
reqMsg.messageType = htons(STUN_MESSAGE_BINDING_REQUEST);
|
|
||||||
reqMsg.messageLength = 0;
|
|
||||||
reqMsg.magicCookie = htonl(STUN_MESSAGE_COOKIE);
|
|
||||||
for (int i = 0; i < ARRAYSIZE(reqMsg.transactionId); i++) {
|
|
||||||
rand_s(&reqMsg.transactionId[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
err = send(s, (char *)&reqMsg, sizeof(reqMsg), 0);
|
|
||||||
if (err == SOCKET_ERROR) {
|
|
||||||
printf("send() failed: %d\n", WSAGetLastError());
|
|
||||||
closesocket(s);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
STUN_MESSAGE respMsg;
|
|
||||||
STUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE mappedAddress;
|
|
||||||
};
|
|
||||||
char respBuf[128];
|
|
||||||
};
|
|
||||||
|
|
||||||
int bytesRead = recv(s, respBuf, sizeof(respBuf), 0);
|
|
||||||
if (bytesRead == SOCKET_ERROR) {
|
|
||||||
printf("recv() failed: %d\n", WSAGetLastError());
|
|
||||||
closesocket(s);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (bytesRead < sizeof(respMsg)) {
|
|
||||||
printf("STUN message truncated: %d\n", bytesRead);
|
|
||||||
closesocket(s);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
closesocket(s);
|
|
||||||
|
|
||||||
if (htonl(respMsg.magicCookie) != STUN_MESSAGE_COOKIE) {
|
|
||||||
printf("Bad STUN cookie value: %x\n", htonl(respMsg.magicCookie));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (!RtlEqualMemory(reqMsg.transactionId, respMsg.transactionId, sizeof(reqMsg.transactionId))) {
|
|
||||||
printf("STUN transaction ID mismatch\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (htons(respMsg.messageType) != STUN_MESSAGE_BINDING_SUCCESS) {
|
|
||||||
printf("STUN message type mismatch: %x\n", htons(respMsg.messageType));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (bytesRead < sizeof(respMsg) + sizeof(mappedAddress)) {
|
|
||||||
printf("STUN message too short: %d\n", bytesRead);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (htons(mappedAddress.attributeType) != STUN_ATTRIBUTE_MAPPED_ADDRESS &&
|
|
||||||
htons(mappedAddress.attributeType) != STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS) {
|
|
||||||
printf("STUN attribute type mismatch: %x\n", htons(mappedAddress.attributeType));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (htons(mappedAddress.attributeLength) != 8) {
|
|
||||||
printf("STUN address length mismatch: %d\n", htons(mappedAddress.attributeLength));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (mappedAddress.addressFamily != 1) {
|
|
||||||
printf("STUN address family mismatch: %x\n", mappedAddress.addressFamily);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (htons(mappedAddress.attributeType) == STUN_ATTRIBUTE_MAPPED_ADDRESS) {
|
|
||||||
// The address is directly encoded
|
|
||||||
wanAddr->sin_addr.S_un.S_addr = mappedAddress.address;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// The address is XORed
|
|
||||||
wanAddr->sin_addr.S_un.S_addr = mappedAddress.address ^ respMsg.magicCookie;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CheckWANAccess(PSOCKADDR_IN wanAddr, PSOCKADDR_IN reportedWanAddr, bool* foundPortForwardingRules, bool* igdDisconnected)
|
bool CheckWANAccess(PSOCKADDR_IN wanAddr, PSOCKADDR_IN reportedWanAddr, bool* foundPortForwardingRules, bool* igdDisconnected)
|
||||||
{
|
{
|
||||||
natpmp_t natpmp;
|
natpmp_t natpmp;
|
||||||
@@ -618,6 +497,9 @@ bool CheckWANAccess(PSOCKADDR_IN wanAddr, PSOCKADDR_IN reportedWanAddr, bool* fo
|
|||||||
|
|
||||||
FreeUPNPUrls(&urls);
|
FreeUPNPUrls(&urls);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
printf("No UPnP devices detected\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the delay of upnpDiscoverAll() to also allow the NAT-PMP endpoint time to respond
|
// Use the delay of upnpDiscoverAll() to also allow the NAT-PMP endpoint time to respond
|
||||||
@@ -643,7 +525,7 @@ bool CheckWANAccess(PSOCKADDR_IN wanAddr, PSOCKADDR_IN reportedWanAddr, bool* fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
printf("Detecting WAN IP address via STUN...");
|
printf("Detecting WAN IP address via STUN...");
|
||||||
if (!STUNFindWanAddress(wanAddr)) {
|
if (!getExternalAddressPortIP4(IPPROTO_UDP, 0, wanAddr)) {
|
||||||
if (!gotReportedWanAddress) {
|
if (!gotReportedWanAddress) {
|
||||||
DisplayMessage("Unable to determine your public IP address. Please check your Internet connection.");
|
DisplayMessage("Unable to determine your public IP address. Please check your Internet connection.");
|
||||||
return false;
|
return false;
|
||||||
@@ -673,7 +555,7 @@ bool IsPossibleCGN(PSOCKADDR_IN wanAddr)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 100.64.0.0/10 - RFC6598 official CGN address
|
// 100.64.0.0/10 - RFC6598 official CGN address
|
||||||
else if ((addr & 0xFFC0) == 0x64400000) {
|
else if ((addr & 0xFFC00000) == 0x64400000) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,6 +592,12 @@ int main(int argc, char* argv[])
|
|||||||
snprintf(logFilePath, sizeof(logFilePath), "%s\\%s", tempPath, "mis-test.log");
|
snprintf(logFilePath, sizeof(logFilePath), "%s\\%s", tempPath, "mis-test.log");
|
||||||
freopen(logFilePath, "w", stdout);
|
freopen(logFilePath, "w", stdout);
|
||||||
|
|
||||||
|
// Print a log header
|
||||||
|
printf("Moonlight Internet Streaming Tester v" VER_VERSION_STR "\n");
|
||||||
|
|
||||||
|
// Print a console header
|
||||||
|
fprintf(stderr, "Moonlight Internet Streaming Tester v" VER_VERSION_STR "\n\n");
|
||||||
|
|
||||||
int err = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
int err = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||||
if (err != NO_ERROR) {
|
if (err != NO_ERROR) {
|
||||||
DisplayMessage("Unable to initialize WinSock");
|
DisplayMessage("Unable to initialize WinSock");
|
||||||
@@ -771,12 +659,6 @@ int main(int argc, char* argv[])
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (igdDisconnected) {
|
|
||||||
DisplayMessage("Your router reports to be disconnected from the Internet. Make sure UPnP is enabled in your router settings. "
|
|
||||||
"If this message persists, make sure your router isn't connected to the Internet through another router. If it is, switch one of the routers to bridge mode.\n\n"
|
|
||||||
"Just in case this warning is due to a buggy router, the test will continue anyway.", MpWarn, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect a double NAT by detecting STUN and and UPnP mismatches
|
// 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) {
|
if (sin.sin_addr.S_un.S_addr != locallyReportedWanAddr.sin_addr.S_un.S_addr) {
|
||||||
printf("Testing GameStream ports via UPnP/NAT-PMP reported WAN address\n");
|
printf("Testing GameStream ports via UPnP/NAT-PMP reported WAN address\n");
|
||||||
@@ -797,7 +679,7 @@ int main(int argc, char* argv[])
|
|||||||
printf("Testing GameStream ports via STUN-reported WAN address\n");
|
printf("Testing GameStream ports via STUN-reported WAN address\n");
|
||||||
if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf))) {
|
if (!TestAllPorts(&ss, portMsgBuf, sizeof(portMsgBuf))) {
|
||||||
if (IsDoubleNAT(&locallyReportedWanAddr)) {
|
if (IsDoubleNAT(&locallyReportedWanAddr)) {
|
||||||
DisplayMessage("Your router appears be connected to the Internet through another router. This configuration breaks port forwarding. To resolve this, switch one of the routers into bridge mode.");
|
snprintf(msgBuf, sizeof(msgBuf), "Your router appears be connected to the Internet through another router. Make sure both routers have UPnP enabled, or better yet, switch one of the routers into bridge/AP mode.");
|
||||||
DisplayMessage(msgBuf);
|
DisplayMessage(msgBuf);
|
||||||
}
|
}
|
||||||
else if (IsPossibleCGN(&locallyReportedWanAddr)) {
|
else if (IsPossibleCGN(&locallyReportedWanAddr)) {
|
||||||
@@ -809,8 +691,9 @@ int main(int argc, char* argv[])
|
|||||||
DisplayMessage(msgBuf);
|
DisplayMessage(msgBuf);
|
||||||
}
|
}
|
||||||
else if (upnpRulesFound) {
|
else if (upnpRulesFound) {
|
||||||
snprintf(msgBuf, sizeof(msgBuf), "We found the correct UPnP rules, but we couldn't confirm that they are working. You can try streaming from a different network by typing the following address into Moonlight's Add PC dialog: %s\n\n"
|
snprintf(msgBuf, sizeof(msgBuf), "We found the correct UPnP rules, but we couldn't confirm that they are working. Depending on your router, it may only work when connecting from a different network.\n\n"
|
||||||
"If that doesn't work, check your router settings for any existing Moonlight port forwarding entries and delete them.", wanAddrStr);
|
"You can try streaming from a different network (like cellular data or tethering) by typing the following address into Moonlight's Add PC dialog: %s\n\n"
|
||||||
|
"If that doesn't work, check your router settings for any existing Moonlight port forwarding entries and delete them or try restarting your router.", wanAddrStr);
|
||||||
DisplayMessage(msgBuf, MpWarn);
|
DisplayMessage(msgBuf, MpWarn);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
#include "../version.h"
|
#include "../version.h"
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION VER_FILEVERSION
|
FILEVERSION VER_VERSION
|
||||||
BEGIN
|
BEGIN
|
||||||
BLOCK "StringFileInfo"
|
BLOCK "StringFileInfo"
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -10,7 +10,7 @@ BEGIN
|
|||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", VER_COMPANYNAME_STR
|
VALUE "CompanyName", VER_COMPANYNAME_STR
|
||||||
VALUE "FileDescription", "Moonlight Internet Streaming Tester"
|
VALUE "FileDescription", "Moonlight Internet Streaming Tester"
|
||||||
VALUE "FileVersion", VER_FILEVERSION_STR
|
VALUE "ProductName", VER_PRODUCTNAME_STR
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,7 @@
|
|||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="mist.cpp" />
|
<ClCompile Include="mist.cpp" />
|
||||||
|
<ClCompile Include="stun.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="mist.rc" />
|
<ResourceCompile Include="mist.rc" />
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
<ClCompile Include="mist.cpp">
|
<ClCompile Include="mist.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="stun.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\version.h">
|
<ClInclude Include="..\version.h">
|
||||||
|
|||||||
+211
@@ -0,0 +1,211 @@
|
|||||||
|
#define _CRT_RAND_S
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <WinSock2.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define STUN_PORT 3478
|
||||||
|
|
||||||
|
#define STUN_RECV_TIMEOUT_SEC 3
|
||||||
|
|
||||||
|
#define STUN_MESSAGE_BINDING_REQUEST 0x0001
|
||||||
|
#define STUN_MESSAGE_BINDING_SUCCESS 0x0101
|
||||||
|
#define STUN_MESSAGE_COOKIE 0x2112a442
|
||||||
|
|
||||||
|
#define STUN_ATTRIBUTE_MAPPED_ADDRESS 0x0001
|
||||||
|
#define STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS 0x0020
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
typedef struct _STUN_ATTRIBUTE_HEADER {
|
||||||
|
unsigned short type;
|
||||||
|
unsigned short length;
|
||||||
|
} STUN_ATTRIBUTE_HEADER, *PSTUN_ATTRIBUTE_HEADER;
|
||||||
|
|
||||||
|
typedef struct _STUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE {
|
||||||
|
STUN_ATTRIBUTE_HEADER hdr;
|
||||||
|
unsigned char reserved;
|
||||||
|
unsigned char addressFamily;
|
||||||
|
unsigned short port;
|
||||||
|
unsigned int address;
|
||||||
|
} STUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE, *PSTUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE;
|
||||||
|
|
||||||
|
#define TXID_DWORDS 3
|
||||||
|
typedef struct _STUN_MESSAGE {
|
||||||
|
unsigned short messageType;
|
||||||
|
unsigned short messageLength;
|
||||||
|
unsigned int magicCookie;
|
||||||
|
int transactionId[TXID_DWORDS];
|
||||||
|
} STUN_MESSAGE, *PSTUN_MESSAGE;
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
bool getExternalAddressPortIP4(int proto, unsigned short localPort, PSOCKADDR_IN wanAddr)
|
||||||
|
{
|
||||||
|
SOCKET sock;
|
||||||
|
STUN_MESSAGE reqMsg;
|
||||||
|
int i;
|
||||||
|
int bytesRead;
|
||||||
|
int tries;
|
||||||
|
int timeout;
|
||||||
|
PSTUN_ATTRIBUTE_HEADER attribute;
|
||||||
|
PSTUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE ipv4Attrib;
|
||||||
|
struct hostent *host;
|
||||||
|
union {
|
||||||
|
STUN_MESSAGE hdr;
|
||||||
|
char buf[1024];
|
||||||
|
} resp;
|
||||||
|
|
||||||
|
host = gethostbyname("stun.stunprotocol.org");
|
||||||
|
if (host == nullptr) {
|
||||||
|
printf("gethostbyname() failed: %d\n", WSAGetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sock = socket(AF_INET, proto == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM, proto);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
printf("socket() failed: %d\n", WSAGetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in bindAddr = {};
|
||||||
|
bindAddr.sin_family = AF_INET;
|
||||||
|
bindAddr.sin_port = htons(localPort);
|
||||||
|
if (bind(sock, (struct sockaddr*)&bindAddr, sizeof(bindAddr)) == SOCKET_ERROR) {
|
||||||
|
printf("bind() failed: %d\n", WSAGetLastError());
|
||||||
|
closesocket(sock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reqMsg.messageType = htons(STUN_MESSAGE_BINDING_REQUEST);
|
||||||
|
reqMsg.messageLength = 0;
|
||||||
|
reqMsg.magicCookie = htonl(STUN_MESSAGE_COOKIE);
|
||||||
|
for (i = 0; i < TXID_DWORDS; i++) {
|
||||||
|
reqMsg.transactionId[i] = rand();
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKADDR_IN stunAddr = {};
|
||||||
|
stunAddr.sin_family = AF_INET;
|
||||||
|
stunAddr.sin_port = htons(STUN_PORT);
|
||||||
|
stunAddr.sin_addr = *(struct in_addr*)host->h_addr;
|
||||||
|
|
||||||
|
// We'll connect() even for UDP so we can use send()/recv() and share more code
|
||||||
|
if (connect(sock, (struct sockaddr*)&stunAddr, sizeof(stunAddr)) == SOCKET_ERROR) {
|
||||||
|
printf("connect() failed: %d\n", WSAGetLastError());
|
||||||
|
closesocket(sock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For UDP, we'll do 3 iterations of 1 second each. For TCP,
|
||||||
|
// we'll do one iteration with a 3 second wait.
|
||||||
|
if (proto == IPPROTO_TCP) {
|
||||||
|
tries = 1;
|
||||||
|
timeout = STUN_RECV_TIMEOUT_SEC;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tries = STUN_RECV_TIMEOUT_SEC;
|
||||||
|
timeout = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesRead = SOCKET_ERROR;
|
||||||
|
for (i = 0; i < tries; i++) {
|
||||||
|
// Retransmit the request every second until the timeout elapses
|
||||||
|
if (send(sock, (char *)&reqMsg, sizeof(reqMsg), 0) == SOCKET_ERROR) {
|
||||||
|
printf("send() failed: %d\n", WSAGetLastError());
|
||||||
|
closesocket(sock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd_set fds;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(sock, &fds);
|
||||||
|
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = timeout;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
|
||||||
|
int selectRes = select(0, &fds, nullptr, nullptr, &tv);
|
||||||
|
if (selectRes == 0) {
|
||||||
|
// Timeout - continue looping
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (selectRes == SOCKET_ERROR) {
|
||||||
|
printf("select() failed: %d\n", WSAGetLastError());
|
||||||
|
closesocket(sock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error handling is below
|
||||||
|
bytesRead = recv(sock, resp.buf, sizeof(resp.buf), 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
closesocket(sock);
|
||||||
|
|
||||||
|
if (bytesRead == 0) {
|
||||||
|
printf("No response from STUN server\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (bytesRead == SOCKET_ERROR) {
|
||||||
|
printf("Failed to read STUN binding response: %d\n", WSAGetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (bytesRead < sizeof(resp.hdr)) {
|
||||||
|
printf("STUN message truncated: %d\n", bytesRead);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (htonl(resp.hdr.magicCookie) != STUN_MESSAGE_COOKIE) {
|
||||||
|
printf("Bad STUN cookie value: %x\n", htonl(resp.hdr.magicCookie));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (memcmp(reqMsg.transactionId, resp.hdr.transactionId, sizeof(reqMsg.transactionId))) {
|
||||||
|
printf("STUN transaction ID mismatch\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (htons(resp.hdr.messageType) != STUN_MESSAGE_BINDING_SUCCESS) {
|
||||||
|
printf("STUN message type mismatch: %x\n", htons(resp.hdr.messageType));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute = (PSTUN_ATTRIBUTE_HEADER)(&resp.hdr + 1);
|
||||||
|
bytesRead -= sizeof(resp.hdr);
|
||||||
|
while (bytesRead > sizeof(*attribute)) {
|
||||||
|
if (bytesRead < sizeof(*attribute) + htons(attribute->length)) {
|
||||||
|
printf("STUN attribute out of bounds: %d\n", htons(attribute->length));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (htons(attribute->type) != STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS) {
|
||||||
|
// Continue searching if this wasn't our address
|
||||||
|
bytesRead -= sizeof(*attribute) + htons(attribute->length);
|
||||||
|
attribute = (PSTUN_ATTRIBUTE_HEADER)(((char*)attribute) + sizeof(*attribute) + htons(attribute->length));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv4Attrib = (PSTUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE)attribute;
|
||||||
|
if (htons(ipv4Attrib->hdr.length) != 8) {
|
||||||
|
printf("STUN address length mismatch: %d\n", htons(ipv4Attrib->hdr.length));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (ipv4Attrib->addressFamily != 1) {
|
||||||
|
printf("STUN address family mismatch: %x\n", ipv4Attrib->addressFamily);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*wanAddr = {};
|
||||||
|
wanAddr->sin_family = AF_INET;
|
||||||
|
|
||||||
|
// The address and port are XORed with the cookie
|
||||||
|
wanAddr->sin_port = ipv4Attrib->port ^ (short)resp.hdr.magicCookie;
|
||||||
|
wanAddr->sin_addr.S_un.S_addr = ipv4Attrib->address ^ resp.hdr.magicCookie;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("No XOR mapped address found in STUN response!\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#define VER_VERSION 2,0,0,0
|
||||||
|
#define VER_VERSION_STR "2.0.0.0"
|
||||||
|
|
||||||
#define VER_COMPANYNAME_STR "Moonlight Game Streaming Project"
|
#define VER_COMPANYNAME_STR "Moonlight Game Streaming Project"
|
||||||
#define VER_FILEVERSION 1,2,0,0
|
#define VER_PRODUCTNAME_STR "Moonlight Internet Streaming Helper"
|
||||||
#define VER_FILEVERSION_STR "1.2.0.0\0"
|
|
||||||
|
|||||||
Reference in New Issue
Block a user