基于 py 的网络拓扑
实验内容
实验内容一
运行给定网络拓扑(tcp_topo.py)
在节点 h1 上执行 TCP 程序
- 执行脚本(disable_tcp_rst.sh, disable_offloading.sh),禁止协议栈的相应功能
- 在 h1 上运行 TCP 协议栈的服务器模式 (./tcp_stack server 10001)
在节点 h2 上执行 TCP 程序
执行脚本(disable_tcp_rst.sh, disable_offloading.sh),禁止协议栈的相应功能
在 h2 上运行 TCP 协议栈的客户端模式,连接 h1 并正确收发数据 (./tcp_stack client 10.0.0.1 10001)
- client 向 server 发送数据,server 将数据 echo 给 client
使用 tcp_stack.py 替换其中任意一端,对端都能正确收发数据
实验内容二
修改 tcp_apps.c(以及 tcp_stack.py),使之能够收发文件
执行 create_randfile.sh,生成待传输数据文件 client-input.dat
运行给定网络拓扑(tcp_topo.py)
在节点 h1 上执行 TCP 程序
- 执行脚本(disable_tcp_rst.sh, disable_offloading.sh),禁止协议栈的相应功能
- 在 h1 上运行 TCP 协议栈的服务器模式 (./tcp_stack server 10001)
在节点 h2 上执行 TCP 程序
执行脚本(disable_tcp_rst.sh, disable_offloading.sh),禁止协议栈的相应功能
在 h2 上运行 TCP 协议栈的客户端模式 (./tcp_stack client 10.0.0.1 10001)
- Client 发送文件 client-input.dat 给 server,server 将收到的数据存储到文件 server-output.dat
使用 md5sum 比较两个文件是否完全相同
使用 tcp_stack.py 替换其中任意一端,对端都能正确收发数据
设计思路
实现数据传输
tcp_sock_read
函数
负责接收 TCP 数据,若 ring buffer 为空,则睡眠,当收到数据包时则被唤醒。具体实现如下:
int tcp_sock_read(struct tcp_sock *tsk, char *buf, int len) {
while (ring_buffer_empty(tsk->rcv_buf)) {
sleep_on(tsk->wait_recv);
}
int rlen = read_ring_buffer(tsk->rcv_buf, buf, len);
wake_up(tsk->wait_recv);
return rlen;
}
}
handle_recv_data
函数
该函数负责接收 TCP 数据包中的数据,根据 ACK 的值添加进 ring buffer。另外,若 ring buffer 为满,则睡眠。具体实现如下:
void handle_recv_data(struct tcp_sock *tsk, struct tcp_cb *cb) {
while (ring_buffer_full(tsk->rcv_buf)) {
sleep_on(tsk->wait_recv);
}
write_ring_buffer(tsk->rcv_buf, cb->payload, cb->pl_len);
wake_up(tsk->wait_recv);
tsk->rcv_nxt = cb->seq + cb->pl_len;
tsk->snd_una = cb->ack;
tcp_send_control_packet(tsk, TCP_ACK);
}
更新后的 tcp_process
函数
相较于上周的实验,本周的实验需要对该函数进行补充,以支持接收 TCP 数据包。更新部分的代码如下:
if (tsk->state == TCP_ESTABLISHED) {
if (tcp->flags & TCP_FIN) {
tcp_set_state(tsk, TCP_CLOSE_WAIT);
tsk->rcv_nxt = cb->seq + 1;
tcp_send_control_packet(tsk, TCP_ACK);
} else if (tcp->flags & TCP_ACK) {
if (cb->pl_len == 0) {
tsk->snd_una = cb->ack;
tsk->rcv_nxt = cb->seq + 1;
tcp_update_window_safe(tsk, cb);
} else {
handle_recv_data(tsk, cb);
}
}
}
tcp_sock_write
函数
负责发送 TCP 数据包。如果对端 recv_window 允许,则发送数据,每次读取 1 个数据包大小的数据,即 min(data_len, 1514 - ETHER_HDR_SIZE - IP_HDR_SIZE - TCP_HDR_SIZE)
。然后封装数据包,通过 IP 层发送函数,将数据包发出去。具体实现如下:
int tcp_sock_write(struct tcp_sock *tsk, char *buf, int len) {
int single_len = 0;
int init_seq = tsk->snd_una;
int init_len = len;
while (len > 1514 - ETHER_HDR_SIZE - IP_BASE_HDR_SIZE - TCP_BASE_HDR_SIZE) {
single_len = min(len, 1514 - ETHER_HDR_SIZE - IP_BASE_HDR_SIZE - TCP_BASE_HDR_SIZE);
send_data(tsk, buf + (tsk->snd_una - init_seq), single_len);
sleep_on(tsk->wait_send);
len -= single_len;
}
send_data(tsk, buf + (tsk->snd_una - init_seq), len);
return init_len;
}
其中调用的 send_data
函数如下,负责调用 tcp_send_packet 函数发送数据包:
void send_data(struct tcp_sock *tsk, char *buf, int len) {
int send_packet_len = ETHER_HDR_SIZE + IP_BASE_HDR_SIZE + TCP_BASE_HDR_SIZE + len;
char * packet = (char *)malloc(send_packet_len);
memcpy(packet + ETHER_HDR_SIZE + IP_BASE_HDR_SIZE + TCP_BASE_HDR_SIZE, buf, len);
tsk->snd_wnd = len;
tcp_send_packet(tsk, packet, send_packet_len);
}
结果验证
实验一结果
实验结果如下:
上图可知,可知本次实验结果符合预期。每次能 echo 正确的值。
另外,若 h2 用 tcp_stack.py 运行 client,h1 不管是运行本设计的 server 还是利用用 tcp_stack.py 运行 server,结果都是 echo 出整个字符串“0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”,结果也符合预期。
实验二结果
实验结果如下:
上图可知,可知本次实验结果符合预期,客户端发送的文件与服务器端接受的文件一致。