mirror of
https://github.com/moonlight-stream/Internet-Hosting-Tool.git
synced 2025-07-03 08:15:40 +00:00
Initial PCP implementation (not fully tested)
This commit is contained in:
parent
c47014887a
commit
3906de1a3a
@ -162,6 +162,7 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="miss.cpp" />
|
||||
<ClCompile Include="pcp.cpp" />
|
||||
<ClCompile Include="tracer.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -21,6 +21,9 @@
|
||||
<ClCompile Include="tracer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pcp.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="miss.rc">
|
||||
|
262
miss/pcp.cpp
Normal file
262
miss/pcp.cpp
Normal file
@ -0,0 +1,262 @@
|
||||
#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>
|
||||
|
||||
#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;
|
||||
|
||||
int mappingNonce[3];
|
||||
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;
|
||||
|
||||
int mappingNonce[3];
|
||||
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 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)
|
||||
{
|
||||
SOCKET sock;
|
||||
PCP_MAP_REQUEST reqMsg;
|
||||
int reqMsgLen;
|
||||
int i;
|
||||
int bytesRead;
|
||||
union {
|
||||
PCP_MAP_RESPONSE hdr;
|
||||
char buf[1024];
|
||||
} resp;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// This is safe for SOCKADDR_IN and SOCKADDR_IN6
|
||||
((PSOCKADDR_IN)localAddr)->sin_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(enable ? 3600 : 0);
|
||||
populateAddressFromSockAddr(localAddr, reqMsg.hdr.localAddress);
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(reqMsg.mappingNonce); i++) {
|
||||
reqMsg.mappingNonce[i] = rand();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
bytesRead = SOCKET_ERROR;
|
||||
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 from PCP server\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 11: // CANNOT_PROVIDE_EXTERNAL
|
||||
printf("CONFLICT\n");
|
||||
break;
|
||||
default:
|
||||
printf("PCP request failed: %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
|
||||
reqMsg.hdr.lifetime = 0;
|
||||
reqMsg.externalPort = resp.hdr.externalPort;
|
||||
for (int i = 0; i < ARRAYSIZE(reqMsg.mappingNonce); i++) {
|
||||
reqMsg.mappingNonce[i] = rand();
|
||||
}
|
||||
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\n");
|
||||
}
|
||||
else {
|
||||
printf("DELETED\n");
|
||||
}
|
||||
|
||||
closesocket(sock);
|
||||
return true;
|
||||
|
||||
fail:
|
||||
closesocket(sock);
|
||||
return false;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user