CS144-Lab2实验笔记

实验简介

这个实验主要完成的是,TCP接受端应该如何工作,才能正确地将TCP Payload交付给socket的问题。

CS144 Lab实现的是这个场景:NIC从链路上收到MAC数据包,交给kernel;kernel解析MAC头后,将这个包传给虚拟的tun设备。CS144 Lab中的其他辅助函数从tun设备中读取IP包,拆包后将完整TCP报文交给tcp_receiver/tcp_sender 这两个我们自己写的程序。

tcp_sender 负责:解析包头、将Payload交付给socket,这样应用程序就能从socket中读取数据了。

实验思路

1. 序列号与绝对序列号的相互转换

对于这个问题不必考虑SYN/FIN是否计算的问题,因为二者都包含了SYN/FIN

1.1 收到的TCP Header中seqno是对端表示其TCP报文内携带的Payload的序列号。

  • 将收到的第一个包(SYN包)携带的seqno称为“起始序列号”,通常是一个32位随机值

  • 之后收到的包的seqno则表示当前包携带的Payload相对于“起始序列号”的偏移

1.2 绝对序列号指的是当前包的seqno相对于起始序列号,实际的偏移

  • 这样就可以知道当前包Payload在乱序字节流里面的起始index了(绝对序列号转到stream index的时候才考虑忽略SYN的问题)

1.3 绝对序列号转换为序列号

  • 绝对序列号加上起始序列号对2^32取模即可

1.4 序列号转换为绝对序列号

(TODO)

2. 实现TCP接收端

2.1 接收端根据收到TCP包头的seqno + SYN_FLAG + FIN_FLAG + Payload 这几个字段,决定本端的ackno + win_size
如下图所示

2.2 根据当前包的seqno得到绝对序列号,再得到stream index

  • 将Payload按照stream index推到乱序字节流中。乱序如何重组为有序的问题Lab1中已经实现了。

2.3 向对端响应ackno

  • ackno 指的是本端期望收到下一个数据包的序列号。也就是已经收到的数据的有序字节长度加1

2.4 向对端响应window_size

  • window_size 指的是本端的接受窗口大小,对端按照收到的窗口大小来填充payload的字节数。具体到实验就是接收端乱序字节流的最大容量减去有序字节流中的字节数(已经重组但未被应用程序读取这部分)。

实现

TCPReceiver类内新增一些变量

1
2
3
4
5
6
7
8
class TCPReceiver {
//! Some TCP Flag received in the first and the last segment from other side
bool _syn{false};
bool _fin{false};
WrappingInt32 _isn{0};

...
}

具体的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//  libsponge/tcp_receiver.cc 
void TCPReceiver::segment_received(const TCPSegment &seg) {
const auto &hdr = seg.header();
const auto &data = seg.payload();
// Not receive SYN yet
if (!_syn) {
if (!hdr.syn) {
return;
} else {
_syn = true;
// the sequence number of the first segment is the initial sequence number
_isn = hdr.seqno;
}
}
// NOW, received SYN from the other side
if (hdr.fin) {
_fin = true;
}
// the checkpoint should be the last absolute sequence number
size_t ckpt = stream_out().bytes_written() + 1;
uint64_t absolute_seqno = unwrap(hdr.seqno, _isn, ckpt);
// In the first segment, the stream index should be 0, or this index should be absolute seqno minus 1
uint64_t stream_idx = absolute_seqno + static_cast<uint64_t>(hdr.syn) - 1;
_reassembler.push_substring(data.copy(), stream_idx, _fin);
}

optional<WrappingInt32> TCPReceiver::ackno() const {
if (!_syn) {
return nullopt;
}
// written bytes + SYN
uint64_t absolute_ackno = stream_out().bytes_written() + 1;
// Only when there is no segment on the fly and receive the FIN flag, then the absolute seq should add 1
absolute_ackno += (_fin && _reassembler.unassembled_bytes() == 0) ? 1 : 0;
return wrap(absolute_ackno, _isn);
}

size_t TCPReceiver::window_size() const {
// the capacity minus the bytes have been reassembled, but not consumed
return this->_capacity - this->stream_out().buffer_size();
}

CS144-Lab2实验笔记
https://gwzlchn.github.io/202205/cs144-lab2/
作者
Zelin Wang
发布于
2022年5月18日
更新于
2022年10月23日
许可协议