diff --git a/miss/miss.vcxproj b/miss/miss.vcxproj index 1ead371..c96a0a3 100644 --- a/miss/miss.vcxproj +++ b/miss/miss.vcxproj @@ -162,6 +162,7 @@ + diff --git a/miss/miss.vcxproj.filters b/miss/miss.vcxproj.filters index 802fb3f..8af3ea8 100644 --- a/miss/miss.vcxproj.filters +++ b/miss/miss.vcxproj.filters @@ -21,6 +21,9 @@ Source Files + + Source Files + diff --git a/miss/pcp.cpp b/miss/pcp.cpp new file mode 100644 index 0000000..85449cd --- /dev/null +++ b/miss/pcp.cpp @@ -0,0 +1,262 @@ +#define _CRT_RAND_S +#define _CRT_SECURE_NO_WARNINGS +#include + +#define WIN32_LEAN_AND_MEAN +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include +#include +#include + +#include +#include + +#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; +} \ No newline at end of file