From 05413a554c7c2bf38ee9afeb7b3876a4a7eef517 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 10 Aug 2020 21:16:21 -0700 Subject: [PATCH] Work around IGDs that deduplicate entries based on the internal port This is a violation of the UPnP IGD specification but we can relay through an alternate port as a workaround. --- miss/miss.cpp | 23 ++++++++ miss/miss.vcxproj | 2 + miss/miss.vcxproj.filters | 6 ++ miss/relay.cpp | 112 ++++++++++++++++++++++++++++++++++++++ miss/relay.h | 5 ++ 5 files changed, 148 insertions(+) create mode 100644 miss/relay.cpp create mode 100644 miss/relay.h diff --git a/miss/miss.cpp b/miss/miss.cpp index b79bdcd..d0f8810 100644 --- a/miss/miss.cpp +++ b/miss/miss.cpp @@ -12,6 +12,7 @@ #include #include +#include "relay.h" #include "..\version.h" #pragma comment(lib, "miniupnpc.lib") @@ -205,6 +206,21 @@ bool UPnPMapPort(struct UPNPUrls* urls, struct IGDdatas* data, int proto, const else if (indefinite) { printf("STATIC "); } + if (err == 718 && proto == IPPROTO_UDP) { // ConflictInMappingEntry + // Some UPnP implementations incorrectly deduplicate on the internal port instead + // of the external port, in violation of the UPnP IGD specification. Since GFE creates + // mappings on the same internal port as us, those routers break our mappings. To + // work around this issue, we run relays for each of the UDP ports on an alternate + // internal port. We'll try the alternate port if we get a conflict for a UDP entry. + // Given that these are already horribly non-spec compliant, we won't take any chances + // and we'll use an indefinite mapping too. + char altPortStr[6]; + snprintf(altPortStr, sizeof(altPortStr), "%d", port + RELAY_PORT_OFFSET); + err = UPNP_AddPortMapping( + urls->controlURL, data->first.servicetype, portStr, + altPortStr, myAddr, myDesc, protoStr, nullptr, "0"); + printf("ALTERNATE "); + } if (err == UPNPCOMMAND_SUCCESS) { printf("OK" NL); return true; @@ -852,6 +868,13 @@ int Run() ResetLogFile(); + // Create the UDP alternate port relays + for (int i = 0; i < ARRAYSIZE(k_Ports); i++) { + if (k_Ports[i].proto == IPPROTO_UDP) { + StartUdpRelay(k_Ports[i].port); + } + } + // Create the thread to watch for GameStream state changes CreateThread(nullptr, 0, GameStreamStateChangeThread, gsChangeEvent, 0, nullptr); diff --git a/miss/miss.vcxproj b/miss/miss.vcxproj index 7e8a9dc..f1969ce 100644 --- a/miss/miss.vcxproj +++ b/miss/miss.vcxproj @@ -163,6 +163,7 @@ + @@ -170,6 +171,7 @@ + diff --git a/miss/miss.vcxproj.filters b/miss/miss.vcxproj.filters index 8af3ea8..6c800ad 100644 --- a/miss/miss.vcxproj.filters +++ b/miss/miss.vcxproj.filters @@ -24,6 +24,9 @@ Source Files + + Source Files + @@ -34,5 +37,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/miss/relay.cpp b/miss/relay.cpp new file mode 100644 index 0000000..7b79fa0 --- /dev/null +++ b/miss/relay.cpp @@ -0,0 +1,112 @@ +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include + +#include +#include + +#include "relay.h" + +typedef struct _UDP_TUPLE { + SOCKET socket; + unsigned short port; +} UDP_TUPLE, *PUDP_TUPLE; + +DWORD +WINAPI +UdpRelayThreadProc(LPVOID Context) +{ + PUDP_TUPLE tuple = (PUDP_TUPLE)Context; + fd_set fds; + int err; + SOCKADDR_IN lastRemoteAddr; + + RtlZeroMemory(&lastRemoteAddr, sizeof(lastRemoteAddr)); + + for (;;) { + char buffer[4096]; + SOCKADDR_IN sourceAddr; + int sourceAddrLen; + + FD_ZERO(&fds); + + FD_SET(tuple->socket, &fds); + + err = select(0, &fds, NULL, NULL, NULL); + if (err <= 0) { + break; + } + + sourceAddrLen = sizeof(sourceAddr); + err = recvfrom(tuple->socket, buffer, sizeof(buffer), 0, (PSOCKADDR)&sourceAddr, &sourceAddrLen); + if (err == SOCKET_ERROR) { + continue; + } + + SOCKADDR_IN destinationAddr; + if (RtlEqualMemory(&sourceAddr.sin_addr, &in4addr_loopback, sizeof(sourceAddr.sin_addr))) { + // Traffic incoming from loopback interface - send it to the last remote address + destinationAddr = lastRemoteAddr; + } + else { + // Traffic incoming from the remote host - remember the source + lastRemoteAddr = sourceAddr; + + // Send it to the normal port via the loopback adapter + destinationAddr = sourceAddr; + destinationAddr.sin_addr = in4addr_loopback; + destinationAddr.sin_port = htons(tuple->port); + } + + sendto(tuple->socket, buffer, err, 0, (PSOCKADDR)&destinationAddr, sizeof(destinationAddr)); + } + + closesocket(tuple->socket); + free(tuple); + return 0; +} + +int StartUdpRelay(unsigned short Port) +{ + SOCKET sock; + SOCKADDR_IN addr; + HANDLE thread; + PUDP_TUPLE tuple; + + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == INVALID_SOCKET) { + printf("socket() failed: %d\n", WSAGetLastError()); + return WSAGetLastError(); + } + + // Bind to the alternate port + RtlZeroMemory(&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(Port + RELAY_PORT_OFFSET); + if (bind(sock, (PSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR) { + printf("bind() failed: %d\n", WSAGetLastError()); + closesocket(sock); + return WSAGetLastError(); + } + + tuple = (PUDP_TUPLE)malloc(sizeof(*tuple)); + if (tuple == NULL) { + return ERROR_OUTOFMEMORY; + } + + tuple->socket = sock; + tuple->port = Port; + + thread = CreateThread(NULL, 0, UdpRelayThreadProc, tuple, 0, NULL); + if (thread == NULL) { + printf("CreateThread() failed: %d\n", GetLastError()); + closesocket(sock); + return GetLastError(); + } + + CloseHandle(thread); + + return 0; +} \ No newline at end of file diff --git a/miss/relay.h b/miss/relay.h new file mode 100644 index 0000000..37d94fe --- /dev/null +++ b/miss/relay.h @@ -0,0 +1,5 @@ +#pragma once + +#define RELAY_PORT_OFFSET -10000 + +int StartUdpRelay(unsigned short Port); \ No newline at end of file