CS144-Lab4实验笔记

实验简介

这个实验主要完成TCP Endpoint的功能,一个TCP端点包含一个TCPSender,和一个TCPReceiver。进行TCP连接的时候,通常是一个TCP端点A向另一个TCP端点B发送“连接建立”请求,经过三次握手后TCP连接建立。之后两个端点间可以相互传送数据。每当一端要发送的字节流到达EOF的时候,向对端发送一个“连接关闭”请求,最后当两个端点均已经发送连接关闭的请求,并且都接到了这个请求的响应后,TCP连接就算关闭了。

TCP状态机

简介中简单描述了下TCP通信的流程,但具体的TCP状态转移需要考虑一些边界情况,可参考下图,

TCP连接的建立(非同时握手)

TCP连接的关闭(非同时关闭)

以上图片均来源于 Linux/UNIX系统编程手册,人民邮电出版社

实验思路

接受报文

TCP连接从网络上接受报文,并调用segment_received函数。当调用发生时,函数应该如下工作:

  1. 如果收到了 RST 报文,则关闭这个连接,并将接受字节流和发送字节流均设为ERROR状态。否则:
  2. 将报文交给TCPReceiver来处理本报文的包头中的seqno/SYN/FIN和数据段
  3. 如果收到了ACK报文(也就是除了第一个SYN报文外的所有正常报文),通知TCPSender更新acknowin_size
  4. 如果当前报文包含数据或者SYN/FIN标志位有效,则至少响应一个报文,以通知对端本端最新的acknowin_size
  5. 如果收到了Keepalive报文,见下。

发送报文

  1. 每当TCPSender将一个TCP报文添加到它的待发送队列中时,TCP连接需要从中取出并将其发送。
  2. 在发送当前数据包之前,TCPConnection 会根据TCPReceiveracknowin_size,将其放置进待发送 TCPSegment 中,并设置报文的 ACK

TCP KeepAlives

KeepAlive是一种周期性检查另一端主机的该TCP连接是否存活的机制。在RFC 1122 中被描述。

KeepAlive报文是一个空的报文段(或者仅包含一个任意字节)。它的序列号等于对端已经ACK过的字节减1: SEG.SEQ = SND.NXT-1,这个序列号对应的数据显然被对方成功接受了,所以不会对对端的接受数据造成影响。如果接受方能正确处理这个KeepAlive报文,则同样返回一个空的ACK报文即可,表明当前TCP连接仍然存活。

KeepAlive报文和其响应报文都不会包含任何新的有效数据,丢失的时候也不会重传。RFC1122同样指出,仅凭一个没有收到响应的KeepAlive报文不能判断对端的TCP连接已经停止工作,所以失败时需要多次发送Keepalive报文。

讲义中给出了处理对端KeepAlive报文的伪算法

1
2
3
4
  if (_receiver.ackno().has_value() and (seg.length_in_sequence_space() == 0)
and seg.header().seqno == _receiver.ackno().value() - 1) {
_sender.send_empty_segment();
}

接受RST报文

有几种情况本端会收到对端的RST报文

  • 本端向对端没有打开的端口发送了SYN报文,会收到RST报文。
  • TCP连接建立后,对端突然崩溃,本端再发送正常报文时,会收到RST报文。
  • TIME-WAIT状态下收到了之前的数据报文,本端正常回复ACK,但对端已经关闭了这个连接,所以会回复一个RST报文(这种情况会导致本端TIME-WAIT状态提前结束,称为TIME-WAIT Assassination,在RFC1337中有所描述
  • 对端主动发出RST报文

本端收到了RST报文后,不需要任何回复,直接关闭这条TCP连接,并将发送/接受的数据流关闭。(实验中是将 ByteStream inbound/outbound设为 ERROR状态)

发送RST报文

上一节已经说明了什么情况下会发送RST报文,但还有以下情况使得一端会主动发出RST报文

  • 本端socket程序收到了程序终止的信号,如SIGINT,直接发送RST终止连接。
  • 实验讲义中说明,如果当前重传次数超过上限,则需要RST报文终止连接。

RST报文的seqno部分必须是正确的(正确指的是在对端的接受窗口内,亦即(RCV.NXT <= SEG.SEQ < RCV.NXT+RCV.WND)这样可以防止TCP重置攻击,见RFC5961

调试心得

  1. TCPConnection起始状态是LISTENactive变量应该为true

    测试函数里面,FSM什么都不执行就是LISTEN态。

    1
    2
    3
    4
    5
    6
    // tests/tcp_expectation.hh
    struct Listen : public TCPAction {
    std::string description() const { return "listen"; }
    void execute(TCPTestHarness &) const {}
    };

    我最开始的实现时候_active设为false,t_ack_rst 这个测试包含一个LISTEN态的比较,死活过不去。

  2. 同时打开

    按照RFC 793的描述,同时打开的情况下,双方第二次握手均应该发送SYN+ACK包,但是测试逻辑这里却希望仅希望收到ACK

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
        TCP A                                            TCP B

    1. CLOSED CLOSED

    2. SYN-SENT --> <SEQ=100><CTL=SYN> ...

    3. SYN-RECEIVED <-- <SEQ=300><CTL=SYN> <-- SYN-SENT

    4. ... <SEQ=100><CTL=SYN> --> SYN-RECEIVED

    5. SYN-RECEIVED --> <SEQ=100><ACK=301><CTL=SYN,ACK> ...

    6. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED

    7. ... <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED

    Simultaneous Connection Synchronization

    Figure 8.

    测试函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    TCPTestHarness test_2(cfg);

    test_2.execute(Connect{});
    test_2.execute(Tick(1));

    TCPSegment seg = test_2.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false),
    "test 2 failed: could not parse SYN segment or invalid flags");
    auto &seg_hdr = seg.header();

    test_2.execute(ExpectState{State::SYN_SENT});

    // send SYN (no ACK yet)
    const WrappingInt32 isn(rd());
    test_2.send_syn(isn);
    test_2.execute(Tick(1));

    test_2.expect_seg(ExpectOneSegment{}.with_syn(false).with_ack(true).with_ackno(isn + 1),
    "test 2 failed: bad ACK for SYN");

    test_2.execute(ExpectState{State::SYN_RCVD});
  3. 调试tun144/145

