Skip to content

Instantly share code, notes, and snippets.

@twist84
Created March 18, 2025 21:45
Show Gist options
  • Save twist84/1e0d4b8f21b63239da3b47e297a40b18 to your computer and use it in GitHub Desktop.
Save twist84/1e0d4b8f21b63239da3b47e297a40b18 to your computer and use it in GitHub Desktop.
C++ Port Mapper Written With Grok AI
#include "port_mapper.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#ifdef _WIN32
#pragma comment(lib, "ws2_32.lib")
#else
#include <sys/select.h>
#endif
#define SSDP_PORT 1900
#define SSDP_ADDR "239.255.255.250"
c_port_mapper::c_port_mapper(const char* ip, int int_port, int ext_port, const char* protocol)
{
m_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
#ifdef _WIN32
if (m_sock == INVALID_SOCKET)
{
m_sock = INVALID_SOCKET;
#else
if (m_sock < 0)
{
m_sock = -1;
#endif
return;
}
strcpy_s(m_local_ip, sizeof(m_local_ip), ip);
m_internal_port = int_port;
m_external_port = ext_port;
strncpy_s(m_protocol, sizeof(m_protocol), protocol, 3);
m_protocol[3] = '\0';
// Convert to uppercase
for (int i = 0; m_protocol[i]; i++) {
m_protocol[i] = toupper(m_protocol[i]);
}
}
c_port_mapper::~c_port_mapper()
{
#ifdef _WIN32
if (m_sock != INVALID_SOCKET)
{
closesocket(m_sock);
}
#else
if (m_sock >= 0)
{
close(m_sock);
}
#endif
}
[[nodiscard]] bool c_port_mapper::discover_router(char* control_url, int url_size)
{
#ifdef _WIN32
if (m_sock == INVALID_SOCKET)
#else
if (m_sock < 0)
#endif
{
return false;
}
const char* search_msg =
"M-SEARCH * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"MAN: \"ssdp:discover\"\r\n"
"MX: 2\r\n"
"ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n"
"\r\n";
struct sockaddr_in addr
{
.sin_family = AF_INET,
.sin_port = htons(SSDP_PORT)
};
inet_pton(AF_INET, SSDP_ADDR, &addr.sin_addr);
if (sendto(m_sock, search_msg, strlen(search_msg), 0,
(struct sockaddr*)&addr, sizeof(addr)) < 0)
{
return false;
}
char buffer[BUFFER_SIZE];
fd_set readfds;
struct timeval timeout
{
.tv_sec = 2,
.tv_usec = 0
};
FD_ZERO(&readfds);
FD_SET(m_sock, &readfds);
if (select(m_sock + 1, &readfds, nullptr, nullptr, &timeout) <= 0)
{
return false;
}
int len = recvfrom(m_sock, buffer, BUFFER_SIZE - 1, 0, nullptr, nullptr);
if (len <= 0)
{
return false;
}
buffer[len] = '\0';
const char* location = strstr(buffer, "LOCATION:");
if (location == nullptr)
{
return false;
}
location += 9; // Skip "LOCATION:"
while (*location == ' ')
{
location++;
}
const char* end = strchr(location, '\r');
if (end == nullptr)
{
return false;
}
int url_len = end - location;
if (url_len >= url_size)
{
return false;
}
strncpy_s(control_url, url_size, location, url_len);
control_url[url_len] = '\0';
return true;
}
[[nodiscard]] bool c_port_mapper::add_mapping(const char* control_url)
{
int tcp_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
#ifdef _WIN32
if (tcp_sock == INVALID_SOCKET)
#else
if (tcp_sock < 0)
#endif
{
return false;
}
char host[256];
int port = 80;
const char* path = strchr(control_url + 7, '/'); // Skip "http://"
if (!path)
{
path = "/";
}
int host_len = path - (control_url + 7);
strncpy_s(host, sizeof(host), control_url + 7, host_len);
host[host_len] = '\0';
const char* port_str = strchr(host, ':');
if (port_str)
{
port = atoi(port_str + 1);
host[port_str - host] = '\0';
}
struct sockaddr_in addr
{
.sin_family = AF_INET,
.sin_port = htons(port)
};
inet_pton(AF_INET, host, &addr.sin_addr);
if (connect(tcp_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0)
{
#ifdef _WIN32
closesocket(tcp_sock);
#else
close(tcp_sock);
#endif
return false;
}
char soap_request[BUFFER_SIZE];
snprintf(soap_request, BUFFER_SIZE,
"POST %s HTTP/1.1\r\n"
"HOST: %s:%d\r\n"
"CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n"
"SOAPACTION: \"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping\"\r\n"
"CONTENT-LENGTH: %d\r\n"
"\r\n"
"<?xml version=\"1.0\"?>\r\n"
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n"
"<s:Body>\r\n"
"<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n"
"<NewRemoteHost></NewRemoteHost>\r\n"
"<NewExternalPort>%d</NewExternalPort>\r\n"
"<NewProtocol>%s</NewProtocol>\r\n"
"<NewInternalPort>%d</NewInternalPort>\r\n"
"<NewInternalClient>%s</NewInternalClient>\r\n"
"<NewEnabled>1</NewEnabled>\r\n"
"<NewPortMappingDescription>Port Mapping</NewPortMappingDescription>\r\n"
"<NewLeaseDuration>0</NewLeaseDuration>\r\n"
"</u:AddPortMapping>\r\n"
"</s:Body>\r\n"
"</s:Envelope>\r\n",
path, host, port, strlen(soap_request) - (strstr(soap_request, "<?xml") - soap_request),
m_external_port, m_protocol, m_internal_port, m_local_ip);
if (send(tcp_sock, soap_request, strlen(soap_request), 0) < 0)
{
#ifdef _WIN32
closesocket(tcp_sock);
#else
close(tcp_sock);
#endif
return false;
}
char response[BUFFER_SIZE];
int len = recv(tcp_sock, response, BUFFER_SIZE - 1, 0);
#ifdef _WIN32
closesocket(tcp_sock);
#else
close(tcp_sock);
#endif
if (len <= 0)
{
return false;
}
response[len] = '\0';
return strstr(response, "200 OK") != nullptr;
}
[[nodiscard]] bool c_port_mapper::delete_mapping(const char* control_url)
{
int tcp_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
#ifdef _WIN32
if (tcp_sock == INVALID_SOCKET)
#else
if (tcp_sock < 0)
#endif
{
return false;
}
char host[256];
int port = 80;
const char* path = strchr(control_url + 7, '/'); // Skip "http://"
if (!path)
{
path = "/";
}
int host_len = path - (control_url + 7);
strncpy_s(host, sizeof(host), control_url + 7, host_len);
host[host_len] = '\0';
const char* port_str = strchr(host, ':');
if (port_str)
{
port = atoi(port_str + 1);
host[port_str - host] = '\0';
}
struct sockaddr_in addr
{
.sin_family = AF_INET,
.sin_port = htons(port)
};
inet_pton(AF_INET, host, &addr.sin_addr);
if (connect(tcp_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0)
{
#ifdef _WIN32
closesocket(tcp_sock);
#else
close(tcp_sock);
#endif
return false;
}
char soap_request[BUFFER_SIZE];
snprintf(soap_request, BUFFER_SIZE,
"POST %s HTTP/1.1\r\n"
"HOST: %s:%d\r\n"
"CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n"
"SOAPACTION: \"urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping\"\r\n"
"CONTENT-LENGTH: %d\r\n"
"\r\n"
"<?xml version=\"1.0\"?>\r\n"
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n"
"<s:Body>\r\n"
"<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n"
"<NewRemoteHost></NewRemoteHost>\r\n"
"<NewExternalPort>%d</NewExternalPort>\r\n"
"<NewProtocol>%s</NewProtocol>\r\n"
"</u:DeletePortMapping>\r\n"
"</s:Body>\r\n"
"</s:Envelope>\r\n",
path, host, port, strlen(soap_request) - (strstr(soap_request, "<?xml") - soap_request),
m_external_port, m_protocol);
if (send(tcp_sock, soap_request, strlen(soap_request), 0) < 0)
{
#ifdef _WIN32
closesocket(tcp_sock);
#else
close(tcp_sock);
#endif
return false;
}
char response[BUFFER_SIZE];
int len = recv(tcp_sock, response, BUFFER_SIZE - 1, 0);
#ifdef _WIN32
closesocket(tcp_sock);
#else
close(tcp_sock);
#endif
if (len <= 0)
{
return false;
}
response[len] = '\0';
return strstr(response, "200 OK") != nullptr;
}
c_app_context::c_app_context()
{
m_mapper = nullptr;
m_is_add = false;
m_internal_port = 0;
m_external_port = 0;
strcpy_s(m_protocol, sizeof(m_protocol), "UDP"); // Default to UDP
}
c_app_context::~c_app_context()
{
dispose();
}
[[nodiscard]] bool c_app_context::initialize(int argc, char* argv[])
{
if (argc < 2 || (strcmp(argv[1], "add") != 0 && strcmp(argv[1], "delete") != 0))
{
printf("Usage: %s <add|delete> <local_ip> <internal_port> <external_port> <protocol>\n", argv[0]);
printf("Example (add): %s add 192.168.1.100 11774 11774 UDP\n", argv[0]);
printf("Example (delete): %s delete 192.168.1.100 11774 11774 TCP\n", argv[0]);
return false;
}
m_is_add = strcmp(argv[1], "add") == 0;
if ((m_is_add && argc != 6) || (!m_is_add && argc != 6))
{
printf("Invalid number of arguments\n");
return false;
}
#ifdef _WIN32
if (WSAStartup(MAKEWORD(2, 2), &m_wsa_data) != 0)
{
printf("WSAStartup failed: %d\n", WSAGetLastError());
return false;
}
#endif
m_internal_port = atoi(argv[3]);
m_external_port = atoi(argv[4]);
if (m_internal_port <= 0 || m_internal_port > 65535 ||
m_external_port <= 0 || m_external_port > 65535)
{
printf("Ports must be between 1 and 65535\n");
return false;
}
// Validate protocol
if (stricmp(argv[5], "TCP") != 0 && stricmp(argv[5], "UDP") != 0)
{
printf("Protocol must be either 'TCP' or 'UDP'\n");
return false;
}
strncpy_s(m_protocol, sizeof(m_protocol), argv[5], 3);
m_protocol[3] = '\0';
// Convert to uppercase
for (int i = 0; m_protocol[i]; i++) {
m_protocol[i] = toupper(m_protocol[i]);
}
m_mapper = new c_port_mapper(argv[2], m_internal_port, m_external_port, m_protocol);
if (m_mapper == nullptr)
{
printf("Failed to create port_mapper\n");
return false;
}
return true;
}
void c_app_context::dispose()
{
delete m_mapper;
m_mapper = nullptr;
#ifdef _WIN32
WSACleanup();
#endif
}
int main(int argc, char* argv[])
{
c_app_context context;
if (!context.initialize(argc, argv))
{
context.dispose();
return 1;
}
char control_url[256];
if (!context.get_mapper()->discover_router(control_url, sizeof(control_url)))
{
printf("No UPnP router found\n");
context.dispose();
return 1;
}
printf("Found router at: %s\n", control_url);
if (context.is_add())
{
if (!context.get_mapper()->add_mapping(control_url))
{
printf("Failed to add port mapping\n");
context.dispose();
return 1;
}
printf("Successfully mapped external port %d to internal port %d (%s)\n",
context.get_external_port(), context.get_internal_port(), context.get_protocol());
}
else
{
if (!context.get_mapper()->delete_mapping(control_url))
{
printf("Failed to delete port mapping\n");
context.dispose();
return 1;
}
printf("Successfully deleted mapping for external port %d (%s)\n",
context.get_external_port(), context.get_protocol());
}
context.dispose();
return 0;
}
#ifndef PORT_MAPPER_H
#define PORT_MAPPER_H
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#endif
#define BUFFER_SIZE 1024
class c_port_mapper
{
public:
c_port_mapper(const char* ip, int int_port, int ext_port, const char* protocol);
~c_port_mapper();
[[nodiscard]] bool discover_router(char* control_url, int url_size);
[[nodiscard]] bool add_mapping(const char* control_url);
[[nodiscard]] bool delete_mapping(const char* control_url);
private:
#ifdef _WIN32
SOCKET m_sock;
#else
int m_sock;
#endif
char m_local_ip[16];
int m_internal_port;
int m_external_port;
char m_protocol[4]; // "TCP" or "UDP"
};
class c_app_context
{
public:
c_app_context();
~c_app_context();
[[nodiscard]] bool initialize(int argc, char* argv[]);
void dispose();
[[nodiscard]] c_port_mapper* get_mapper() const { return m_mapper; }
[[nodiscard]] bool is_add() const { return m_is_add; }
[[nodiscard]] int get_internal_port() const { return m_internal_port; }
[[nodiscard]] int get_external_port() const { return m_external_port; }
[[nodiscard]] const char* get_protocol() const { return m_protocol; }
private:
#ifdef _WIN32
WSADATA m_wsa_data;
#endif
c_port_mapper* m_mapper;
bool m_is_add;
int m_internal_port;
int m_external_port;
char m_protocol[4]; // "TCP" or "UDP"
};
#endif // PORT_MAPPER_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment