I am trying to send a TCP SYN packet through a RAW socket in C (I know it's not the easiest thing to do but I have academic reasons to do so).
When I check the outgoing packet, every fields are good but one: the TCP checksum. Indeed, Wireshark tells me it is incorrect. In the following code, you can see how I build the packet (the IP headers are omitted because they seem to be OK).
#define MINIMUM_TCP_HEADER_LENGTH 20
#define DEFAULT_TCP_WINDOW 32767
// [... Code that sets the IP headers ...]
struct tcphdr * tcpHeaders = (struct tcphdr *) (packetBuffer + IPHeaderLength);
tcpHeaders->source = htons(srcPort_16);
tcpHeaders->dest = htons(dstPort_16);
tcpHeaders->seq = htonl(tcpSeq_32);
tcpHeaders->ack_seq = 0;
tcpHeaders->doff = (MINIMUM_TCP_HEADER_LENGTH / 4);
tcpHeaders->res1 = 0;
tcpHeaders->res2 = 0;
tcpHeaders->syn = 1;
tcpHeaders->ack = 0;
tcpHeaders->fin = 0;
tcpHeaders->psh = 0;
tcpHeaders->urg = 0;
tcpHeaders->rst = 0;
tcpHeaders->window = htons(DEFAULT_TCP_WINDOW);
tcpHeaders->check = 0x0;
tcpHeaders->urg_ptr = 0x0;
//Sets the data
uint8_t * tcdData = ((uint8_t*) tcpHeaders + MINIMUM_TCP_HEADER_LENGTH);
memcpy(tcdData, message, strlen(message));
//Compute TCP checksum over the pseudo TCP header
uint8_t * pseudo = pseudoBuffer;
memset(pseudo, 0, DEFAULT_BUFFER_SIZE);
memcpy(pseudo, &((ipHeaders->ip_src).s_addr), 4);
pseudo += 4;
memcpy(pseudo, &((ipHeaders->ip_dst).s_addr), 4);
pseudo += 4;
memset(pseudo++, 0, 1);
memset(pseudo++, IPPROTO_TCP, 1);
uint16_t tcpSegmentLength = htons(MINIMUM_TCP_HEADER_LENGTH + (uint32_t) strlen(message));
memcpy(pseudo, &tcpSegmentLength, 2);
pseudo += 2;
//Append the TCP headers and data to the pseudo header
memcpy(pseudo, tcpHeaders, MINIMUM_TCP_HEADER_LENGTH + strlen(attentionMessage));
pseudo += MINIMUM_TCP_HEADER_LENGTH + strlen(attentionMessage);
//Compute checksum
int pseudoBufferLength = pseudo - pseudoBuffer;
tcpHeaders->check = calculateInternetChecksum((uint16_t*) pseudoBuffer, pseudoBufferLength);
//[... Code that proceed to send the packet ...]
It is worth noting that both "packetBuffer" have been filled with zeros using memset (just like "pseudo" is) and that the "message" is a regular string.
Here is the function that computes the checksum:
uint16_t onesComplementAddition(uint16_t *buff, unsigned int nbytes) {
if(buff == 0 || nbytes == 0)
return 0;
uint32_t sum = 0;
for (; nbytes > 1; nbytes -= 2)
sum += *buff++;
if (nbytes == 1)
sum += *(uint8_t*) buff;
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
return sum;
}
uint16_t calculateInternetChecksum(uint16_t *buff, unsigned int nbytes) {
return ~(onesComplementAddition(buff, nbytes));
}
Wireshark suggests that this could be caused by "TCP checksum offload" but I doubt it as I don't receive any response from the machine I probe (even though I know for a fact that I should).
Does anyone have an idea why the TCP checksum is not correct?
Thanks in advance.
Sorry guys, it's one of those times when asking the question actually gives me the answer.
If I understood the documentation correctly, when using a RAW socket, the kernel will replace the IP source address with the address of the sending interface only if the "source" field is 0 (http://man7.org/linux/man-pages/man7/raw.7.html#DESCRIPTION).
As I was using that feature, I computed my checksum over a TCP pseudo header with an IP source that was 0. That could obviously not work.
Thanks if anyone tried to solve my issue.