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.
This commit is contained in:
Cameron Gutman 2020-08-10 21:16:21 -07:00
parent 5f015acdaa
commit 05413a554c
5 changed files with 148 additions and 0 deletions

View File

@ -12,6 +12,7 @@
#include <assert.h>
#include <stdlib.h>
#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);

View File

@ -163,6 +163,7 @@
<ItemGroup>
<ClCompile Include="miss.cpp" />
<ClCompile Include="pcp.cpp" />
<ClCompile Include="relay.cpp" />
<ClCompile Include="tracer.cpp" />
</ItemGroup>
<ItemGroup>
@ -170,6 +171,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\version.h" />
<ClInclude Include="relay.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -24,6 +24,9 @@
<ClCompile Include="pcp.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="relay.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="miss.rc">
@ -34,5 +37,8 @@
<ClInclude Include="..\version.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="relay.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

112
miss/relay.cpp Normal file
View File

@ -0,0 +1,112 @@
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <Ws2ipdef.h>
#include "relay.h"
typedef struct _UDP_TUPLE {
SOCKET socket;
unsigned short port;
} UDP_TUPLE, *PUDP_TUPLE;
DWORD
WINAPI
UdpRelayThreadProc(LPVOID Context)
{
PUDP_TUPLE tuple = (PUDP_TUPLE)Context;
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;
}

5
miss/relay.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#define RELAY_PORT_OFFSET -10000
int StartUdpRelay(unsigned short Port);