TCP checksum is incorrect when trying to send packet in C

2.3k views Asked by At

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.

1

There are 1 answers

0
Bert.e On BEST ANSWER

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.