Cameron Gutman fccf9e7191 Only operate the relay and maintain the pinholes when GameStream is enabled
Sunshine will soon have native IPv6 support, so we need to stay out of its way.
2023-08-25 23:39:30 -05:00

1124 lines
36 KiB
C++

#define _CRT_SECURE_NO_WARNINGS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#pragma comment(lib, "ws2_32")
#include <WinSock2.h>
#include <Mswsock.h>
#include <Ws2ipdef.h>
#include <WS2tcpip.h>
#pragma comment(lib, "iphlpapi")
#include <Iphlpapi.h>
#pragma comment(lib, "miniupnpc.lib")
#define MINIUPNP_STATICLIB
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>
#include <miniupnpc/upnperrors.h>
#include "../version.h"
#define SERVICE_NAME L"GSv6FwdSvc"
#define GAA_INITIAL_SIZE 8192
LPFN_WSARECVMSG WSARecvMsg;
bool PCPMapPort(PSOCKADDR_STORAGE localAddr, int localAddrLen, PSOCKADDR_STORAGE pcpAddr, int pcpAddrLen, int proto, int port, bool enable, bool indefinite);
static const unsigned short UDP_PORTS[] = {
47998, 47999, 48000, 48002, 48010
};
static const unsigned short TCP_PORTS[] = {
47984, 47989, 48010
};
typedef struct _SOCKET_TUPLE {
SOCKET s1;
SOCKET s2;
} SOCKET_TUPLE, *PSOCKET_TUPLE;
typedef struct _LISTENER_TUPLE {
SOCKET listener;
unsigned short port;
} LISTENER_TUPLE, *PLISTENER_TUPLE;
typedef struct _UDP_TUPLE {
SOCKET ipv6Socket;
SOCKET ipv4Socket;
unsigned short port;
} UDP_TUPLE, *PUDP_TUPLE;
int
ForwardSocketData(SOCKET from, SOCKET to)
{
char buffer[4096];
int len;
len = recv(from, buffer, sizeof(buffer), 0);
if (len <= 0) {
return len;
}
if (send(to, buffer, len, 0) != len) {
return SOCKET_ERROR;
}
return len;
}
DWORD
WINAPI
TcpRelayThreadProc(LPVOID Context)
{
PSOCKET_TUPLE tuple = (PSOCKET_TUPLE)Context;
fd_set fds;
int err;
bool s1ReadShutdown = false;
bool s2ReadShutdown = false;
for (;;) {
FD_ZERO(&fds);
if (!s1ReadShutdown) {
FD_SET(tuple->s1, &fds);
}
if (!s2ReadShutdown) {
FD_SET(tuple->s2, &fds);
}
if (s1ReadShutdown && s2ReadShutdown) {
// Both sides gracefully closed
break;
}
err = select(0, &fds, NULL, NULL, NULL);
if (err <= 0) {
break;
}
else if (FD_ISSET(tuple->s1, &fds)) {
err = ForwardSocketData(tuple->s1, tuple->s2);
if (err == 0) {
// Graceful closure from s1. Propagate to s2.
shutdown(tuple->s2, SD_SEND);
s1ReadShutdown = true;
}
else if (err < 0) {
// Forceful closure. Tear down the whole connection.
break;
}
}
else if (FD_ISSET(tuple->s2, &fds)) {
err = ForwardSocketData(tuple->s2, tuple->s1);
if (err == 0) {
// Graceful closure from s2. Propagate to s1.
shutdown(tuple->s1, SD_SEND);
s2ReadShutdown = true;
}
else if (err < 0) {
// Forceful closure. Tear down the whole connection.
break;
}
}
}
closesocket(tuple->s1);
closesocket(tuple->s2);
free(tuple);
return 0;
}
PIP_ADAPTER_ADDRESSES
AllocAndGetAdaptersAddresses(ULONG Family, ULONG Flags)
{
PIP_ADAPTER_ADDRESSES addresses;
ULONG length;
ULONG error;
addresses = NULL;
length = GAA_INITIAL_SIZE;
do {
free(addresses);
addresses = (PIP_ADAPTER_ADDRESSES)malloc(length);
if (addresses == NULL) {
printf("malloc(%u) failed\n", length);
return NULL;
}
error = GetAdaptersAddresses(Family, Flags, NULL, addresses, &length);
} while (error == ERROR_BUFFER_OVERFLOW);
if (error != ERROR_SUCCESS) {
printf("GetAdaptersAddresses() failed: %d\n", error);
free(addresses);
return NULL;
}
return addresses;
}
void
FindLocalAddressBySocket(SOCKET s, PIN_ADDR targetAddress)
{
PIP_ADAPTER_ADDRESSES addresses;
PIP_ADAPTER_ADDRESSES currentAdapter;
PIP_ADAPTER_UNICAST_ADDRESS currentAddress;
SOCKADDR_IN6 localSockAddr;
int localSockAddrLen;
// If we fail to find an address, return the loopback address
targetAddress->S_un.S_addr = htonl(INADDR_LOOPBACK);
// Get local address of the accepted socket so we can find the interface
localSockAddrLen = sizeof(localSockAddr);
if (getsockname(s, (PSOCKADDR)&localSockAddr, &localSockAddrLen) == SOCKET_ERROR) {
printf("getsockname() failed: %d\n", WSAGetLastError());
return;
}
// Get a list of all interfaces and addresses on the system
addresses = AllocAndGetAdaptersAddresses(AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST |
GAA_FLAG_SKIP_MULTICAST |
GAA_FLAG_SKIP_DNS_SERVER |
GAA_FLAG_SKIP_FRIENDLY_NAME);
if (addresses == NULL) {
return;
}
// First, find the interface that owns the incoming address
currentAdapter = addresses;
while (currentAdapter != NULL) {
// Check if this interface has the IP address we want
currentAddress = currentAdapter->FirstUnicastAddress;
while (currentAddress != NULL) {
if (currentAddress->Address.lpSockaddr->sa_family == AF_INET6) {
PSOCKADDR_IN6 ifaceAddrV6 = (PSOCKADDR_IN6)currentAddress->Address.lpSockaddr;
if (RtlEqualMemory(&localSockAddr.sin6_addr, &ifaceAddrV6->sin6_addr, sizeof(IN6_ADDR))) {
break;
}
}
currentAddress = currentAddress->Next;
}
if (currentAddress != NULL) {
// It does, bail out
break;
}
currentAdapter = currentAdapter->Next;
}
// Check if we found the incoming interface
if (currentAdapter == NULL) {
// Hopefully the error is caused by transient interface reconfiguration
printf("Unable to find incoming interface\n");
goto Exit;
}
// Now find an IPv4 address on this interface
currentAddress = currentAdapter->FirstUnicastAddress;
while (currentAddress != NULL) {
if (currentAddress->Address.lpSockaddr->sa_family == AF_INET) {
PSOCKADDR_IN ifaceAddrV4 = (PSOCKADDR_IN)currentAddress->Address.lpSockaddr;
*targetAddress = ifaceAddrV4->sin_addr;
goto Exit;
}
currentAddress = currentAddress->Next;
}
// If we get here, there was no IPv4 address on this interface.
// This is a valid situation, for example if the IPv6 interface
// has no IPv4 connectivity. In this case, we can preserve most
// functionality by forwarding via localhost. WoL won't work but
// the basic stuff will.
Exit:
free(addresses);
return;
}
DWORD
WINAPI
TcpListenerThreadProc(LPVOID Context)
{
PLISTENER_TUPLE tuple = (PLISTENER_TUPLE)Context;
SOCKET acceptedSocket, targetSocket;
SOCKADDR_IN targetAddress;
PSOCKET_TUPLE relayTuple;
HANDLE thread;
printf("TCP relay running for port %d\n", tuple->port);
for (;;) {
acceptedSocket = accept(tuple->listener, NULL, 0);
if (acceptedSocket == INVALID_SOCKET) {
printf("accept() failed: %d\n", WSAGetLastError());
break;
}
targetSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (targetSocket == INVALID_SOCKET) {
printf("socket() failed: %d\n", WSAGetLastError());
closesocket(acceptedSocket);
continue;
}
RtlZeroMemory(&targetAddress, sizeof(targetAddress));
targetAddress.sin_family = AF_INET;
targetAddress.sin_port = htons(tuple->port);
FindLocalAddressBySocket(acceptedSocket, &targetAddress.sin_addr);
if (connect(targetSocket, (PSOCKADDR)&targetAddress, sizeof(targetAddress)) == SOCKET_ERROR) {
// FIXME: This can race with reopening stdout and cause a crash in the CRT
//printf("connect() failed: %d\n", WSAGetLastError());
closesocket(acceptedSocket);
closesocket(targetSocket);
continue;
}
relayTuple = (PSOCKET_TUPLE)malloc(sizeof(*relayTuple));
if (relayTuple == NULL) {
closesocket(acceptedSocket);
closesocket(targetSocket);
break;
}
relayTuple->s1 = acceptedSocket;
relayTuple->s2 = targetSocket;
thread = CreateThread(NULL, 0, TcpRelayThreadProc, relayTuple, 0, NULL);
if (thread == NULL) {
printf("CreateThread() failed: %d\n", GetLastError());
closesocket(acceptedSocket);
closesocket(targetSocket);
free(relayTuple);
break;
}
CloseHandle(thread);
}
closesocket(tuple->listener);
free(tuple);
return 0;
}
int StartTcpRelay(unsigned short Port, SOCKET* Listener)
{
SOCKET listeningSocket;
SOCKADDR_IN6 addr6;
HANDLE thread;
PLISTENER_TUPLE tuple;
DWORD val;
listeningSocket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (listeningSocket == INVALID_SOCKET) {
printf("socket() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
val = PROTECTION_LEVEL_UNRESTRICTED;
if (setsockopt(listeningSocket, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (char*)&val, sizeof(val)) == SOCKET_ERROR) {
printf("setsockopt(IPV6_PROTECTION_LEVEL) failed: %d\n", WSAGetLastError());
}
RtlZeroMemory(&addr6, sizeof(addr6));
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(Port);
if (bind(listeningSocket, (PSOCKADDR)&addr6, sizeof(addr6)) == SOCKET_ERROR) {
printf("bind() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
if (listen(listeningSocket, SOMAXCONN) == SOCKET_ERROR) {
printf("listen() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
tuple = (PLISTENER_TUPLE)malloc(sizeof(*tuple));
if (tuple == NULL) {
return ERROR_OUTOFMEMORY;
}
tuple->listener = *Listener = listeningSocket;
tuple->port = Port;
thread = CreateThread(NULL, 0, TcpListenerThreadProc, tuple, 0, NULL);
if (thread == NULL) {
printf("CreateThread() failed: %d\n", GetLastError());
return GetLastError();
}
CloseHandle(thread);
return 0;
}
int
ForwardUdpPacketV4toV6(PUDP_TUPLE tuple,
WSABUF* sourceInfoControlBuffer,
PSOCKADDR_IN6 targetAddress)
{
DWORD len;
char buffer[4096];
WSABUF buf;
WSAMSG msg;
buf.buf = buffer;
buf.len = sizeof(buffer);
msg.name = NULL;
msg.namelen = 0;
msg.lpBuffers = &buf;
msg.dwBufferCount = 1;
msg.Control.buf = NULL;
msg.Control.len = 0;
msg.dwFlags = 0;
if (WSARecvMsg(tuple->ipv4Socket, &msg, &len, NULL, NULL) == SOCKET_ERROR) {
printf("WSARecvMsg() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
msg.name = (PSOCKADDR)targetAddress;
msg.namelen = sizeof(*targetAddress);
msg.lpBuffers->len = len;
msg.Control = *sourceInfoControlBuffer;
msg.dwFlags = 0;
if (WSASendMsg(tuple->ipv6Socket, &msg, 0, &len, NULL, NULL) == SOCKET_ERROR) {
printf("WSASendMsg() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
return 0;
}
int
ForwardUdpPacketV6toV4(PUDP_TUPLE tuple,
PSOCKADDR_IN targetAddress,
/* Out */ WSABUF* destInfoControlBuffer,
/* Out */ PSOCKADDR_IN6 sourceAddress)
{
DWORD len;
char buffer[4096];
WSABUF buf;
WSAMSG msg;
buf.buf = buffer;
buf.len = sizeof(buffer);
msg.name = (PSOCKADDR)sourceAddress;
msg.namelen = sizeof(*sourceAddress);
msg.lpBuffers = &buf;
msg.dwBufferCount = 1;
msg.Control = *destInfoControlBuffer;
msg.dwFlags = 0;
if (WSARecvMsg(tuple->ipv6Socket, &msg, &len, NULL, NULL) == SOCKET_ERROR) {
printf("WSARecvMsg() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
// IPV6_PKTINFO must be populated
assert(WSA_CMSG_FIRSTHDR(&msg)->cmsg_level == IPPROTO_IPV6);
assert(WSA_CMSG_FIRSTHDR(&msg)->cmsg_type == IPV6_PKTINFO);
// Copy the returned data length back
destInfoControlBuffer->len = msg.Control.len;
msg.name = (PSOCKADDR)targetAddress;
msg.namelen = sizeof(*targetAddress);
msg.lpBuffers->len = len;
msg.Control.buf = NULL;
msg.Control.len = 0;
msg.dwFlags = 0;
if (WSASendMsg(tuple->ipv4Socket, &msg, 0, &len, NULL, NULL) == SOCKET_ERROR) {
printf("WSASendMsg() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
return 0;
}
DWORD
WINAPI
UdpRelayThreadProc(LPVOID Context)
{
PUDP_TUPLE tuple = (PUDP_TUPLE)Context;
fd_set fds;
int err;
SOCKADDR_IN6 lastRemote;
SOCKADDR_IN localTarget;
char lastSourceBuf[1024];
WSABUF lastSource;
printf("UDP relay running for port %d\n", tuple->port);
RtlZeroMemory(&localTarget, sizeof(localTarget));
localTarget.sin_family = AF_INET;
localTarget.sin_port = htons(tuple->port);
localTarget.sin_addr.S_un.S_addr = htonl(INADDR_LOOPBACK);
RtlZeroMemory(&lastRemote, sizeof(lastRemote));
RtlZeroMemory(&lastSource, sizeof(lastSource));
for (;;) {
FD_ZERO(&fds);
FD_SET(tuple->ipv6Socket, &fds);
FD_SET(tuple->ipv4Socket, &fds);
err = select(0, &fds, NULL, NULL, NULL);
if (err <= 0) {
break;
}
else if (FD_ISSET(tuple->ipv6Socket, &fds)) {
// Forwarding incoming IPv6 packets to the IPv4 port
// and storing the source address as our current remote
// target for sending IPv4 data back. Collect the address
// we received the packet on to be able to send from the same
// source when we relay.
lastSource.buf = lastSourceBuf;
lastSource.len = sizeof(lastSourceBuf);
// Don't check for errors to prevent transient issues (like GFE not having started yet)
// from bringing down the whole relay.
ForwardUdpPacketV6toV4(tuple, &localTarget, &lastSource, &lastRemote);
}
else if (FD_ISSET(tuple->ipv4Socket, &fds)) {
// Forwarding incoming IPv4 packets to the last known
// address IPv6 address we've heard from. Pass the destination data
// from the last v6 packet we received to use as the source address.
// Don't check for errors to prevent transient issues (like GFE not having started yet)
// from bringing down the whole relay.
ForwardUdpPacketV4toV6(tuple, &lastSource, &lastRemote);
}
}
closesocket(tuple->ipv6Socket);
closesocket(tuple->ipv4Socket);
free(tuple);
return 0;
}
int StartUdpRelay(unsigned short Port, SOCKET* Ipv4Socket, SOCKET* Ipv6Socket)
{
SOCKET ipv6Socket;
SOCKET ipv4Socket;
SOCKADDR_IN6 addr6;
SOCKADDR_IN addr;
PUDP_TUPLE tuple;
HANDLE thread;
GUID wsaRecvMsgGuid = WSAID_WSARECVMSG;
DWORD bytesReturned;
DWORD val;
ipv6Socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (ipv6Socket == INVALID_SOCKET) {
printf("socket() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
if (WSAIoctl(ipv6Socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &wsaRecvMsgGuid, sizeof(wsaRecvMsgGuid),
&WSARecvMsg, sizeof(WSARecvMsg), &bytesReturned, NULL, NULL) == SOCKET_ERROR) {
printf("WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, WSARecvMsg) failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
// IPV6_PKTINFO is required to ensure that the destination IPv6 address matches the source that
// we send our reply from. If we don't do this, traffic destined to addresses that aren't the default
// outgoing NIC/address will get dropped by the remote party.
val = TRUE;
if (setsockopt(ipv6Socket, IPPROTO_IPV6, IPV6_PKTINFO, (char*)&val, sizeof(val)) == SOCKET_ERROR) {
printf("setsockopt(IPV6_PKTINFO) failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
val = PROTECTION_LEVEL_UNRESTRICTED;
if (setsockopt(ipv6Socket, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (char*)&val, sizeof(val)) == SOCKET_ERROR) {
printf("setsockopt(IPV6_PROTECTION_LEVEL) failed: %d\n", WSAGetLastError());
}
RtlZeroMemory(&addr6, sizeof(addr6));
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(Port);
if (bind(ipv6Socket, (PSOCKADDR)&addr6, sizeof(addr6)) == SOCKET_ERROR) {
printf("bind() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
ipv4Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (ipv4Socket == INVALID_SOCKET) {
printf("socket() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
RtlZeroMemory(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.S_un.S_addr = htonl(INADDR_LOOPBACK);
if (bind(ipv4Socket, (PSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR) {
printf("bind() failed: %d\n", WSAGetLastError());
return WSAGetLastError();
}
tuple = (PUDP_TUPLE)malloc(sizeof(*tuple));
if (tuple == NULL) {
return ERROR_OUTOFMEMORY;
}
tuple->ipv4Socket = *Ipv4Socket = ipv4Socket;
tuple->ipv6Socket = *Ipv6Socket = ipv6Socket;
tuple->port = Port;
thread = CreateThread(NULL, 0, UdpRelayThreadProc, tuple, 0, NULL);
if (thread == NULL) {
printf("CreateThread() failed: %d\n", GetLastError());
return GetLastError();
}
CloseHandle(thread);
return 0;
}
void NETIOAPI_API_ IpInterfaceChangeNotificationCallback(PVOID context, PMIB_IPINTERFACE_ROW, MIB_NOTIFICATION_TYPE)
{
SetEvent((HANDLE)context);
}
void UPnPCreatePinholeForPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const char* myAddr, int port)
{
char uniqueId[8];
char protoStr[3];
char portStr[6];
snprintf(portStr, sizeof(portStr), "%d", port);
snprintf(protoStr, sizeof(protoStr), "%d", proto);
printf("Creating UPnP IPv6 pinhole for %s %s -> %s...", protoStr, portStr, myAddr);
// Lease time is in seconds - 7200 = 2 hours
int err = UPNP_AddPinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, "", "0", myAddr, portStr, protoStr, "7200", uniqueId);
if (err == UPNPCOMMAND_SUCCESS) {
printf("OK\n");
}
else {
printf("ERROR %d (%s)\n", err, strupnperror(err));
}
}
void UPnPCreatePinholesForInterface(struct UPNPUrls* urls, struct IGDdatas* data, const char* tmpAddr)
{
PIP_ADAPTER_ADDRESSES addresses;
PIP_ADAPTER_ADDRESSES currentAdapter;
PIP_ADAPTER_UNICAST_ADDRESS currentAddress;
in6_addr targetAddress;
inet_pton(AF_INET6, tmpAddr, &targetAddress);
// Get a list of all interfaces with IPv6 addresses on the system
addresses = AllocAndGetAdaptersAddresses(AF_INET6,
GAA_FLAG_SKIP_ANYCAST |
GAA_FLAG_SKIP_MULTICAST |
GAA_FLAG_SKIP_DNS_SERVER |
GAA_FLAG_SKIP_FRIENDLY_NAME);
if (addresses == NULL) {
return;
}
currentAdapter = addresses;
currentAddress = nullptr;
while (currentAdapter != nullptr) {
// First, search for the adapter
currentAddress = currentAdapter->FirstUnicastAddress;
while (currentAddress != nullptr) {
assert(currentAddress->Address.lpSockaddr->sa_family == AF_INET6);
PSOCKADDR_IN6 currentAddrV6 = (PSOCKADDR_IN6)currentAddress->Address.lpSockaddr;
if (RtlEqualMemory(&currentAddrV6->sin6_addr, &targetAddress, sizeof(targetAddress))) {
// Found interface with matching address
break;
}
currentAddress = currentAddress->Next;
}
if (currentAddress != nullptr) {
// Get out of the loop if we found the matching address
break;
}
currentAdapter = currentAdapter->Next;
}
if (currentAdapter == nullptr) {
printf("No adapter found with IPv6 address: %s\n", tmpAddr);
goto Exit;
}
// Now currentAdapter is the adapter we reached the IGD with. Create pinholes for all
// public IPv6 addresses on this interface using this IGD.
currentAddress = currentAdapter->FirstUnicastAddress;
while (currentAddress != nullptr) {
assert(currentAddress->Address.lpSockaddr->sa_family == AF_INET6);
PSOCKADDR_IN6 currentAddrV6 = (PSOCKADDR_IN6)currentAddress->Address.lpSockaddr;
// Exclude link-local and privacy addresses
if (currentAddrV6->sin6_scope_id == 0 && currentAddress->SuffixOrigin != IpSuffixOriginRandom) {
char currentAddrStr[128] = {};
inet_ntop(AF_INET6, &currentAddrV6->sin6_addr, currentAddrStr, sizeof(currentAddrStr));
for (int i = 0; i < ARRAYSIZE(TCP_PORTS); i++) {
UPnPCreatePinholeForPort(urls, data, IPPROTO_TCP, currentAddrStr, TCP_PORTS[i]);
}
for (int i = 0; i < ARRAYSIZE(UDP_PORTS); i++) {
UPnPCreatePinholeForPort(urls, data, IPPROTO_UDP, currentAddrStr, UDP_PORTS[i]);
}
}
currentAddress = currentAddress->Next;
}
Exit:
free(addresses);
return;
}
void UpdateUpnpPinholes()
{
int upnpErr;
struct UPNPUrls urls;
struct IGDdatas data;
char localAddress[128];
char ipv6WanAddr[128] = {};
struct UPNPDev* ipv6Devs = upnpDiscoverAll(5000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 1, 2, &upnpErr);
printf("UPnP IPv6 IGD discovery completed with error code: %d\n", upnpErr);
int ret = UPNP_GetValidIGD(ipv6Devs, &urls, &data, localAddress, sizeof(localAddress));
if (ret == 0) {
printf("No UPnP device found!\n");
freeUPNPDevlist(ipv6Devs);
return;
}
else if (ret == 3) {
printf("No UPnP IGD found!\n");
FreeUPNPUrls(&urls);
freeUPNPDevlist(ipv6Devs);
return;
}
else if (ret == 1) {
printf("Found a connected UPnP IGD\n");
}
else if (ret == 2) {
printf("Found a disconnected UPnP IGD (!)\n");
}
else {
printf("UPNP_GetValidIGD() failed: %d\n", ret);
freeUPNPDevlist(ipv6Devs);
return;
}
// Don't try IPv6FC without a control URL
if (data.IPv6FC.controlurl[0] != 0) {
int firewallEnabled, pinholeAllowed;
// Check if this firewall supports IPv6 pinholes
ret = UPNP_GetFirewallStatus(urls.controlURL_6FC, data.IPv6FC.servicetype, &firewallEnabled, &pinholeAllowed);
if (ret == UPNPCOMMAND_SUCCESS) {
printf("UPnP IPv6 firewall control available. Firewall is %s, pinhole is %s\n",
firewallEnabled ? "enabled" : "disabled",
pinholeAllowed ? "allowed" : "disallowed");
if (pinholeAllowed) {
// If the IGD supports IPv6 pinholes, create them for all IPv6 addresses on this interface
UPnPCreatePinholesForInterface(&urls, &data, localAddress);
}
}
else {
printf("UPnP IPv6 firewall control is unavailable with error %d (%s)\n", ret, strupnperror(ret));
}
}
else {
printf("IPv6 firewall control not supported by UPnP IGD!\n");
}
FreeUPNPUrls(&urls);
freeUPNPDevlist(ipv6Devs);
}
void UpdatePcpPinholes()
{
PIP_ADAPTER_ADDRESSES addresses;
PIP_ADAPTER_ADDRESSES currentAdapter;
PIP_ADAPTER_UNICAST_ADDRESS currentAddress;
// Get all IPv6 interfaces
addresses = AllocAndGetAdaptersAddresses(AF_INET6,
GAA_FLAG_SKIP_ANYCAST |
GAA_FLAG_SKIP_MULTICAST |
GAA_FLAG_SKIP_DNS_SERVER |
GAA_FLAG_SKIP_FRIENDLY_NAME |
GAA_FLAG_INCLUDE_GATEWAYS);
if (addresses == NULL) {
return;
}
currentAdapter = addresses;
while (currentAdapter != NULL) {
// Skip over interfaces with no gateway
if (currentAdapter->FirstGatewayAddress == NULL) {
currentAdapter = currentAdapter->Next;
continue;
}
PSOCKADDR_IN6 gatewayAddrV6 = (PSOCKADDR_IN6)currentAdapter->FirstGatewayAddress->Address.lpSockaddr;
char addressStr[128];
inet_ntop(AF_INET6, &gatewayAddrV6->sin6_addr, addressStr, sizeof(addressStr));
printf("Using PCP server: %s%%%d\n", addressStr, gatewayAddrV6->sin6_scope_id);
// Create pinholes for all IPv6 GUAs
currentAddress = currentAdapter->FirstUnicastAddress;
while (currentAddress != NULL) {
assert(currentAddress->Address.lpSockaddr->sa_family == AF_INET6);
PSOCKADDR_IN6 currentAddrV6 = (PSOCKADDR_IN6)currentAddress->Address.lpSockaddr;
// Exclude link-local and privacy addresses
if (currentAddrV6->sin6_scope_id == 0 && currentAddress->SuffixOrigin != IpSuffixOriginRandom) {
inet_ntop(AF_INET6, &currentAddrV6->sin6_addr, addressStr, sizeof(addressStr));
printf("Updating PCP mappings for address %s\n", addressStr);
for (int i = 0; i < ARRAYSIZE(TCP_PORTS); i++) {
PCPMapPort(
(PSOCKADDR_STORAGE)currentAddrV6,
currentAddress->Address.iSockaddrLength,
(PSOCKADDR_STORAGE)currentAdapter->FirstGatewayAddress->Address.lpSockaddr,
currentAdapter->FirstGatewayAddress->Address.iSockaddrLength,
IPPROTO_TCP,
TCP_PORTS[i],
true,
false);
}
for (int i = 0; i < ARRAYSIZE(UDP_PORTS); i++) {
PCPMapPort(
(PSOCKADDR_STORAGE)currentAddrV6,
currentAddress->Address.iSockaddrLength,
(PSOCKADDR_STORAGE)currentAdapter->FirstGatewayAddress->Address.lpSockaddr,
currentAdapter->FirstGatewayAddress->Address.iSockaddrLength,
IPPROTO_UDP,
UDP_PORTS[i],
true,
false);
}
}
currentAddress = currentAddress->Next;
}
currentAdapter = currentAdapter->Next;
}
free(addresses);
}
void ResetLogFile(bool standaloneExe)
{
char timeString[MAX_PATH + 1] = {};
SYSTEMTIME time;
if (!standaloneExe) {
char oldLogFilePath[MAX_PATH + 1];
char currentLogFilePath[MAX_PATH + 1];
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\GSv6Fwd-old.log", oldLogFilePath, sizeof(oldLogFilePath));
ExpandEnvironmentStringsA("%ProgramData%\\MISS\\GSv6Fwd-current.log", currentLogFilePath, sizeof(currentLogFilePath));
// Close the existing stdout handle. This is important because otherwise
// it may still be open as stdout when we try to MoveFileEx below.
fclose(stdout);
// Rotate the current to the old log file
MoveFileExA(currentLogFilePath, oldLogFilePath, MOVEFILE_REPLACE_EXISTING);
// Redirect stdout to this new file
if (freopen(currentLogFilePath, "w", stdout) == NULL) {
// If we couldn't create a log file, just redirect stdout to NUL.
// We have to open _something_ or printf() will crash.
freopen("NUL", "w", stdout);
}
}
// Print a log header
printf("IPv6 Forwarder for GameStream v" VER_VERSION_STR "\n");
// Print the current time
GetSystemTime(&time);
GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &time, "hh':'mm':'ss tt", timeString, ARRAYSIZE(timeString));
printf("The current UTC time is: %s\n", timeString);
}
bool IsGameStreamEnabled()
{
DWORD error;
DWORD enabled;
DWORD len;
HKEY key;
error = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\NVIDIA Corporation\\NvStream", 0, KEY_READ | KEY_WOW64_64KEY, &key);
if (error != ERROR_SUCCESS) {
printf("RegOpenKeyEx() failed: %d\n", error);
return false;
}
len = sizeof(enabled);
error = RegQueryValueExA(key, "EnableStreaming", nullptr, nullptr, (LPBYTE)&enabled, &len);
RegCloseKey(key);
if (error != ERROR_SUCCESS) {
printf("RegQueryValueExA() failed: %d\n", error);
return false;
}
return enabled != 0;
}
DWORD WINAPI GameStreamStateChangeThread(PVOID Context)
{
HKEY key;
DWORD err;
do {
// We're watching this key that way we can still detect GameStream turning on
// if GFE wasn't even installed when our service started
do {
err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\NVIDIA Corporation", 0, KEY_READ | KEY_WOW64_64KEY, &key);
if (err != ERROR_SUCCESS) {
// Wait 10 seconds and try again
Sleep(10000);
}
} while (err != ERROR_SUCCESS);
// Notify the main thread when the GameStream state changes
bool lastGameStreamState = IsGameStreamEnabled();
while ((err = RegNotifyChangeKeyValue(key, true, REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET, nullptr, false)) == ERROR_SUCCESS) {
bool currentGameStreamState = IsGameStreamEnabled();
if (lastGameStreamState != currentGameStreamState) {
SetEvent((HANDLE)Context);
}
lastGameStreamState = currentGameStreamState;
}
// If the key is deleted (by DDU or similar), we will hit this code path and poll until it comes back.
RegCloseKey(key);
} while (err == ERROR_KEY_DELETED);
return err;
}
void StartRelay(SOCKET* tcpSockets, SOCKET* udpSockets) {
int err;
for (int i = 0; i < ARRAYSIZE(TCP_PORTS); i++) {
err = StartTcpRelay(TCP_PORTS[i], &tcpSockets[i]);
if (err != 0) {
printf("Failed to start relay on TCP %d: %d\n", TCP_PORTS[i], err);
tcpSockets[i] = INVALID_SOCKET;
}
}
for (int i = 0; i < ARRAYSIZE(UDP_PORTS); i++) {
err = StartUdpRelay(UDP_PORTS[i], &udpSockets[i * 2], &udpSockets[i * 2 + 1]);
if (err != 0) {
printf("Failed to start relay on UDP %d: %d\n", UDP_PORTS[i], err);
udpSockets[i * 2] = udpSockets[i * 2 + 1] = INVALID_SOCKET;
}
}
}
int Run(bool standaloneExe)
{
int err;
WSADATA data;
ResetLogFile(standaloneExe);
HANDLE ifaceChangeEvent = CreateEvent(nullptr, true, false, nullptr);
HANDLE gameStreamStateChangeEvent = CreateEvent(nullptr, true, false, nullptr);
err = WSAStartup(MAKEWORD(2, 0), &data);
if (err == SOCKET_ERROR) {
printf("WSAStartup() failed: %d\n", err);
return err;
}
// Watch for IPv6 address and interface changes
HANDLE ifaceChangeHandle;
NotifyIpInterfaceChange(AF_INET6, IpInterfaceChangeNotificationCallback, ifaceChangeEvent, false, &ifaceChangeHandle);
// Watch for GameStream state changes
HANDLE stateChangeThread = CreateThread(NULL, 0, GameStreamStateChangeThread, gameStreamStateChangeEvent, 0, NULL);
if (stateChangeThread == NULL) {
printf("CreateThread() failed: %d\n", GetLastError());
return GetLastError();
}
CloseHandle(stateChangeThread);
// Ensure we get adequate CPU time even when the PC is heavily loaded
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
// Keep track of TCP and UDP sockets for the relays. UDP requires 2 sockets per port (1 for v4 and 1 for v6).
SOCKET tcpSockets[ARRAYSIZE(TCP_PORTS)] = {};
SOCKET udpSockets[ARRAYSIZE(UDP_PORTS) * 2] = {};
// Start the relays if GameStream is on
if (IsGameStreamEnabled()) {
StartRelay(tcpSockets, udpSockets);
}
for (;;) {
ResetEvent(ifaceChangeEvent);
// Update pinholes if GameStream is enabled
if (IsGameStreamEnabled()) {
printf("GameStream is enabled\n");
UpdatePcpPinholes();
UpdateUpnpPinholes();
}
else {
printf("GameStream is disabled\n");
}
printf("Going to sleep...\n");
fflush(stdout);
HANDLE objects[] = { ifaceChangeEvent, gameStreamStateChangeEvent };
switch (WaitForMultipleObjects(2, objects, FALSE, 120 * 1000)) {
case WAIT_OBJECT_0:
// Interface state changed. Update pinholes immediately.
break;
case WAIT_OBJECT_0 + 1:
// GameStream state changed
ResetEvent(gameStreamStateChangeEvent);
// Shutdown all relay sockets to trigger the relay threads to terminate.
// The relay threads will call closesocket().
for (int i = 0; i < ARRAYSIZE(tcpSockets); i++) {
if (tcpSockets[i] != NULL && tcpSockets[i] != INVALID_SOCKET) {
shutdown(tcpSockets[i], SD_BOTH);
CancelIoEx((HANDLE)tcpSockets[i], NULL);
tcpSockets[i] = INVALID_SOCKET;
}
}
for (int i = 0; i < ARRAYSIZE(udpSockets); i++) {
if (udpSockets[i] != NULL && udpSockets[i] != INVALID_SOCKET) {
shutdown(udpSockets[i], SD_BOTH);
CancelIoEx((HANDLE)udpSockets[i], NULL);
udpSockets[i] = INVALID_SOCKET;
}
}
// If GameStream is now enabled, start the relay
if (IsGameStreamEnabled()) {
StartRelay(tcpSockets, udpSockets);
}
else {
printf("Stopped relay after GameStream was disabled\n");
}
break;
case WAIT_TIMEOUT:
// Time to refresh the pinholes
break;
case WAIT_FAILED:
return -1;
}
ResetLogFile(standaloneExe);
}
return 0;
}
static SERVICE_STATUS_HANDLE ServiceStatusHandle;
static SERVICE_STATUS ServiceStatus;
DWORD
WINAPI
HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
switch (dwControl)
{
case SERVICE_CONTROL_INTERROGATE:
return NO_ERROR;
case SERVICE_CONTROL_STOP:
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
return NO_ERROR;
default:
return NO_ERROR;
}
}
VOID
WINAPI
ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
int err;
ServiceStatusHandle = RegisterServiceCtrlHandlerEx(SERVICE_NAME, HandlerEx, NULL);
if (ServiceStatusHandle == NULL) {
printf("RegisterServiceCtrlHandlerEx() failed: %d\n", GetLastError());
return;
}
ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwWin32ExitCode = NO_ERROR;
ServiceStatus.dwWaitHint = 0;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
ServiceStatus.dwCheckPoint = 0;
// Tell SCM we're running
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
// Start the relay
err = Run(false);
if (err != 0) {
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = err;
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
return;
}
}
static const SERVICE_TABLE_ENTRY ServiceTable[] = {
{ SERVICE_NAME, ServiceMain },
{ NULL, NULL }
};
int main(int argc, char* argv[])
{
if (argc == 2 && !strcmp(argv[1], "exe")) {
Run(true);
return 0;
}
return StartServiceCtrlDispatcher(ServiceTable);
}