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