Created
March 18, 2025 21:45
-
-
Save twist84/1e0d4b8f21b63239da3b47e297a40b18 to your computer and use it in GitHub Desktop.
C++ Port Mapper Written With Grok AI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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