0

I am programming a code that sends a text message to a specific port using UDP. I initialize IP header and UDP myself. My problem is that the finished UDP packet is corrupted and Wireshark doesn't read the text data correctly.

Let me show you my code:

#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;
}

If the user types in "hi", this is how the packet looks like in hexadecimal:

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

NOTE that 68-69 is the "hi" text!!!

This is what Wireshark shows:

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]

See how the data is ff11???

I tried changing the way I build the packet, and it resulted in no changes. I expected that Wireshark would recognize the data "hi" but it keeps showing that the data is "ff11".

16
  • 1
    what is the target machine that you are trying to send the data to? Cause I'm not sure that you need to create the IP header or the checksum if you're sending from a windows to windows application Commented Aug 21, 2024 at 19:30
  • @user20574, the hexadeciaml values in wireshark are true, but wireshark reads "ff11" as the data when "6869" is the data. and i cant understand why. the data should be "hi" Commented Aug 21, 2024 at 19:35
  • @Valdez, i built a reciever program. its a simple code that gets the message and prints it on console. i just want to create a raw udp socket which will work correctly. Commented Aug 21, 2024 at 19:36
  • @user20574 ff11 is a part of the packet. its slightly before 6869. Commented Aug 21, 2024 at 19:36
  • It's the data part of the packet (according to Wireshark, which is always right). Which part was it supposed to be? Commented Aug 21, 2024 at 19:38

1 Answer 1

3

The problem is that you are sending your custom IP header as part of the normal packet payload. That is why Wireshark is not showing your text correctly.

When using a RAW socket, you must enable the IP_HDRINCL socket option if you want to send your own IP header. Otherwise, the OS will generate the header for you.

Per Microsoft's documentation (since you are running this code on Windows):

TCP/IP raw sockets

The following rules apply to the operations over SOCK_RAW sockets:

  • ...

  • When sending IPv4 data, an application has a choice on whether to specify the IPv4 header at the front of the outgoing datagram for the packet. If the IP_HDRINCL socket option is set to true for an IPv4 socket (address family of AF_INET), the application must supply the IPv4 header in the outgoing data for send operations. If this socket option is false (the default setting), then the IPv4 header should not be in included the outgoing data for send operations.

  • ...

IPPROTO_IP socket options

Option Get Set Optval type Description
IP_HDRINCL yes yes DWORD (boolean) When set to TRUE, indicates the application provides the IP header. Applies only to SOCK_RAW sockets. The TCP/IP service provider may set the ID field, if the value supplied by the application is zero. The IP_HDRINCL option is applied only to the SOCK_RAW type of protocol. A TCP/IP service provider that supports SOCK_RAW should also support IP_HDRINCL.

Try adding this to your code after you have created the socket and before you start sending data:

DWORD on = 1;
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&on, sizeof(on));
Sign up to request clarification or add additional context in comments.

2 Comments

you are a genius im checking it out now
Should be also noted thatWindows doesn'tallow packets wth incorrect information, e.g. source of packet. datagrmas with wrong sources would be dropped. I ran into an issue that some semi-obscure security software suites used in enterprise environments also prevents creation of custom IPv4 headers,

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.