【计网实验——prj13】网络传输机制实验一

【计网实验——prj13】网络传输机制实验一

实验要求

  • 运行给定网络拓扑(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)
  • 可以在一端用tcp_stack.py替换tcp_stack执行,测试另一端
  • 通过wireshark抓包来来验证建立和关闭连接的正确性

实现方案

  本次实验的主要任务是实现TCP协议栈,分为TCP数据包处理和连接管理两个部分。

1. 连接管理

(1)建立连接

  TCP协议中存在主动建立连接和被动建立连接两种方式,服务器为被动建立连接的一方,客户端为主动建立连接的一方。

被动建立连接

  被动建立连接的过程如下:

	①申请占用一个端口号	(bind操作)
	②监听该端口号	(listen操作)
	③收到SYN数据包,状态切换为 TCP_SYN_RECV	(accept操作)
	④回复ACK并发送SYN数据包
	⑤收到ACK数据包,状态切换为 TCP_ESTABLISHED

  本次实验中需要实现上述过程中的listen和accept操作,对应函数如下:

  • tcp_sock_listen 函数
      设置backlog,切换状态到TCP_LISTEN,并且利用hash_tcp函数把TCP Socket加入到listen_table中。具体实现如下:
// set backlog (the maximum number of pending connection requst), switch the
// TCP_STATE, and hash the tcp sock into listen_table
int tcp_sock_listen(struct tcp_sock *tsk, int backlog)
{
	fprintf(stdout, "TODO: implement %s please.\n", __FUNCTION__);
	tsk->backlog = backlog;
	tcp_set_state(tsk, TCP_LISTEN);
	return tcp_hash(tsk);
}
  • tcp_sock_accept 函数
      若accept_queue非空,取出队列中第一个 tcp socket 并 accept,否则,调用sleep_on函数进行阻塞,等待TCP协议栈完成相应数据包是收发,直到收到ACK后被唤醒。具体实现如下:
// if accept_queue is not emtpy, pop the first tcp sock and accept it,
// otherwise, sleep on the wait_accept for the incoming connection requests
struct tcp_sock *tcp_sock_accept(struct tcp_sock *tsk)
{
	fprintf(stdout, "TODO: implement %s here.\n", __FUNCTION__);
	while (list_empty(&tsk->accept_queue)) {
		sleep_on(tsk->wait_accept);
	}
	struct tcp_sock * pop_stack;
	if ((pop_stack = tcp_sock_accept_dequeue(tsk)) != NULL) {
		pop_stack->state = TCP_ESTABLISHED;
		tcp_hash(pop_stack);
		return pop_stack;
	} else {
		return NULL;
	}
}
主动建立连接

  主动建立连接的过程如下:

	①发送目的端口的SYN数据包,状态切换为 TCP_SYN_SENT	(connect操作)
	②收到SYN 数据包(设置TCP_ACK标志位)
	③回复ACK数据包,状态切换为 TCP_ESTABLISHED

  本次实验中需要实现上述过程中的connect操作,对应函数如下:

  • tcp_sock_connect 函数
  1. 初始化tcp socket的四元组 (sip, sport, dip, dport)
  2. 将tcp socket 按照端口号将 bind_hash_list 节点 hash 到bind_table
  3. 发送 SYN 信号包,切换TCP状态到 TCP_SYN_SENT,通过调用sleep_on函数来等待
    SYN包;
  4. SYN 到达,该线程被唤醒,说明此时连接已经建立完成。具体实现如下:
int tcp_sock_connect(struct tcp_sock *tsk, struct sock_addr *skaddr)
{
	fprintf(stdout, "TODO: implement %s please.\n", __FUNCTION__);
	u16 sport = tcp_get_port();
	if (sport == 0) {
		return -1;
	}
	u32 saddr = longest_prefix_match(ntohl(skaddr->ip))->iface->ip;
	tsk->sk_sip = saddr;
	tsk->sk_sport = sport;
	tsk->sk_dip = ntohl(skaddr->ip);
	tsk->sk_dport = ntohs(skaddr->port);
	tcp_bind_hash(tsk);

	tcp_send_control_packet(tsk, TCP_SYN);
	tcp_set_state(tsk, TCP_SYN_SENT);
	tcp_hash(tsk);
	sleep_on(tsk->wait_connect);
	return sport;	
}
(2)断开连接

  与建立连接相对应的,关闭连接也存在主动关闭和被动关闭两种方式,

主动关闭

  主动关闭连接涉及如下过程:

	①发送FIN包,切换状态为TCP_FIN_WAIT_1               (close操作)
	②收到FIN对应的ACK包,切换状态为TCP_FIN_WAIT_2
	③收到对方发送的FIN包,回复ACK,切换状态为TCP_TIME_WAIT
	④等待2*MSL时间,切换状态为TCP_CLOSED,连接结束(需要实现定时器线程,定期扫描,适时结束TCP_TIME_WAIT状态的流)
