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 | ||
|
|
83c4413926 | ||
|
|
15d60ed10a | ||
|
|
f1738fc11e | ||
|
|
0867814ae8 | ||
|
|
541a0bf9d9 | ||
|
|
632bba6da8 | ||
|
|
0ec33223f4 | ||
|
|
45b624c203 | ||
|
|
7bc806edb4 | ||
|
|
afe60bfe30 | ||
|
|
ac4e0101f2 | ||
|
|
b619d7fed5 | ||
|
|
522fa0426c | ||
|
|
c35241f7d6 | ||
|
|
ae4346e587 | ||
|
|
0e7bea68ae | ||
|
|
8362f5495c | ||
|
|
44625583b9 | ||
|
|
727e8c0339 | ||
|
|
fe4d895d88 | ||
|
|
5d0b834efe | ||
|
|
dad9b01a6f | ||
|
|
9a95d075e6 |
+2
-1
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
|
||||
xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension">
|
||||
<Product Id="*" Name="Moonlight Internet Streaming Helper" Language="1033" Version="1.0.0.0" Manufacturer="Cameron Gutman" UpgradeCode="cfc2fb53-88e2-4867-851d-9ed9fe7ab2a3">
|
||||
<Product Id="*" Name="Moonlight Internet Streaming Helper" Language="1033" Version="!(bind.fileVersion.MistExe)"
|
||||
Manufacturer="Moonlight Game Streaming Project" UpgradeCode="cfc2fb53-88e2-4867-851d-9ed9fe7ab2a3">
|
||||
<Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" />
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
|
||||
|
||||
Binary file not shown.
+4
-4
@@ -1,16 +1,16 @@
|
||||
#include <windows.h>
|
||||
#include "version.h"
|
||||
#include "../version.h"
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VER_FILEVERSION
|
||||
FILEVERSION VER_VERSION
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904E4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", VER_COMPANYNAME_STR
|
||||
VALUE "FileDescription", VER_FILEDESCRIPTION_STR
|
||||
VALUE "FileVersion", VER_FILEVERSION_STR
|
||||
VALUE "FileDescription", "Moonlight Internet Streaming Service"
|
||||
VALUE "ProductName", VER_PRODUCTNAME_STR
|
||||
END
|
||||
END
|
||||
|
||||
|
||||
+3
-1
@@ -162,12 +162,14 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="miss.cpp" />
|
||||
<ClCompile Include="pcp.cpp" />
|
||||
<ClCompile Include="tracer.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="miss.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="version.h" />
|
||||
<ClInclude Include="..\version.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
<ClCompile Include="miss.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="tracer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pcp.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="miss.rc">
|
||||
@@ -25,7 +31,7 @@
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="version.h">
|
||||
<ClInclude Include="..\version.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#define VER_COMPANYNAME_STR "Moonlight Game Streaming Project"
|
||||
#define VER_FILEVERSION 1,0,0,0
|
||||
#define VER_FILEVERSION_STR "1.0.0.0\0"
|
||||
#define VER_FILEDESCRIPTION_STR "Moonlight Internet Streaming Service"
|
||||
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
#include <windows.h>
|
||||
#include "../version.h"
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VER_VERSION
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904E4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", VER_COMPANYNAME_STR
|
||||
VALUE "FileDescription", "Moonlight Internet Streaming Tester"
|
||||
VALUE "ProductName", VER_PRODUCTNAME_STR
|
||||
END
|
||||
END
|
||||
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
||||
@@ -160,6 +160,13 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="mist.cpp" />
|
||||
<ClCompile Include="stun.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="mist.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\version.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
||||
@@ -18,5 +18,18 @@
|
||||
<ClCompile Include="mist.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="stun.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\version.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="mist.rc">
|
||||
<Filter>Source Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
+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;
|
||||
}
|
||||
Reference in New Issue
Block a user