本地tun144/tun145两张网卡无法通信。tun145发的包tun144收不到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ip a
...
49: tun144: <NO-CARRIER,POINTOPOINT,MULTICAST,NOARP,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 500
link/none
inet 169.254.144.1/24 scope global tun144
valid_lft forever preferred_lft forever
inet6 fe80::40ad:1e69:28e2:988c/64 scope link stable-privacy
valid_lft forever preferred_lft forever
50: tun145: <NO-CARRIER,POINTOPOINT,MULTICAST,NOARP,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 500
link/none
inet 169.254.145.1/24 scope global tun145
valid_lft forever preferred_lft forever
inet6 fe80::8e6c:7fed:e456:8f35/64 scope link stable-privacy
valid_lft forever preferred_lft forever
1
2
3
4
5
6
7
8
9
$ ip r
default via 10.19.0.254 dev eno1 proto static metric 100
default via 10.30.0.254 dev eno3 proto static metric 20102
10.19.0.0/24 dev eno1 proto kernel scope link src 10.19.0.36 metric 100
10.30.0.0/19 dev eno3 proto kernel scope link src 10.30.19.36 metric 102
169.254.0.0/16 dev tun144 scope link metric 1000 linkdown
169.254.144.0/24 dev tun144 scope link linkdown rto_min lock 10ms
169.254.145.0/24 dev tun145 scope link linkdown rto_min lock 10ms
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
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
$ sudo iptables -L -n -t nat   
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
CONNMARK all -- 169.254.144.0/24 0.0.0.0/0 CONNMARK set 0x90
CONNMARK all -- 169.254.145.0/24 0.0.0.0/0 CONNMARK set 0x91

Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:9001
MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:9000
MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 connmark match 0x90
MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 connmark match 0x91

Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:9001 to:172.17.0.2:9001
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:9000 to:172.17.0.2:9000
  1. Debug打印

可以加上TCPConnection状态打印函数,为了避免影响性能,存在DEBUG宏的时候编译,或者传给gcc-DDEBUG参数

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
    class TCPConnectionDebugger {
private:
bool open_debugger{true};

public:
TCPConnectionDebugger() : open_debugger(true) {}
~TCPConnectionDebugger() {}

std::string color_1(const std::string &data) { return data; }

std::string color_2(const std::string &data) { return data; }

void print_segment(const TCPConnection &that,
const TCPSegment &seg,
const std::string &desription,
bool check = true) {
DUMMY_CODE(that, seg, desription, check);
#ifdef DEBUG
std::cerr << "\n" << color_1(desription) << "\n";
std::cerr << (color_2("Flag") + " : ") << (seg.header().syn ? "S" : "") << (seg.header().fin ? "F" : "")
<< (seg.header().ack ? "A" : "") << (seg.header().rst ? "R" : "") << "\n"
<< (color_2("Sequnce Number") + " : ") << (seg.header().seqno.raw_value()) << "\n"
<< (color_2("Acknowledgement Number") + " : ") << (seg.header().ackno) << "\n"
<< (color_2("Window Size") + " : ") << (seg.header().win) << "\n"
<< (color_2("Payload") + " : ") << (seg.payload().size() ? seg.payload().str() : "empty string")
<< "\n"
<< (color_2("Payload Size") + " : ") << (seg.payload().size()) << "\n"
<< (color_2("Sequnce Space") + " : ") << (seg.length_in_sequence_space()) << "\n"
<< (color_2("ackno of sender") + " : ") << (that._sender.next_seqno_absolute()) << "\n"
<< (color_2("next seqno absolute of sender") + " : ") << (that._sender.next_seqno_absolute())
<< "\n";
std::cerr << (color_2("Active: ") + ((that._active) ? "Y " : "N"))
<< (color_2(" TIME_WAIT: ") + ((that._linger_after_streams_finish) ? "Y\n" : "N\n"));
std::cerr << (color_2("Sender State: ")) << that.state().name() << std::endl;
#endif
}
};

性能调优

  1. 第一次实现的性能
    1
    2
    3
    $ ./apps/tcp_benchmark
    CPU-limited throughput : 2.65 Gbit/s
    CPU-limited throughput with reordering: 1.45 Gbit/s

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