Как создать необработанный пакет UDP на C++?

Я программирую код, который отправляет текстовое сообщение на определенный порт с помощью UDP. Я сам инициализирую заголовок IP и UDP. Моя проблема в том, что готовый пакет UDP поврежден, и Wireshark неправильно читает текстовые данные.

Позвольте мне показать вам мой код:

#include <iostream>
#include <string>
#include <cstring>
#include <winsock2.h>
#include <ws2tcpip.h>  // Include this for inet_pton function

#pragma comment(lib, "ws2_32.lib") // Link with ws2_32.lib for Winsock functions

unsigned short packetID = 1;

#define SRCPRT 1234
#define DSTPRT 4321
#define BUFFER 4096



struct IPHeader {
    unsigned char  version_ihl;         
    unsigned char  tos;                  // Type of Service
    unsigned short total_length;         // Total length of the packet (IP header + UDP header + data)
    unsigned short id;                   //Unique identification of the packet
    unsigned short flags_fragment_offset;
    unsigned char  ttl;                  // Time to Live 
    unsigned char  protocol;           
    unsigned short checksum;             //Checksum of the IP header
    unsigned int   src_ip;               //Source IP address
    unsigned int   dst_ip;               //Destination IP address
};

struct UDPHeader {
    unsigned short src_port;  
    unsigned short dst_port;  
    unsigned short length;    
    unsigned short checksum;  //Checksum for UDP
};


struct Packet 
{
    IPHeader ipHeader;
    UDPHeader udpHeader;
    char data[BUFFER - sizeof(IPHeader) - sizeof(UDPHeader)];
};


// Function to calculate the checksum of a buffer (used for IP (psuedo) and UDP header)
unsigned short calculateChecksum(unsigned short* buffer, size_t size)
{
    unsigned long checksum = 0;
    // Add each 16 bit word in the buffer to the checksum
    while (size > 1)
    {
        checksum += *buffer;   // Add the current 16-bit word to the checksum
        buffer++;              // Move to the next 16-bit word
        size -= sizeof(unsigned short); // Decrease size by 2 bytes
    }

    // If there's byte left, add it
    if (size == 1)
    {
        checksum += *(unsigned char*)buffer;  //Add the last byte
    }

    // Handle carry; if the checksum is greater than 16 bits (0xFFFF), we have overflow
    while (checksum >> 16)
    {
        checksum = (checksum & 0xFFFF) + (checksum >> 16);  // Add the overflow bits to the lower 16 bits
    }

    //Flip all the bits
    return (unsigned short)(~checksum);
}

