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".