被动关闭

  被动关闭连接涉及如下过程:

	①收到FIN包,回复相应ACK,切换状态为TCP_CLOSE_WAIT,还可以发送数据
	②自己没有待发送数据,断开连接,发送FIN包,切换状态为TCP_LAST_ACK         (close操作)
	③收到FIN包对应的ACK,切换状态为TCP_CLOSED,连接结束

  本次实验中需要实现上述过程中的close操作和定时器线程,对应函数如下:

  • tcp_sock_close 函数
      根据不同的TCP状态进行关闭连接操作,即发送FIN/ACK包,并切换到相应的状态。具体实现如下:
// close the tcp sock, by releasing the resources, sending FIN/RST packet
// to the peer, switching TCP_STATE to closed
void tcp_sock_close(struct tcp_sock *tsk)
{
	fprintf(stdout, "TODO: implement %s here.\n", __FUNCTION__);
	if (tsk->state == TCP_CLOSE_WAIT) {
		tcp_send_control_packet(tsk, TCP_FIN|TCP_ACK);
		tcp_set_state(tsk, TCP_LAST_ACK);
	} else if (tsk->state == TCP_ESTABLISHED) {
		tcp_set_state(tsk, TCP_FIN_WAIT_1);
		tcp_send_control_packet(tsk, TCP_FIN|TCP_ACK);
	} else {
		tcp_unhash(tsk);
		tcp_set_state(tsk, TCP_CLOSED);
	}
}
  • tcp_timer_thread 函数
      该函数为定时器线程,定期扫描。若等待超过2*MSL的时间,进入TCP_CLOSED状态,结束TCP_TIME_WAIT状态的流。
void *tcp_timer_thread(void *arg)
{
	init_list_head(&timer_list);
	while (1) {
		usleep(TCP_TIMER_SCAN_INTERVAL);
		struct tcp_timer * time_entry, *time_q;
		list_for_each_entry_safe (time_entry, time_q, &timer_list, list) {
			if (time_entry->enable == 1 && (time(NULL) * TCP_MSL - time_entry->timeout * TCP_MSL > TCP_TIMEWAIT_TIMEOUT)) {
				struct tcp_sock * tsk = timewait_to_tcp_sock(time_entry);
				list_delete_entry(&time_entry->list);
				tcp_set_state(tsk, TCP_CLOSED);
				tcp_bind_unhash(tsk);
			}
		}
	}
	return NULL;
}

2. TCP数据包处理

  节点在收到一个TCP数据包后,会首先检查TCP校验和是否正确,然后进行TCP查找,找到连接相应的socket并返回,之后再根据包的种类和状态机进行数据包的处理。其主要过程通过函数void handle_tcp_packet(char *packet, struct iphdr *ip, struct tcphdr *tcp)实现,如下(框架中已写好):

// handle TCP packet: find the appropriate tcp sock, and let the tcp sock 
// to process the packet.
void handle_tcp_packet(char *packet, struct iphdr *ip, struct tcphdr *tcp)
{
	//检查校验和
	if (tcp_checksum(ip, tcp) != tcp->checksum) {
		log(ERROR, "received tcp packet with invalid checksum, drop it.");
		return ;
	}

	struct tcp_cb cb;
	tcp_cb_init(ip, tcp, &cb);
	//在established_table和listen_table中查找tcp sock
	struct tcp_sock *tsk = tcp_sock_lookup(&cb);
	//根据状态机进行数据包处理
	tcp_process(tsk, &cb, packet);
}

  其中,tcp_sock_lookup函数中调用了tcp_sock_lookup_established函数和tcp_sock_lookup_listen函数实现在established_tablelisten_table中的socket查找。

tcp_sock_lookup_established函数

  查找时首先根据四元组查找已经建立好的的socket,若找到则返回对应的socket。

// lookup tcp sock in established_table with key (saddr, daddr, sport, dport)
struct tcp_sock *tcp_sock_lookup_established(u32 saddr, u32 daddr, u16 sport, u16 dport)
{
	fprintf(stdout, "TODO: implement %s here.\n", __FUNCTION__);

	int hash = tcp_hash_function(saddr, daddr, sport, dport); 
	struct list_head * list = &tcp_established_sock_table[hash];

	struct tcp_sock *tmp;
	list_for_each_entry(tmp, list, hash_list) {
		if (saddr == tmp->sk_sip   && daddr == tmp->sk_dip &&
			sport == tmp->sk_sport && dport == tmp->sk_dport) {
			return tmp;
		}
	}

	return NULL;
}
tcp_sock_lookup_listen函数

  若没有找到已经完成建立的socket,则对处在LISTEN状态下的socket进行查找,若找到则返回对应的socket。

// lookup tcp sock in listen_table with key (sport)
//
// In accordance with BSD socket, saddr is in the argument list, but never used.
struct tcp_sock *tcp_sock_lookup_listen(u32 saddr, u16 sport)
{
	fprintf(stdout, "TODO: implement %s here.\n", __FUNCTION__);