int main()
{
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        std::cerr << "WSAStartup failed" << std::endl;
        return 1;
    }

    SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
    if (sock == INVALID_SOCKET)
    {
        // Socket creation failed probably cause of admin privilges. run the prigram as admin.
        std::cerr << "Socket creation failed: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    sockaddr_in localAddr;
    localAddr.sin_family = AF_INET;
    localAddr.sin_port = htons(SRCPRT); // Local source port
    localAddr.sin_addr.s_addr = INADDR_ANY; // Bind to any local address

    if (bind(sock, (sockaddr*)&localAddr, sizeof(localAddr)) == SOCKET_ERROR)
    {
        std::cerr << "Bind failed: " << WSAGetLastError() << std::endl;
        closesocket(sock);
        WSACleanup();
        return 1;
    }

    // Destination address structure
    sockaddr_in dest;
    dest.sin_family = AF_INET;
    dest.sin_port = DSTPRT; 

    //Use inet_pton to convert IP address string to numeric format
    if (inet_pton(AF_INET, "127.0.0.1", &dest.sin_addr) <= 0) 
    {
        std::cerr << "Invalid addres" << std::endl;
        closesocket(sock);
        WSACleanup();
        return 1;
    }

    // Loop for getting user input and sending packets
    while (true)
    {
        // Get text input from the user
        std::cout << "Enter text to send (or 'exit' to quit): ";
        std::string input;
        std::getline(std::cin, input);

        // Exit the loop
        if (input == "exit") break;

        // Convert input string to C-style string 
        const char* data = input.c_str();
        size_t data_len = strlen(data); // Use size_t for length

        // Set up the IP header
        IPHeader ipHeader;
        ipHeader.version_ihl = (4 << 4) | (5); // Correct IPv4 version and header length
        ipHeader.tos = 0; // Type of Service
        ipHeader.total_length = htons(sizeof(IPHeader) + sizeof(UDPHeader) + data_len); // Total length of the packet
        ipHeader.id = htons(packetID); // Packet ID should be unique (not neccesary tho, i made it unique for fun yay)
        ipHeader.flags_fragment_offset = 0; // No fragmentation (our program is small)
        ipHeader.ttl = 255; // Maximum TTL
        ipHeader.protocol = IPPROTO_UDP; // UDP protocol
        ipHeader.checksum = 0; // Will be calculated later

        // Use inet_pton to convert IP address string to numeric format
        if (inet_pton(AF_INET, "127.0.0.1", &ipHeader.src_ip) <= 0)
        {
            std::cerr << "Invalid source address" << std::endl;
            closesocket(sock);
            WSACleanup();
            return 1;
        }

        ipHeader.dst_ip = dest.sin_addr.s_addr; //Destination IP address

        // UDP header
        UDPHeader udpHeader;
        // Set the source and destination ports in the UDP header
        udpHeader.src_port = htons(SRCPRT); 
        udpHeader.dst_port = htons(DSTPRT); 

        udpHeader.length = htons(sizeof(UDPHeader) + data_len); 
        udpHeader.checksum = 0; // Checksum,  set to 0 , calc later




        char packet[BUFFER];
        memset(packet, 0, BUFFER);

        // Calculate the length of the UDP segment (header + data)
        uint16_t udp_len = sizeof(UDPHeader) + data_len;

        // Set IP header
        ipHeader.total_length = htons(sizeof(IPHeader) + udp_len);
        ipHeader.id = htons(10);  // or increment for each packet
        ipHeader.checksum = 0;    // Initially set to 0 for checksum calculation

        // Copy IP header to packet
        memcpy(packet, &ipHeader, sizeof(IPHeader));

        // Set UDP header
        udpHeader.length = htons(udp_len);
        udpHeader.checksum = 0;   // Set to 0 (optional, as it's often not used)

        // Copy UDP header to packet
        memcpy(packet + sizeof(IPHeader), &udpHeader, sizeof(UDPHeader));

        // Copy data to packet
        memcpy(packet + sizeof(IPHeader) + sizeof(UDPHeader), data, data_len);

        // Calculate the checksum for the IP header
        ipHeader.checksum = calculateChecksum((unsigned short*)packet, sizeof(IPHeader));

        // Update the packet with the correct IP checksum
        memcpy(packet, &ipHeader, sizeof(IPHeader));

        // Before sending, print the IP and UDP headers
        std::cout << "IP ID: " << ntohs(ipHeader.id) << std::endl;
        std::cout << "IP Total Length: " << ntohs(ipHeader.total_length) << std::endl;
        std::cout << "UDP Length: " << ntohs(udpHeader.length) << std::endl;

        for (size_t i = 0; i < data_len; ++i)
        {
            printf("Data byte %zu: %02x\n", i, (unsigned char)data[i]);
        }


       

        // Send the packet
        int result = sendto(sock, packet, ntohs(ipHeader.total_length), 0, (sockaddr*)&dest, sizeof(dest));

        if (result == SOCKET_ERROR) {
            std::cerr << "Send failed: " << WSAGetLastError() << std::endl;
        }
        else {
            std::cout << "Packet sent successfully, bytes sent: " << result << std::endl;
        }

    }

    // Close the socket and clean up Winsock resources
    closesocket(sock);
    WSACleanup();

    return 0;
}

