tcpnetwork-programmingtun

Getting TCP Retransmission instead of ACK on TUN device


I'm trying to implement a TCP stack over TUN device according to RFC 793 in Linux. By default, my program is in the LISTEN state and is waiting for an SYN packet to establish a connection. I use nc to send an SYN:

$ nc 192.168.20.99 20

My program responds with SYN, ACK, but nc doesn't send an ACK at the end. This is the flow:

# tshark -i tun0 -z flow,tcp,network
1 0.000000000 192.168.20.1 → 192.168.20.99 TCP 60 39284 → 20 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=1691638570 TSecr=0 WS=128
2 0.000112185 192.168.20.99 → 192.168.20.1 TCP 40 20 → 39284 [SYN, ACK] Seq=0 Ack=1 Win=10 Len=0
3 1.001056784 192.168.20.1 → 192.168.20.99 TCP 60 [TCP Retransmission] [TCP Port numbers reused] 39284 → 20 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=1691639571 TSecr=0 WS=128

|Time     | 192.168.20.1                          |
|         |                   | 192.168.20.99     |                   
|0.000000000|         SYN       |                   |Seq = 0
|         |(39284)  ------------------>  (20)     |
|0.000112185|         SYN, ACK  |                   |Seq = 0 Ack = 1
|         |(39284)  <------------------  (20)     |
|1.001056784|         SYN       |                   |Seq = 0
|         |(39284)  ------------------>  (20)     |

More info about my TCP header:

Frame 2: 40 bytes on wire (320 bits), 40 bytes captured (320 bits) on interface tun0, id 0
Raw packet data
Internet Protocol Version 4, Src: 192.168.20.99, Dst: 192.168.20.1
Transmission Control Protocol, Src Port: 20, Dst Port: 39310, Seq: 0, Ack: 1, Len: 0
    Source Port: 20
    Destination Port: 39310
    [Stream index: 0]
    [Conversation completeness: Incomplete, CLIENT_ESTABLISHED (3)]
    [TCP Segment Len: 0]
    Sequence Number: 0    (relative sequence number)
    Sequence Number (raw): 0
    [Next Sequence Number: 1    (relative sequence number)]
    Acknowledgment Number: 1    (relative ack number)
    Acknowledgment number (raw): 645383655
    0101 .... = Header Length: 20 bytes (5)
    Flags: 0x012 (SYN, ACK)
    Window: 10
    [Calculated window size: 10]
    Checksum: 0x99b0 [unverified]
    [Checksum Status: Unverified]
    Urgent Pointer: 0

NOTE: I'm aware of the ISN prediction attack, but this is just a test, and 0 for the sequence number is just as random as any other number in this case.

UPDATE: This is the output of tcpdump which says I'm calculating checksum wrong:

# tcpdump -i tun0 -vv -n
...
 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40, bad cksum 16f3 (->911b)!)
    192.168.20.99.20 > 192.168.20.1.39308: Flags [S.], cksum 0x9bb0 (incorrect -> 0x1822), seq 0, ack 274285560, win 10, length 0
...

Here is my checksum calculator (From RFC 1071):

uint16_t checksum(void *addr, int count)
{
    uint32_t sum = 0;
    uint16_t *ptr = addr;

    while (count > 1) {
        sum += *ptr++;
        count -= 2;
    }

    if (count > 0)
        sum += *(uint8_t *)ptr;

    while (sum >> 16)
        sum = (sum & 0xffff) + (sum >> 16);

    return ~sum;
}

And I'm passing the combination of pseudo-header with the TCP segment for TCP checksum. (in big-endian order):

uint16_t tcp_checksum(struct tcp_header *tcph, uint8_t *pseudo_header)
{
    size_t len = PSEUDO_HEADER_SIZE + (tcph->data_offset * 4);
    uint8_t combination[len];
    memcpy(combination, pseudo_header, PSEUDO_HEADER_SIZE);
    dump_tcp_header(tcph, combination, PSEUDO_HEADER_SIZE);
    return checksum(combination, len / 2);
}

What am I doing wrong here?


Solution

  • Problem solved by calculating checksums via in_cksum.c from tcpdump source code, which is a line-by-line implementation of the RFC 1071. I also had to set IFF_NO_PI for the tun device. For this case, using a tap device instead of a tun device is probably a better choice to handle EtherType.