	int hash = tcp_hash_function(0, 0, sport, 0);
	struct list_head * list = &tcp_listen_sock_table[hash];

	struct tcp_sock *tmp;
	list_for_each_entry(tmp, list, hash_list) {
		if (sport == tmp->sk_sport) {
			return tmp;
		}
	}

	return NULL;
}

3. 状态迁移

tcp_process函数

  在完成socket查找后,由该函数根据TCP状态机的不同状态进行数据包处理以及状态切换,具体步骤为:

  • (1)检查是否为RST包,如果是,直接结束连接;
  • (2)检查当前TCP状态以及数据包类型,按照下图虚线部分的内容(与连接管理过程紧密相关,详细描述见上文)进行相应类型的数据包转发以及状态转换:
    在这里插入图片描述
      具体代码实现如下:
// Process the incoming packet according to TCP state machine. 
void tcp_process(struct tcp_sock *tsk, struct tcp_cb *cb, char *packet)
{
	fprintf(stdout, "\nTODO: implement %s here.\n", __FUNCTION__);
	struct tcphdr * tcp = packet_to_tcp_hdr(packet);

	if ((tcp->flags & TCP_RST) == TCP_RST) {
		tcp_sock_close(tsk);
		return;
	}

	if (tsk->state == TCP_LISTEN) {
		if ((tcp->flags & TCP_SYN) == TCP_SYN) {
			tcp_set_state(tsk, TCP_SYN_RECV);
			struct tcp_sock * child = alloc_child_tcp_sock(tsk, cb);
			tcp_send_control_packet(child, TCP_ACK|TCP_SYN);
		}
	}

	if (tsk->state == TCP_SYN_SENT) {
		if ((tcp->flags & TCP_ACK) == TCP_ACK) {
			wake_up(tsk->wait_connect);
			tsk->rcv_nxt = cb->seq + 1;
		    tsk->snd_una = cb->ack;
			tcp_send_control_packet(tsk, TCP_ACK);
			tcp_set_state(tsk, TCP_ESTABLISHED);
		}
	}

	if (tsk->state == TCP_SYN_RECV) {
		if ((tcp->flags & TCP_ACK) == TCP_ACK) {
			if (!tcp_sock_accept_queue_full(tsk)) {
				struct tcp_sock * csk = tcp_sock_listen_dequeue(tsk);
				tcp_sock_accept_enqueue(csk);
				if (!is_tcp_seq_valid(csk,cb)) {
					return;
				}
				csk->rcv_nxt = cb->seq;
		        csk->snd_una = cb->ack;
				tcp_set_state(csk, TCP_ESTABLISHED);
				wake_up(tsk->wait_accept);
			}
		}
	}

	if (!is_tcp_seq_valid(tsk,cb)) {
		return;
	}

	if (tsk->state == TCP_ESTABLISHED) {
		if ((tcp->flags & TCP_FIN) == TCP_FIN) {
			tcp_set_state(tsk, TCP_CLOSE_WAIT);
			tsk->rcv_nxt = cb->seq + 1;
			tcp_send_control_packet(tsk, TCP_ACK);
		} 
	}

	if (tsk->state == TCP_LAST_ACK) {
		if ((tcp->flags & TCP_ACK) == TCP_ACK) {
			tcp_set_state(tsk, TCP_CLOSED);
			tcp_unhash(tsk);
		}
	}

	if (tsk->state == TCP_FIN_WAIT_1) {
		if ((tcp->flags & TCP_ACK) == TCP_ACK) {
			tcp_set_state(tsk, TCP_FIN_WAIT_2);
		}
	}

	if (tsk->state == TCP_FIN_WAIT_2) {
		if ((tcp->flags & TCP_FIN) == TCP_FIN) {
			tsk->rcv_nxt = cb->seq + 1;
			tcp_send_control_packet(tsk, TCP_ACK);
			tcp_set_state(tsk, TCP_TIME_WAIT);
			tcp_set_timewait_timer(tsk);
		}
	}
}

  其中,状态为LISTEN,数据包为SYN时,调用了alloc_child_tcp_sock函数为被动建立连接一方建立一个Child Socket。

运行结果

  结点h1作为服务器,结点h2作为客户端,分别运行TCP程序,wireshark抓包结果如下:

h2运行python tcp_stack.py测试h1

  实验结果如下:

在这里插入图片描述
  抓包结果与状态转移符合预期。

h1运行python tcp_stack.py测试h2

  实验结果如下:

在这里插入图片描述
  抓包结果与状态转移符合预期。

h1和h2同时运行本实验中实现的tcp_stack程序

  实验结果如下:

在这里插入图片描述
  上图中,蓝色部分为本实验中Server和Client的结果,红色部分为标准Server和Client的结果。通过比对可知本次实验结果符合预期。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值