深入理解TCP/IP协议的实现之三次握手(基于linux1.2.13)

上篇我们分析了accept函数,他是消费者,这篇我们看看生产者是怎么实现的。我们从tcp_rcv函数开始,这个函数是一个分发器。当接收到一个tcp包的时候,底层就会调这个函数交给tcp层处理。

// daddr,saddr是ip头的字段,len为tcp头+数据长度 
int tcp_rcv(
	struct sk_buff *skb, 
	struct device *dev, 
	struct options *opt, 
	unsigned long daddr, 
	unsigned short len, 
	unsigned long saddr, 
	int redo, 
	struct inet_protocol * protocol
){
	
	// tcp报文头
	th = skb->h.th;
	// 从tcp的socket哈希链表中找到对应的socket结构
	sk = get_sock(&tcp_prot, th->dest, saddr, th->source, daddr);
	// 读缓冲区已满,丢弃数据包
	if (sk->rmem_alloc + skb->mem_len >= sk->rcvbuf) 
	{
		kfree_skb(skb, FREE_READ);
		release_sock(sk);
		return(0);
	}
	// 这个就是上篇我们说的skb中关联的sock结构体。
	skb->sk=sk;
	// 增加读缓冲区已使用的内存的大小
	sk->rmem_alloc += skb->mem_len;
	// 连接还没有建立,不是通信数据包
	if(sk->state!=TCP_ESTABLISHED)		
	{
	
		// 是监听socket则可能是一个syn包	
		if(sk->state==TCP_LISTEN)
		{	
			// 不存在这种可能的各种情况,直接丢包			   
			if(th->rst || !th->syn || th->ack || ip_chk_addr(daddr)!=IS_MYADDR)
			{
				kfree_skb(skb, FREE_READ);
				release_sock(sk);
				return 0;
			}
		
			// 是个syn包,建立连接
			tcp_conn_request(sk, skb, daddr, saddr, opt, dev, tcp_init_seq());
		
			release_sock(sk);
			return 0;
		}
	}
}

上面的函数主要的逻辑两个
1 get_sock是根据源ip、端口和目的ip、端口从tcp的sock_array哈希表里找到对应的sock结构体。

/*
	num	目的端口
	raddr 源ip
	rnum 源端口
	laddr 目的ip
*/
struct sock *get_sock(struct proto *prot, unsigned short num,
				unsigned long raddr,
				unsigned short rnum, unsigned long laddr)
{
	struct sock *s;
	struct sock *result = NULL;
	int badness = -1;
	unsigned short hnum;

	hnum = ntohs(num);

	for(s = prot->sock_array[hnum & (SOCK_ARRAY_SIZE - 1)];
			s != NULL; s = s->next) 
	{
		int score = 0;
		// 绑定的端口是否等于报文中的目的端口
		if (s->num != hnum) 
			continue;
		// 该sock已经不用了,下一个
		if(s->dead && (s->state == TCP_CLOSE))
			continue;
		/* local address matches? */
		// 绑定的ip,bind的时候赋值
		if (s->saddr) {
			// 报文中的目的ip是不是等于该socket绑定的ip
			if (s->saddr != laddr)
				continue;
			score++;
		}
		/* remote address matches? */
		// 目的ip
		if (s->daddr) {
			if (s->daddr != raddr)
				continue;
			score++;
		}
		/* remote port matches? */
		// 目的端口
		if (s->dummy_th.dest) {
			if (s->dummy_th.dest != rnum)
				continue;
			score++;
		}
		/* perfect match? */
		// 全匹配,直接返回
		if (score == 3)
			return s;
		/* no, check if this is the best so far.. */
		if (score <= badness)
			continue;
		// 记录最好的匹配项
		result = s;
		badness = score;
  	}
  	return result;
}

对于监听型的socket,我们在bind的时候写入了绑定的ip和端口。对于监听型的socket,是没有目的ip和目的端口的。通信型的socket才有。所以上面的函数根据服务端绑定的ip和端口。判断是否等于tcp报文中的目的ip和端口。最后拿到监听型的sock结构体。
2 tcp_conn_request处理sync包,tcp_conn_request里完成了tcp的第一次和第二次握手。

// 收到一个syn包时的处理 
static void tcp_conn_request(struct sock *sk, struct sk_buff *skb,
		 unsigned long daddr, unsigned long saddr,
		 struct options *opt, struct device *dev, unsigned long seq)
{
	struct sk_buff *buff;
	struct tcphdr *t1;
	unsigned char *ptr;
	struct sock *newsk;
	struct tcphdr *th;
	struct device *ndev=NULL;
	int tmp;
	struct rtable *rt;
	
	th = skb->h.th;
	
	// 过载则丢包,防止ddos,max_ack_backlog即listen的参数
	if (sk->ack_backlog >= sk->max_ack_backlog) 
	{
		tcp_statistics.TcpAttemptFails++;
		kfree_skb(skb, FREE_READ);
		return;
	}