Если пользователь вводит "hi", пакет выглядит в шестнадцатеричном формате следующим образом:

0000   02 00 00 00 45 00 00 32 ed af 00 00 80 11 00 00
0010   7f 00 00 01 7f 00 00 01 45 00 00 1e 00 0a 00 00
0020   ff 11 bd c2 7f 00 00 01 7f 00 00 01 04 d2 10 e1
0030   00 0a 00 00 68 69

ОБРАТИТЕ ВНИМАНИЕ, что 68-69 — это текст "hi"!!!

Вот что показывает Wireshark:

Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1
    0100 .... = Version: 4
    .... 0101 = Header Length: 20 bytes (5)
    Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
    Total Length: 50
    Identification: 0xedaf (60847)
    Flags: 0x0000
    Fragment offset: 0
    Time to live: 128
    Protocol: UDP (17)
    Header checksum: 0x0000 [validation disabled]
    [Header checksum status: Unverified]
    Source: 127.0.0.1
    Destination: 127.0.0.1
User Datagram Protocol, Src Port: 17664, Dst Port: 30
Data (2 bytes)
    Data: ff11
    [Length: 2]

Посмотрите, какие данные ff11???

Я попытался изменить способ построения пакета, но это не привело к каким-либо изменениям. Я ожидал, что Wireshark распознает данные "hi", но он продолжает показывать, что это "ff11".

на какую целевую машину вы пытаетесь отправить данные? Потому что я не уверен, что вам нужно создавать заголовок IP или контрольную сумму, если вы отправляете из приложения Windows в приложение Windows.

Valdez 21.08.2024 21:30

Wireshark показывает пакет в шестнадцатеричном формате рядом с информацией о пакете. IIRC, вы можете нажать на любую часть информации о пакете, и Wireshark выделит эту часть шестнадцатеричного кода. Совпадает ли оно с вашими ожиданиями? Посмотрите, порт UDP тоже неправильный.

user20574 21.08.2024 21:34

@user20574 user20574, шестнадцатеричные значения в Wireshark верны, но Wireshark считывает «ff11» как данные, когда данными являются «6869». и я не могу понять почему. данные должны быть «привет»

ni1 21.08.2024 21:35

@ni1 Нажмите на ff11. Выделяет ли он 6869 в шестнадцатеричном виде или что-то еще? Могу поспорить, что он выделяет ff11 в шестнадцатеричном представлении.

user20574 21.08.2024 21:35

@Valdez, я создал программу-приемник. это простой код, который получает сообщение и печатает его на консоли. я просто хочу создать необработанный сокет udp, который будет работать правильно.

ni1 21.08.2024 21:36

@user20574 user20574 ff11 является частью пакета. это чуть раньше 6869.

ni1 21.08.2024 21:36

Это часть пакета с данными (согласно Wireshark, который всегда прав). Какая часть должна была быть?

user20574 21.08.2024 21:38

Обычно, когда я передаю сообщения между двумя компьютерами с сетевыми стеками (Windows в Windows или в Linux), контрольные суммы и заголовки IP обрабатываются автоматически, и все, что мне нужно сделать, это получить количество байтов в полезной нагрузке. Так что, возможно, Wireshark считывает дополнительную информацию, которую вы отправляете как часть полезной нагрузки, и не обязательно интерпретирует ее как заголовки пакетов.

Valdez 21.08.2024 21:38

@ user20574, это должно было быть 6869. посмотрите пример Wireshark, там написано, что данные — это ff11, а я хочу, чтобы данные были «привет», а шестнадцатеричное значение «привет» — 6869.

ni1 21.08.2024 21:39

@Valdez, возможно, ты прав, но я создаю пакет RAW udp, поэтому я создам его сам, включая контрольную сумму :))

ni1 21.08.2024 21:39

@ni1 хорошо, попробуйте удалить определенные вами заголовки пакетов и посмотрите, работает ли это лучше, если нет, вам придется зайти в сетевые драйверы и посмотреть, можете ли вы отключить создание информации заголовка для вас

