Безопасность на грани: Откройте секрет получения внешнего IP-адреса через Stun на С++!

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Введение
В этой статье мы рассмотрим пример кода на C++, который использует библиотеку Winsock для выполнения STUN-теста. STUN (Session Traversal Utilities for NAT) - это протокол, который позволяет определить наличие и тип NAT (Network Address Translation) между вашим устройством и сервером. Это полезно для настройки соединений в сетях, использующих NAT.

Подготовка к работе
Для начала мы подключаем необходимые заголовочные файлы:

C++: Скопировать в буфер обмена
Код:
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>

Также, мы указываем компилятору использовать библиотеку `ws2_32.lib`, которая содержит функции Winsock:

C++: Скопировать в буфер обмена
#pragma comment(lib, "ws2_32.lib")

Основной код
Мы начинаем с определения макросов для обработки ошибок:

C++: Скопировать в буфер обмена
Код:
#define REPORT_ERROR(msg) \
    do { \
        std::cerr << msg << std::endl; \
        return; \
    } while (0)

// Другие макросы для обработки ошибок

Далее, у нас есть структура `stunPack`, представляющая STUN-пакет:

C++: Скопировать в буфер обмена
Код:
struct stunPack {
    uint16_t MessageType;
    uint16_t MessageLength;
    uint32_t MessageCookie;
    uint8_t MessageTransactionID[12];
};

Мы также имеем функции `BEbytes`, которые выполняют перекодировку чисел в сетевой порядок байт:

C++: Скопировать в буфер обмена
Код:
void BEbytes(void* buffer, uint16_t value);
void BEbytes(void* buffer, uint32_t value);

Функция `StunRequest` выполняет запрос к STUN-серверу:

C++: Скопировать в буфер обмена
void StunRequest(const std::string& server, SOCKET sock);

Функция `xorAddr` используется для обработки ответа от STUN-сервера и извлечения IP-адреса и порта:

C++: Скопировать в буфер обмена
void xorAddr(const uint8_t* b, std::string& result);

Основная функция
В функции `StunTest` мы инициализируем Winsock и создаем сокет:

C++: Скопировать в буфер обмена
Код:
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
    REPORT_ERROR("Failed to initialize Winsock");
}

SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock == INVALID_SOCKET) {
    REPORT_ERROR("Failed to create socket");
    WSACleanup();
    return;
}

Затем мы выполняем запрос к STUN-серверу (в данном случае, "stun.l.google.com") и ожидаем ответ:

C++: Скопировать в буфер обмена
Код:
StunRequest("stun.l.google.com", sock);

char buffer[1024];
while (true) {
    int bytesReceived = recv(sock, buffer, sizeof(buffer), 0);
    if (bytesReceived == SOCKET_ERROR) {
        REPORT_ERROR("Error receiving data");
    }

    if (bytesReceived >= 6) {
        std::string s;
        xorAddr(reinterpret_cast<uint8_t*>(buffer + bytesReceived - 6), s);
        std::cout << s << std::endl;
    }
}

Заключительные шаги включают закрытие сокета и очистку Winsock:

C++: Скопировать в буфер обмена
Код:
closesocket(sock);
WSACleanup();

Весь код
C++: Скопировать в буфер обмена
Код:
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>

#pragma comment(lib, "ws2_32.lib")

#define REPORT_ERROR(msg) \
    do { \
        std::cerr << msg << std::endl; \
        return; \
    } while (0)

#define REPORT_ERROR_NULL(msg) \
    do { \
        std::cerr << msg << std::endl; \
        return nullptr; \
    } while (0)

#define REPORT_ERROR_EMPTY_STRING(msg) \
    do { \
        std::cerr << msg << std::endl; \
        return ""; \
    } while (0)

#define REPORT_ERROR_NEGATIVE_INT(msg) \
    do { \
        std::cerr << msg << std::endl; \
        return -1; \
    } while (0)

struct stunPack {
    uint16_t MessageType;
    uint16_t MessageLength;
    uint32_t MessageCookie;
    uint8_t MessageTransactionID[12];
};

void BEbytes(void* buffer, uint16_t value) {
    *(uint16_t*)buffer = htons(value);
}

void BEbytes(void* buffer, uint32_t value) {
    *(uint32_t*)buffer = htonl(value);
}

void StunRequest(const std::string& server, SOCKET sock) {
    addrinfo hints, * result = nullptr;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_DGRAM;

    if (getaddrinfo(server.c_str(), "19302", &hints, &result) != 0) {
        REPORT_ERROR("Failed to resolve server address");
    }

    stunPack sp;
    sp.MessageType = htons(1);
    sp.MessageLength = htons(0);
    sp.MessageCookie = htonl(0x2112A442);
    uint8_t transactionID[12] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
    memcpy(sp.MessageTransactionID, transactionID, sizeof(transactionID));

    if (sendto(sock, (const char*)&sp, sizeof(sp), 0, result->ai_addr, (int)result->ai_addrlen) == SOCKET_ERROR) {
        REPORT_ERROR("Failed to send data");
    }

    freeaddrinfo(result);
}
void xorAddr(const uint8_t* b, std::string& result) {
    uint16_t port = ntohs(*(uint16_t*)b);
    port ^= 0x2112;

    struct in_addr addr;
    addr.S_un.S_un_b.s_b1 = b[2] ^ 0x21;
    addr.S_un.S_un_b.s_b2 = b[3] ^ 0x12;
    addr.S_un.S_un_b.s_b3 = b[4] ^ 0xa4;
    addr.S_un.S_un_b.s_b4 = b[5] ^ 0x42;

    char ipAddress[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &addr, ipAddress, INET_ADDRSTRLEN) == nullptr) {
        REPORT_ERROR("Failed to convert IP address");
    }

    result = ipAddress + std::string(":") + std::to_string(port);
}

void StunTest() {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        REPORT_ERROR("Failed to initialize Winsock");
    }

    SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock == INVALID_SOCKET) {
        REPORT_ERROR("Failed to create socket");
        WSACleanup();
        return;
    }

    StunRequest("stun.l.google.com", sock);
    //StunRequest("stun1.l.google.com", sock);
    //StunRequest("stun2.l.google.com", sock);
    //StunRequest("stun3.l.google.com", sock);

    char buffer[1024];
    while (true) {
        int bytesReceived = recv(sock, buffer, sizeof(buffer), 0);
        if (bytesReceived == SOCKET_ERROR) {
            REPORT_ERROR("Error receiving data");
        }

        if (bytesReceived >= 6) {
            std::string s;
            xorAddr(reinterpret_cast<uint8_t*>(buffer + bytesReceived - 6), s);
            std::cout << s << std::endl;
        }
    }

    closesocket(sock);
    WSACleanup();
}

int main() {
    StunTest();
    return 0;
}

Завершение
Этот пример кода демонстрирует, как использовать Winsock для выполнения STUN-теста и извлечения IP-адреса и порта. Вы можете адаптировать этот код для своих нужд и использовать его в своих проектах, связанных с сетевой коммуникацией.
 
Сверху Снизу