	// 分配一个新的sock结构用于通信。accept的时候返回的就是这个sock结构体
	newsk = (struct sock *) kmalloc(sizeof(struct sock), GFP_ATOMIC);
	// 从listen套接字复制内容,再覆盖某些字段
	memcpy(newsk, sk, sizeof(*newsk));
	skb_queue_head_init(&newsk->write_queue);
	skb_queue_head_init(&newsk->receive_queue);
	newsk->send_head = NULL;
	newsk->send_tail = NULL;
	skb_queue_head_init(&newsk->back_log);
	newsk->rtt = 0;		/*TCP_CONNECT_TIME<<3*/
	newsk->rto = TCP_TIMEOUT_INIT;
	newsk->mdev = 0;
	newsk->max_window = 0;
	newsk->cong_window = 1;
	newsk->cong_count = 0;
	newsk->ssthresh = 0;
	newsk->backoff = 0;
	newsk->blog = 0;
	newsk->intr = 0;
	newsk->proc = 0;
	newsk->done = 0;
	newsk->partial = NULL;
	newsk->pair = NULL;
	newsk->wmem_alloc = 0;
	newsk->rmem_alloc = 0;
	newsk->localroute = sk->localroute;
	newsk->max_unacked = MAX_WINDOW - TCP_WINDOW_DIFF;
	newsk->err = 0;
	newsk->shutdown = 0;
	newsk->ack_backlog = 0;
	// 期待收到的对端下一个字节的序列号
	newsk->acked_seq = skb->h.th->seq+1;
	// 进程可以读但是还没有读取的字节序列号
	newsk->copied_seq = skb->h.th->seq+1;
	// 当收到对端fin包的时候,回复的ack包中的序列号	
	newsk->fin_seq = skb->h.th->seq;
	// 进入syn_recv状态
	newsk->state = TCP_SYN_RECV;
	newsk->timeout = 0;
	newsk->ip_xmit_timeout = 0;
	// 下一个发送的字节的序列号
	newsk->write_seq = seq; 
	// 可发送的字节序列号最大值
	newsk->window_seq = newsk->write_seq;
	// 序列号小于rcv_ack_seq的数据包都已经收到
	newsk->rcv_ack_seq = newsk->write_seq;
	newsk->urg_data = 0;
	newsk->retransmits = 0;
	// 关闭套接字的时候不需要等待一段时间才能关闭
	newsk->linger=0;
	newsk->destroy = 0;
	init_timer(&newsk->timer);
	newsk->timer.data = (unsigned long)newsk;
	newsk->timer.function = &net_timer;
	init_timer(&newsk->retransmit_timer);
	newsk->retransmit_timer.data = (unsigned long)newsk;
	newsk->retransmit_timer.function=&retransmit_timer;
	// 记录端口,发送ack和get_sock的时候用
	newsk->dummy_th.source = skb->h.th->dest;
	newsk->dummy_th.dest = skb->h.th->source;
	// 记录ip,发送ack和get_sock的时候用
	newsk->daddr = saddr;
	newsk->saddr = daddr;
	// 放到tcp的sock哈希表
	put_sock(newsk->num,newsk);
	// tcp头
	newsk->dummy_th.res1 = 0;
	newsk->dummy_th.doff = 6;
	newsk->dummy_th.fin = 0;
	newsk->dummy_th.syn = 0;
	newsk->dummy_th.rst = 0;	
	newsk->dummy_th.psh = 0;
	newsk->dummy_th.ack = 0;
	newsk->dummy_th.urg = 0;
	newsk->dummy_th.res2 = 0;
	newsk->acked_seq = skb->h.th->seq + 1;
	newsk->copied_seq = skb->h.th->seq + 1;
	newsk->socket = NULL;
	newsk->ip_ttl=sk->ip_ttl;
	newsk->ip_tos=skb->ip_hdr->tos;
	rt=ip_rt_route(saddr, NULL,NULL);
	
	if(rt!=NULL && (rt->rt_flags&RTF_WINDOW))
		newsk->window_clamp = rt->rt_window;
	else
		newsk->window_clamp = 0;
		
	if (sk->user_mss)
		newsk->mtu = sk->user_mss;
	else if(rt!=NULL && (rt->rt_flags&RTF_MSS))
		newsk->mtu = rt->rt_mss - HEADER_SIZE;
	else 
	{
#ifdef CONFIG_INET_SNARL	/* Sub Nets Are Local */
		if ((saddr ^ daddr) & default_mask(saddr))
#else
		if ((saddr ^ daddr) & dev->pa_mask)
#endif
			newsk->mtu = 576 - HEADER_SIZE;
		else
			newsk->mtu = MAX_WINDOW;
	}

	// mtu等于设备的mtu减去ip头和tcp头的大小
	newsk->mtu = min(newsk->mtu, dev->mtu - HEADER_SIZE);

	// 解析tcp选项
	tcp_options(newsk,skb->h.th);
	// 分配一个skb
	buff = newsk->prot->wmalloc(newsk, MAX_SYN_SIZE, 1, GFP_ATOMIC);
	// skb和sock关联,4个字节是用于tcp mss选项,告诉对端自己的mss
	buff->len = sizeof(struct tcphdr)+4;
	buff->sk = newsk;
	buff->localroute = newsk->localroute;