Valdez 21.08.2024 21:41

@Valdez подожди, они по-прежнему автоматически создают для меня заголовки? Можете ли вы объяснить, в чем, по вашему мнению, проблема?

ni1 21.08.2024 21:49

@ni1 Я предполагаю, что сетевой стек Windows принимает созданные вами заголовки и обрабатывает их как часть полезной нагрузки, а не фактические заголовки/контрольные суммы, которые он будет использовать для передачи этих пакетов. Следовательно, когда вы читаете их в Wireshark, вы получаете все дополнительные данные.

Valdez 21.08.2024 21:52

@Вальдез, святой, ты, наверное, прав, спасибо, я понятия не имею, как это исправить :(((

ni1 21.08.2024 21:52

@ni1 Обычно вы не беспокоитесь о создании этих деталей, если отправляете из Windows или Linux. Вы делаете это только в том случае, если отправляете это из какой-либо встроенной системы, которая не имеет сложного сетевого стека (например, FPGA и т. д.).

Valdez 21.08.2024 21:57

@Valdez, это часть моего проекта по созданию необработанного udp-соединения, поэтому я должен это сделать. его обязательно использовать необработанные сокеты. я использую окна. какие-нибудь рекомендации, чтобы это исправить?

ni1 21.08.2024 22:00

@ni1 Извините, ничем не могу помочь

Valdez 21.08.2024 22:02

@ni1 Да, но в пакете, куда передаются данные, есть ff11, почему в пакете, куда передаются данные, есть ff11?

user20574 21.08.2024 22:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
18
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что вы отправляете собственный IP-заголовок как часть обычной полезной нагрузки пакета. Вот почему Wireshark неправильно отображает ваш текст.

При использовании сокета RAW необходимо включить опцию сокета IP_HDRINCL, если вы хотите отправить собственный IP-заголовок. В противном случае ОС сгенерирует для вас заголовок.

Согласно документации Microsoft (поскольку вы запускаете этот код в Windows):

Необработанные сокеты TCP/IP

Следующие правила применяются к операциям над сокетами SOCK_RAW:

  • ...

  • При отправке данных IPv4 приложение может выбрать, указывать ли заголовок IPv4 в начале исходящей дейтаграммы пакета. Если для параметра сокета IP_HDRINCL установлено значение true для сокета IPv4 (семейство адресов AF_INET), приложение должно предоставить заголовок IPv4 в исходящих данных для операций отправки. Если этот параметр сокета имеет значение false (настройка по умолчанию), то заголовок IPv4 не должен включаться в исходящие данные для операций отправки.

  • ...

Параметры сокета IPPROTO_IP

Вариант Получать Набор Тип Оптвал Описание IP_HDRINCL да да DWORD (логическое значение) Если установлено значение TRUE, это означает, что приложение предоставляет заголовок IP. Применяется только к сокетам SOCK_RAW. Поставщик услуг TCP/IP может установить поле ID, если значение, предоставленное приложением, равно нулю. Параметр IP_HDRINCL применяется только к протоколу типа SOCK_RAW. Поставщик услуг TCP/IP, поддерживающий SOCK_RAW, должен также поддерживать IP_HDRINCL.

Попробуйте добавить это в свой код после создания сокета и до того, как начнете отправлять данные:

DWORD on = 1;
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&on, sizeof(on));

ты гений, я сейчас это проверю

ni1 21.08.2024 23:08

ты спас мне жизнь, спасибо тебе большое

ni1 21.08.2024 23:10

Следует также отметить, что Windows не допускает пакеты с неверной информацией, например. источник пакета. датаграммы с неправильными источниками будут удалены. Я столкнулся с проблемой, заключающейся в том, что некоторые малоизвестные пакеты программного обеспечения безопасности, используемые в корпоративных средах, также предотвращают создание пользовательских заголовков IPv4.

Swift - Friday Pie 22.08.2024 08:33

Другие вопросы по теме