diff --git a/GSv6Fwd/GSv6Fwd.vcxproj b/GSv6Fwd/GSv6Fwd.vcxproj
index 4946851..a083722 100644
--- a/GSv6Fwd/GSv6Fwd.vcxproj
+++ b/GSv6Fwd/GSv6Fwd.vcxproj
@@ -148,6 +148,7 @@
+
diff --git a/GSv6Fwd/GSv6Fwd.vcxproj.filters b/GSv6Fwd/GSv6Fwd.vcxproj.filters
index 5b082c6..a8a5a5e 100644
--- a/GSv6Fwd/GSv6Fwd.vcxproj.filters
+++ b/GSv6Fwd/GSv6Fwd.vcxproj.filters
@@ -18,5 +18,8 @@
Source Files
+
+ Source Files
+
\ No newline at end of file
diff --git a/GSv6Fwd/pcp.cpp b/GSv6Fwd/pcp.cpp
new file mode 100644
index 0000000..e774e08
--- /dev/null
+++ b/GSv6Fwd/pcp.cpp
@@ -0,0 +1,296 @@
+#define _CRT_RAND_S
+#define _CRT_SECURE_NO_WARNINGS
+#include
+
+#define WIN32_LEAN_AND_MEAN
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+#include
+#include
+#include
+
+#pragma comment(lib, "Shlwapi.lib")
+#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;
+
+ unsigned char mappingNonce[12];
+ 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;
+
+ unsigned char mappingNonce[12];
+ 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 populateMappingNonce(PPCP_MAP_REQUEST request, PSOCKADDR_STORAGE pcpAddr, int pcpAddrLen)
+{
+ struct {
+ unsigned short port;
+ unsigned char localAddress[16];
+ SOCKADDR_STORAGE targetAddress;
+ } dataToHash;
+
+ assert(request->internalPort != 0);
+
+ dataToHash.port = request->internalPort;
+ memcpy(dataToHash.localAddress, request->hdr.localAddress, sizeof(dataToHash.localAddress));
+ memcpy(&dataToHash.targetAddress, pcpAddr, pcpAddrLen);
+
+ HashData((BYTE*)&dataToHash, 18 + pcpAddrLen, request->mappingNonce, sizeof(request->mappingNonce));
+}
+
+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, bool indefinite)
+{
+ SOCKET sock;
+ PCP_MAP_REQUEST reqMsg;
+ int reqMsgLen;
+ int i;
+ int bytesRead;
+ union {
+ PCP_MAP_RESPONSE hdr;
+ char buf[1024];
+ } resp;
+ int lifetime;
+
+ if (!enable) {
+ lifetime = 0;
+ }
+ else if (indefinite) {
+ lifetime = 604800; // 1 week
+ }
+ else {
+ lifetime = 3600;
+ }
+
+ 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;
+ }
+
+ if (localAddr->ss_family == AF_INET6) {
+ // Make sure we're sourcing from the correct IPv6 address to ensure the port
+ // is opened correctly and that the PCP server doesn't refuse our mapping.
+ ((PSOCKADDR_IN6)localAddr)->sin6_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(lifetime);
+ populateAddressFromSockAddr(localAddr, reqMsg.hdr.localAddress);
+
+ 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);
+ }
+
+ // This must be done after the rest of the message is populated
+ populateMappingNonce(&reqMsg, pcpAddr, pcpAddrLen);
+
+ bytesRead = 0;
+ 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\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 2: // NOT_AUTHORIZED
+ printf("UNAUTHORIZED\n");
+ break;
+ case 11: // CANNOT_PROVIDE_EXTERNAL
+ printf("CONFLICT\n");
+ break;
+ default:
+ printf("ERROR: %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 (with the same nonce)
+ reqMsg.hdr.lifetime = 0;
+ reqMsg.externalPort = resp.hdr.externalPort;
+ 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 (%d seconds remaining)\n", ntohl(resp.hdr.hdr.lifetime));
+ }
+ else {
+ printf("DELETED\n");
+ }
+
+ closesocket(sock);
+ return true;
+
+fail:
+ closesocket(sock);
+ return false;
+}
\ No newline at end of file