	t1 =(struct tcphdr *) buff->data;

	// 构造ip和mac头
	tmp = sk->prot->build_header(buff, newsk->saddr, newsk->daddr, &ndev,
			       IPPROTO_TCP, NULL, MAX_SYN_SIZE,sk->ip_tos,sk->ip_ttl);

	buff->len += tmp;
	// tcp头
	t1 =(struct tcphdr *)((char *)t1 +tmp);
  
	memcpy(t1, skb->h.th, sizeof(*t1));
	buff->h.seq = newsk->write_seq;
	t1->dest = skb->h.th->source;
	t1->source = newsk->dummy_th.source;
	t1->seq = ntohl(newsk->write_seq++);
	// 是个ack包,即第二次握手
	t1->ack = 1;
	newsk->window = tcp_select_window(newsk);
	newsk->sent_seq = newsk->write_seq;
	t1->window = ntohs(newsk->window);
	t1->res1 = 0;
	t1->res2 = 0;
	t1->rst = 0;
	t1->urg = 0;
	t1->psh = 0;
	t1->syn = 1;
	t1->ack_seq = ntohl(skb->h.th->seq+1);
	t1->doff = sizeof(*t1)/4+1;
	ptr =(unsigned char *)(t1+1);
	ptr[0] = 2;
	ptr[1] = 4;
	ptr[2] = ((newsk->mtu) >> 8) & 0xff;
	ptr[3] =(newsk->mtu) & 0xff;

	tcp_send_check(t1, daddr, saddr, sizeof(*t1)+4, newsk);
	// 发送ack,即第二次握手
	newsk->prot->queue_xmit(newsk, ndev, buff, 0);
	reset_xmit_timer(newsk, TIME_WRITE , TCP_TIMEOUT_INIT);
	// skb关联的socket为newsk,accept的时候摘取skb时即拿到该socket返回给应用层
	skb->sk = newsk;
	// 把skb中数据的大小算在newsk中 
	sk->rmem_alloc -= skb->mem_len;
	newsk->rmem_alloc += skb->mem_len;
	// 插入监听型socket的接收队列,accept的时候摘取
	skb_queue_tail(&sk->receive_queue,skb);
	// 连接队列节点个数加1
	sk->ack_backlog++;
	release_sock(newsk);
	tcp_statistics.TcpOutSegs++;
}

这个函数主要是生成一个sock结构体,挂载到skb中,然后把skb插入队列中。最后发送ack完成第二次握手。

我们继续来看第三次握手。前面说过tcp_rcv是处理tcp数据包的。所以我们还是回到这个函数。

// daddr,saddr是ip头的字段,len为tcp头+数据长度 
int tcp_rcv(
	struct sk_buff *skb, 
	struct device *dev, 
	struct options *opt, 
	unsigned long daddr, 
	unsigned short len, 
	unsigned long saddr, 
	int redo, 
	struct inet_protocol * protocol
){
	// 从tcp的socket哈希链表中找到对应的socket结构
	sk = get_sock(&tcp_prot, th->dest, saddr, th->source, daddr);
	// 省略大量代码
	if(sk->state==TCP_SYN_RECV)
	{
		tcp_set_state(sk, TCP_ESTABLISHED);
	}
}

这里的逻辑很简单,就是设置sock结构体的状态为建立。但是我们发现,get_sock的时候,拿到的不再是listen型的那个sock结构体了,而是tcp_conn_request中生成的那个。因为tcp_conn_request生成的sock里设置了源ip、端口、目的ip、端口。get_sock匹配的时候会全匹配到这个新的sock。
    三次握手的过程中,第一次握手的时候,在监听型的sock结构体的接收队列里插入了一个sock节点。在第三次握手的时候,修改这个sock状态为已连接。我们看看accept函数是怎么摘取这个队列中的节点的。

// 返回一个完成的连接
static struct sk_buff *tcp_dequeue_established(struct sock *s)
{
	struct sk_buff *skb;
	unsigned long flags;
	save_flags(flags);
	cli(); 
	skb=tcp_find_established(s);
	if(skb!=NULL)
		skb_unlink(skb);	/* Take it off the queue */
	restore_flags(flags);
	return skb;
}

// 找出已经完成三次握手的socket
static struct sk_buff *tcp_find_established(struct sock *s)
{
	struct sk_buff *p=skb_peek(&s->receive_queue);
	if(p==NULL)
		return NULL;
	do
	{
		if(p->sk->state == TCP_ESTABLISHED || p->sk->state >= TCP_FIN_WAIT1)
			return p;
		p=p->next;
	}
	while(p!=(struct sk_buff *)&s->receive_queue);
	return NULL;
}

就是找到状态为TCP_ESTABLISHED的节点返回。另外监听型socket和通信型socket他的接收队列意义是不一样的,前者是已完成连接或者正在建立连接的队列,后者是数据包队列。这一版的linux把正在连接和已经完成连接的sock都放到一个队列里维护,现在的版本据说已经分为两个队列了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值