Linux网络协议栈从应用层到内核层②

1、bind 源码剖析

系统调用原型

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:文件描述符,通常为socket函数的返回值
addr:一个指向 sockaddr 结构的指针,该结构包含了要绑定的地址和端口信息
addrlen:addr 结构的大小

bind 系统调用源码

SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
	struct socket *sock;
	struct sockaddr_storage address;
	int err, fput_needed;

	//通过fd获得struct file,再通过struct file再获得struct socket
	//它们三者的关系在创建socket时,就链接好了
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (sock) {
		//将用户态的数据拷贝到内核中
		err = move_addr_to_kernel(umyaddr, addrlen, &address);
		if (err >= 0) {
			err = security_socket_bind(sock,
						   (struct sockaddr *)&address,
						   addrlen);
			if (!err)
				//在创建套接字时,已经将inet_protosw数组的第一个元素的ops赋值给sock的ops
				//然而inet_protosw数组的第一个元素就是tcp
				/*{
				.type =       SOCK_STREAM,
				.protocol =   IPPROTO_TCP,
				.prot =       &tcp_prot,
				.ops =        &inet_stream_ops,
				.flags =      INET_PROTOSW_PERMANENT |
			      INET_PROTOSW_ICSK,
				}*/
				//因此这里的sock->ops 等于 inet_stream_ops
				//这里调用的bind最终会调用到af_inet.c中的inet_bind函数
				err = sock->ops->bind(sock,
						      (struct sockaddr *)
						      &address, addrlen);
		}
		fput_light(sock->file, fput_needed);
	}
	return err;
}

sockfd_lookup_light函数

struct fd {
	struct file *file;
	unsigned int flags;
};

static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
{
	//获取fd对应的file
	struct fd f = fdget(fd);
	struct socket *sock;

	*err = -EBADF;
	if (f.file) {
		//通过file获取sock
		sock = sock_from_file(f.file, err); //内部return file->private_data  
		if (likely(sock)) {
			*fput_needed = f.flags;
			return sock;
		}
		fdput(f);
	}
	return NULL;
}

inet_bind函数

int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
	//进行相应数据的初始化
	struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
	struct sock *sk = sock->sk;
	struct inet_sock *inet = inet_sk(sk);
	struct net *net = sock_net(sk);
	unsigned short snum;
	int chk_addr_ret;
	u32 tb_id = RT_TABLE_LOCAL;
	int err;

	//因为inet_bind是上层的抽象,此时还需要调用具体协议栈内的bind函数,也就是tcp
	//如果tcp实现了自己的bind,就进行调用
	//在创建套接字时,已经将inet_protosw数组的第一个元素的prot赋值给sock的prot
	//然而inet_protosw数组的第一个元素就是tcp,prot就是tcp_prot
	//但是在tcp_ipv4.c中可以查看到tcp并没有自己的bind函数,因此对应tcp只需要使用上层的inet_bind函数就够用了
	if (sk->sk_prot->bind) {
		err = sk->sk_prot->bind(sk, uaddr, addr_len);
		goto out;
	}
	err = -EINVAL;
	//进行地址长度的校验
	if (addr_len < sizeof(struct sockaddr_in))
		goto out;
	//进行协议族的校验
	if (addr->sin_family != AF_INET) {
		/* Compatibility games : accept AF_UNSPEC (mapped to AF_INET)
		 * only if s_addr is INADDR_ANY.
		 */
		err = -EAFNOSUPPORT;
		if (addr->sin_family != AF_UNSPEC ||
		    addr->sin_addr.s_addr != htonl(INADDR_ANY))
			goto out;
	}

	tb_id = l3mdev_fib_table_by_index(net, sk->sk_bound_dev_if) ? : tb_id;
	chk_addr_ret = inet_addr_type_table(net, addr->sin_addr.s_addr, tb_id);

	err = -EADDRNOTAVAIL;

	if (!net->ipv4.sysctl_ip_nonlocal_bind &&
	    !(inet->freebind || inet->transparent) &&
	    addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
	    chk_addr_ret != RTN_LOCAL &&
	    chk_addr_ret != RTN_MULTICAST &&
	    chk_addr_ret != RTN_BROADCAST)
		goto out;

	snum = ntohs(addr->sin_port);//将网络字节序转换为主机字节序
	err = -EACCES;
	if (snum && snum < PROT_SOCK &&
	    !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE))
		goto out;

	lock_sock(sk);

	err = -EINVAL;
	if (sk->sk_state != TCP_CLOSE || inet->inet_num)
		goto out_release_sock;

	//struct inet_sock *inet是struct sock 的进一步包装
	//内部包含通信四元组(本地地址,目的地址,本地端口,目的端口)
	/*struct inet_sock {
	struct sock		sk;
		#define inet_daddr		sk.__sk_common.skc_daddr  //目的地址
		#define inet_rcv_saddr		sk.__sk_common.skc_rcv_saddr //本地地址
		#define inet_dport		sk.__sk_common.skc_dport  //目的端口
		#define inet_num		sk.__sk_common.skc_num   //本地端口
		...
	}*/
	inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;   //绑定本地地址
	if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
		inet->inet_saddr = 0;  
	
	//确保端口能被绑定
	if ((snum || !inet->bind_address_no_port) &&
	    sk->sk_prot->get_port(sk, snum)) {
		inet->inet_saddr = inet->inet_rcv_saddr = 0;
		err = -EADDRINUSE;
		goto out_release_sock;
	}

	if (inet->inet_rcv_saddr)
		sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
	if (snum)
		sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
	inet->inet_sport = htons(inet->inet_num);//绑定本地端口
	//目的地址和目的端口未确定
	inet->inet_daddr = 0;
	inet->inet_dport = 0;
	sk_dst_reset(sk);
	err = 0;
out_release_sock:
	release_sock(sk);
out:
	return err;
}

整体流程

在这里插入图片描述

2、listen 源码剖析

系统调用原型

int listen(int sockfd, int backlog);

sockfd:文件描述符,指向类型为SOCK_STREAM或SOCK_SEQPACKET
backlog:全链接队列的大小,三次握手完成,服务端就会将链接放在全链接队列当中,上层再通过accept获取链接。如果服务端的全链接队列已经满了,那么其他的客户端就无法链接了

listen系统调用源码

SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
	struct socket *sock;
	int err, fput_needed;
	int somaxconn;

	//通过fd获取socket
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (sock) {
		//获取系统配置的全链接大小
		somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
		//我们传入的backlog和系统配置的backlog进行比较,取较小值
		//因此我们在调用listen时,可以限定全链接队列的大小,但是也不能完全限定,因为还要和系统配置比较
		if ((unsigned int)backlog > somaxconn)
			backlog = somaxconn;

		err = security_socket_listen(sock, backlog);
		if (!err)
			//调用到af_inet.c中的inet_stream_ops中的inet_listen函数
			err = sock->ops->listen(sock, backlog);

		fput_light(sock->file, fput_needed);
	}
	return err;
}

inet_stream_ops中的inet_listen函数

int inet_listen(struct socket *sock, int backlog)
{
	struct sock *sk = sock->sk;
	unsigned char old_state;
	int err;

	lock_sock(sk);

	err = -EINVAL;
	//如果sock没有处于未连接的状态或者sock的type为SOCK_STREAM,则返回
	if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
		goto out;

	//如果sock不是关闭或者监听,则返回
	old_state = sk->sk_state;
	if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
		goto out;

	if (old_state != TCP_LISTEN) {
		//涉及到TCP Fast Open的选项,它是一种加速TCP连接建立的技术
		if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) &&
		    (sysctl_tcp_fastopen & TFO_SERVER_ENABLE) &&
		    !inet_csk(sk)->icsk_accept_queue.fastopenq.max_qlen) {
			fastopen_queue_tune(sk, backlog);
			tcp_fastopen_init_key_once(true);
		}
		//将sock从非listen到listen状态的初始化
		err = inet_csk_listen_start(sk, backlog);
		if (err)
			goto out;
	}
	//更改sock的
	sk->sk_max_ack_backlog = backlog;
	err = 0;

out:
	release_sock(sk);
	return err;
}

inet_csk_listen_start函数

int inet_csk_listen_start(struct sock *sk, int backlog)
{
	struct inet_connection_sock *icsk = inet_csk(sk);//面向连接的sock
	struct inet_sock *inet = inet_sk(sk);
	int err = -EADDRINUSE;

	reqsk_queue_alloc(&icsk->icsk_accept_queue);//申请全连接队列

	sk->sk_max_ack_backlog = backlog;//更改全链接队列的最大值
	sk->sk_ack_backlog = 0;//因为当前正在处于更改listen状态,所以不可能有链接到来,因此当前全链接队列的大小为0
	inet_csk_delack_init(sk);

	//更改状态
	sk_state_store(sk, TCP_LISTEN);
	//检测端口是冲突
	if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
		//赋值
		inet->inet_sport = htons(inet->inet_num);

		sk_dst_reset(sk);
		// 当sock保存到全局的哈希列表里
		err = sk->sk_prot->hash(sk);

		if (likely(!err))
			return 0;
	}
	//如果失败,那么就把状态该为close
	sk->sk_state = TCP_CLOSE;
	return err;
}

整体流程

在这里插入图片描述

3、accept 源码剖析

系统调用原型

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd:文件描述符,指向类型为SOCK_STREAM或SOCK_SEQPACKET
addr:一个指向 sockaddr 结构的指针,该结构包含了要绑定的地址和端口信息
addrlen:addr 结构的大小

accept会从全链接队列中取出第一个链接,如果成功,就会返回一个文件描述符

accept系统调用源码

SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
		int __user *, upeer_addrlen)
{
	return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}

SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
		int __user *, upeer_addrlen, int, flags)
{
	struct socket *sock, *newsock;
	struct file *newfile;
	int err, len, newfd, fput_needed;
	struct sockaddr_storage address;

	//进行相应的检测
	if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
		return -EINVAL;

	if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
		flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

	//根据listen fd 获取对应的socket
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (!sock)
		goto out;

	err = -ENFILE;
	newsock = sock_alloc(); //为新链接申请socket
	if (!newsock)
		goto out_put;

	//将listen fd所对应的socket的type和ops赋值给新链接的socket
	newsock->type = sock->type;
	newsock->ops = sock->ops;

	__module_get(newsock->ops->owner);
	//为新链接申请fd
	newfd = get_unused_fd_flags(flags);
	if (unlikely(newfd < 0)) {
		err = newfd;
		sock_release(newsock);
		goto out_put;
	}
	//为新链接申请file,并和新链接的socket建立联系
	newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
	if (IS_ERR(newfile)) {
		err = PTR_ERR(newfile);
		put_unused_fd(newfd);
		sock_release(newsock);
		goto out_put;
	}

	err = security_socket_accept(sock, newsock);
	if (err)
		goto out_fd;

	//调用到af_inet.c中的inet_stream_ops中的inet_accept函数
	err = sock->ops->accept(sock, newsock, sock->file->f_flags);
	if (err < 0)
		goto out_fd;

	//获取对端地址
	if (upeer_sockaddr) {
		if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
					  &len, 2) < 0) {
			err = -ECONNABORTED;
			goto out_fd;
		}
		err = move_addr_to_user(&address,
					len, upeer_sockaddr, upeer_addrlen);
		if (err < 0)
			goto out_fd;
	}


	//新链接的fd和新链接的file建立联系
	fd_install(newfd, newfile);
	err = newfd;
	//新链接的fd,file,socket之间建立联系,跟创建socket源码时类似
out_put:
	fput_light(sock->file, fput_needed);
out:
	return err;
out_fd:
	fput(newfile);
	put_unused_fd(newfd);
	goto out_put;
}


int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
	struct sock *sk1 = sock->sk;//listen的sock
	int err = -EINVAL;
	//sk2是新链接的sock
	//调用tcp实现的accept获取sock赋值给listen的sock
	//tcp.ipv4.c中的inet_csk_accept
	struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);

	if (!sk2)
		goto do_err;

	lock_sock(sk2);

	sock_rps_record_flow(sk2);
	WARN_ON(!((1 << sk2->sk_state) &
		  (TCPF_ESTABLISHED | TCPF_SYN_RECV |
		  TCPF_CLOSE_WAIT | TCPF_CLOSE)));

	sock_graft(sk2, newsock);//建立起新链接的socket 和 sock之间的联系

	newsock->state = SS_CONNECTED;
	err = 0;
	release_sock(sk2);
do_err:
	return err;
}

tcp.ipv4.c中的inet_csk_accept

struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
	//struct inet_connection_sock是内核中实现有链接的socket
	//inet_connection_sock-->inet_sock-->sock
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct request_sock_queue *queue = &icsk->icsk_accept_queue; //全链接队列
	/*struct request_sock_queue {
		...
		u32			synflood_warned;
		atomic_t		qlen;  //半链接队列sk个数
		...
		全链接队列的队列头和队列尾
		struct request_sock	*rskq_accept_head; //头
		struct request_sock	*rskq_accept_tail; //尾
		...
	};*/
	struct request_sock *req; //用来存放已经到来的链接
	struct sock *newsk;
	int error;

	lock_sock(sk);

	/* We need to make sure that this socket is listening,
	 * and that it has something pending.
	 */
	error = -EINVAL;
	//传进来的sock不处于listen状态
	if (sk->sk_state != TCP_LISTEN)
		goto out_err;

	/* Find already established connection */
	//如果全链接队列为空
	if (reqsk_queue_empty(queue)) {
		//获取阻塞的时间,如果sock为非阻塞,timeo就为0
		//return noblock ? 0 : sk->sk_rcvtimeo;
		long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);

		/* If this is a non blocking socket don't sleep */
		error = -EAGAIN;
		//sock为非阻塞,返回
		if (!timeo)
			goto out_err;

		//sock为阻塞,在这个函数内部会进一步调用schedule_timeout进行睡眠,直到超时,或者有链接到来才会被唤醒
		error = inet_csk_wait_for_connect(sk, timeo);
		//如果超时了,或者出错了,返回
		if (error)
			goto out_err;
	}
	//执行到这里,说明全链接队列里一定有链接
	//去除链接
	req = reqsk_queue_remove(queue, sk);
	newsk = req->sk;
	...
}

reqsk_queue_remove函数取链接

static inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue,
						      struct sock *parent)
{
	struct request_sock *req;

	spin_lock_bh(&queue->rskq_lock);
	req = queue->rskq_accept_head; //取出队头元素
	if (req) {
		sk_acceptq_removed(parent); //sk->sk_ack_backlog--; 全链接队列的个数减1
		queue->rskq_accept_head = req->dl_next; //rskq_accept_head=rskq_accept_head->dl_next 删除队头元素
		//如果只有一个元素,取完之后,头尾都是NULL
		if (queue->rskq_accept_head == NULL)
			queue->rskq_accept_tail = NULL;
	}
	spin_unlock_bh(&queue->rskq_lock);
	return req;
}

4、connect 源码剖析

系统调用原型

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:文件描述符,指向类型为SOCK_STREAM或SOCK_SEQPACKET
addr:一个指向 sockaddr 结构的指针,该结构包含了要绑定的地址和端口信息
addrlen:addr 结构的大小

如果连接成功,则返回0,否则返回-1

系统调用源码

SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
		int, addrlen)
{
	struct socket *sock;
	struct sockaddr_storage address;
	int err, fput_needed;
	//通过fd获得与之对应socket
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (!sock)
		goto out;
	//将用户层的数据拷贝至内核
	err = move_addr_to_kernel(uservaddr, addrlen, &address);
	if (err < 0)
		goto out_put;

	err = security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
	if (err)
		goto out_put;

	//调用af_inet.c中的inet_stream_ops中的inet_stream_connect函数
	err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
				 sock->file->f_flags);
out_put:
	fput_light(sock->file, fput_needed);
out:
	return err;
}

af_inet.c中的inet_stream_ops中的inet_stream_connect函数

int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
			int addr_len, int flags)
{
	int err;

	lock_sock(sock->sk);
	//进一步的调用
	err = __inet_stream_connect(sock, uaddr, addr_len, flags);
	release_sock(sock->sk);
	return err;
}


int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
			  int addr_len, int flags)
{
	struct sock *sk = sock->sk;
	int err;
	long timeo;

	//做相应的检测
	if (addr_len < sizeof(uaddr->sa_family))
		return -EINVAL;

	if (uaddr->sa_family == AF_UNSPEC) {
		err = sk->sk_prot->disconnect(sk, flags);
		sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
		goto out;
	}

	//只有当socket的状态为SS_UNCONNECTED并且sock的状态为TCP_CLOSE后,最终才会调用到tcp的connect函数
	switch (sock->state) {
	default:
		err = -EINVAL;
		goto out;
	case SS_CONNECTED:
		err = -EISCONN;
		goto out;
	case SS_CONNECTING:
		err = -EALREADY;
		break;
	case SS_UNCONNECTED:
		err = -EISCONN;
		if (sk->sk_state != TCP_CLOSE)
			goto out;

		//最终会调用到tcp_ipv4.c中tcp_v4_connect函数(tcp实现的connect)
		//进行三次握手
		err = sk->sk_prot->connect(sk, uaddr, addr_len);
		if (err < 0)
			goto out;

		//更改状态为SS_CONNECTING,表示链接成功
		sock->state = SS_CONNECTING;

		err = -EINPROGRESS;
		break;
	}

	//判断上层的文件描述符有没有指定非阻塞
	//return noblock ? 0 : sk->sk_sndtimeo;
	timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
	
	//判断sk中的状态是否为TCPF_SYN_SENT或者TCPF_SYN_RECV
	//因为sk的状态会随着三次握手的变化而变化
	if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
		int writebias = (sk->sk_protocol == IPPROTO_TCP) &&
				tcp_sk(sk)->fastopen_req &&
				tcp_sk(sk)->fastopen_req->data ? 1 : 0;

		//如果没超时,就是调用inet_wait_for_connect进行等待 
		if (!timeo || !inet_wait_for_connect(sk, timeo, writebias))
			goto out;

		err = sock_intr_errno(timeo);
		if (signal_pending(current))
			goto out;
	}

	if (sk->sk_state == TCP_CLOSE)
		goto sock_error;

	sock->state = SS_CONNECTED;
	err = 0;
out:
	return err;

sock_error:
	err = sock_error(sk) ? : -ECONNABORTED;
	sock->state = SS_UNCONNECTED;
	if (sk->sk_prot->disconnect(sk, flags))
		sock->state = SS_DISCONNECTING;
	goto out;
}

客户端调用connect成功,但三次握手并未完成,进程是如何阻塞自己

看看在inet_wait_for_connect函数内部,进程是如何阻塞自己的

static long inet_wait_for_connect(struct sock *sk, long timeo, int writebias)
{
	//初始化一个等待队列(wait),并指定了一个唤醒函数woken_wake_function
	//当条件满足时,内核就会调用woken_wake_function将其唤醒
	DEFINE_WAIT_FUNC(wait, woken_wake_function);

	//将wait加入到等待sock的等待队列中
	add_wait_queue(sk_sleep(sk), &wait);
	sk->sk_write_pending += writebias;

	//当sk的sk_state为TCPF_SYN_SENT或者TCPF_SYN_RECV,都表示三次握手还没完成
	while ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
		release_sock(sk);
		//调用wait_woken进行睡眠
		timeo = wait_woken(&wait, TASK_INTERRUPTIBLE, timeo);
		lock_sock(sk);
		//退出来的原因如果是被信号中断或者超时,那么就退出循环,否则再次进行睡眠
		if (signal_pending(current) || !timeo)
			break;
	}
	//移除等待队列
	remove_wait_queue(sk_sleep(sk), &wait);
	sk->sk_write_pending -= writebias;
	return timeo;
}


//DEFINE_WAIT_FUNC等待队列
//function就是内核唤醒该进程时需要执行的函数
#define DEFINE_WAIT_FUNC(name, function)				\
	wait_queue_t name = {						\
		//current表示当前进程的task_struct
		//在设置等待队列时,当前进程就会把自己的task_struct放在等待队列中,方便内核唤醒
		.private	= current,				\
		.func		= function,				\
		.task_list	= LIST_HEAD_INIT((name).task_list),	\
	}


//woken_wake_function唤醒函数
int woken_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	smp_wmb(); /* C */
	wait->flags |= WQ_FLAG_WOKEN;

	return default_wake_function(wait, mode, sync, key);
}

int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,
			  void *key)
{
						//curr->private 就表示被挂起的进程的task_struct
	return try_to_wake_up(curr->private, mode, wake_flags);
}

看看tcp_ipv4.c中tcp_v4_connect函数是如何进行三次握手操作的

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
	struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
	struct inet_sock *inet = inet_sk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	__be16 orig_sport, orig_dport;
	__be32 daddr, nexthop;
	struct flowi4 *fl4;
	struct rtable *rt;
	int err;
	struct ip_options_rcu *inet_opt;

	//检查地址的有效性
	if (addr_len < sizeof(struct sockaddr_in))
		return -EINVAL;

	if (usin->sin_family != AF_INET)
		return -EAFNOSUPPORT;

	//获取目标地址和下一跳地址
	nexthop = daddr = usin->sin_addr.s_addr;
	inet_opt = rcu_dereference_protected(inet->inet_opt,
					     lockdep_sock_is_held(sk));
	if (inet_opt && inet_opt->opt.srr) {
		if (!daddr)
			return -EINVAL;
		nexthop = inet_opt->opt.faddr;
	}

	//保存原始的源端口号和目的端口号
	orig_sport = inet->inet_sport;
	orig_dport = usin->sin_port;
	//获取并设置用于连接的路由
	fl4 = &inet->cork.fl.u.ip4;

	rt = ip_route_connect(fl4, nexthop, inet->inet_saddr,
			      RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
			      IPPROTO_TCP,
			      orig_sport, orig_dport, sk);
	//检查路由连接的结果
	if (IS_ERR(rt)) {
		err = PTR_ERR(rt);
		if (err == -ENETUNREACH)
			IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
		return err;
	}

	//检查路由是否指向多播或广播地址
	if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
		ip_rt_put(rt);
		return -ENETUNREACH;
	}

	//更新目标地址为路由的目标地址
	if (!inet_opt || !inet_opt->opt.srr)
		daddr = fl4->daddr;

	//更新源 IP 地址
	if (!inet->inet_saddr)
		inet->inet_saddr = fl4->saddr;
	sk_rcv_saddr_set(sk, inet->inet_saddr);

	//重置 TCP 相关的状态
	if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) {
		/* Reset inherited state */
		tp->rx_opt.ts_recent	   = 0;
		tp->rx_opt.ts_recent_stamp = 0;
		if (likely(!tp->repair))
			tp->write_seq	   = 0;
	}

	if (tcp_death_row.sysctl_tw_recycle &&
	    !tp->rx_opt.ts_recent_stamp && fl4->daddr == daddr)
		tcp_fetch_timewait_stamp(sk, &rt->dst);

	
	inet->inet_dport = usin->sin_port;//设置目的端口号
	sk_daddr_set(sk, daddr);//设置目标地址

	//设置扩展头长度
	inet_csk(sk)->icsk_ext_hdr_len = 0;
	if (inet_opt)
		inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen;

	//设置最大报文段长度
	tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT;

	//设置套接字的状态为 SYN-SENT,并调用inet_hash_connect设置源端口号

	tcp_set_state(sk, TCP_SYN_SENT);
	err = inet_hash_connect(&tcp_death_row, sk);//确定源端口号
	if (err)
		goto failure;

	sk_set_txhash(sk);

	//设置新的端口并更新路由表
	rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
			       inet->inet_sport, inet->inet_dport, sk);
	if (IS_ERR(rt)) {
		err = PTR_ERR(rt);
		rt = NULL;
		goto failure;
	}
	/* OK, now commit destination to socket.  */
	//设置套接字的 GSO 类型并设置能力
	sk->sk_gso_type = SKB_GSO_TCPV4;
	sk_setup_caps(sk, &rt->dst);

	//设置初始化的 TCP 序列号
	if (!tp->write_seq && likely(!tp->repair))
		// TCP 初始化的序列号 跟四元组和时间有关,最大程度上保证序列号不重复
		tp->write_seq = secure_tcp_sequence_number(inet->inet_saddr,
							   inet->inet_daddr,
							   inet->inet_sport,
							   usin->sin_port);
	//为套接字分配一个随机的标识符
	inet->inet_id = tp->write_seq ^ jiffies;

	//发送 TCP 连接请求(发送syn包)
	err = tcp_connect(sk);

	rt = NULL;
	if (err)
		goto failure;

	return 0;

failure:
	//失败时处理的操作,将状态改为TCP_CLOSE
	tcp_set_state(sk, TCP_CLOSE);
	ip_rt_put(rt);
	sk->sk_route_caps = 0;
	inet->inet_dport = 0;
	return err;
}

客户端在connect时,如何选择源端口

在前面的代码中,会调用inet_hash_connect(&tcp_death_row, sk)函数选择源端口,通过分析源码,看看是如何选择的?

int inet_hash_connect(struct inet_timewait_death_row *death_row,
		      struct sock *sk)
{
	u32 port_offset = 0;

	if (!inet_sk(sk)->inet_num)
		port_offset = inet_sk_port_offset(sk);
	return __inet_hash_connect(death_row, sk, port_offset,
				   __inet_check_established);
}

//进一步调用__inet_hash_connect(death_row, sk, port_offset, __inet_check_established);
int __inet_hash_connect(struct inet_timewait_death_row *death_row,
		struct sock *sk, u32 port_offset,
		int (*check_established)(struct inet_timewait_death_row *,
			struct sock *, __u16, struct inet_timewait_sock **))
{
	//相应结构的初始化
	//在内核当中,有一个全局的连接信息 inet_hashinfo
	/*struct inet_hashinfo {
		...
		struct inet_ehash_bucket	*ehash; //存放拥有完整连接信息的连接,key,四元组结构体,value:表示该连接的sock
		struct inet_bind_hashbucket	*bhash; //存放所有绑定某端号的连接,key:本地端口,value:绑定该端口的sock
		struct inet_listen_hashbucket	listening_hash[INET_LHTABLE_SIZE]
					____cacheline_aligned_in_smp;
		//存放所有监听sock,key:本地端口,value:对应listen状态下的sock
		...
	};*/
	
	/*struct inet_bind_hashbucket {
		spinlock_t		lock;
		struct hlist_head	chain;
	};*/
	
	/*struct hlist_head {
		struct hlist_node *first;
	};*/
	
	/*struct inet_bind_bucket {
		possible_net_t		ib_net;
		unsigned short		port;//对应的端口
		signed char		fastreuse;
		signed char		fastreuseport;
		kuid_t			fastuid;
		int			num_owners;
		struct hlist_node	node;
		struct hlist_head	owners; //用于保存sock中的skc_bind_node,就将端口和sock关联在一起
	};*/
	
	//通过port就能找到对应的inet_bind_hashbucket,从而找到hlist_head,进一步再找到hlist_node
	//hlist_node node 又指向了sock中的skc_bind_node,此时就能找到正在使用port的sock
	
	
	struct inet_hashinfo *hinfo = death_row->hashinfo;
	struct inet_timewait_sock *tw = NULL;
	struct inet_bind_hashbucket *head;
	int port = inet_sk(sk)->inet_num;
	struct net *net = sock_net(sk);
	struct inet_bind_bucket *tb;
	u32 remaining, offset;
	int ret, i, low, high;
	static u32 hint;

	//如果端口存在,直接返回
	if (port) {
		head = &hinfo->bhash[inet_bhashfn(net, port,
						  hinfo->bhash_size)];
		tb = inet_csk(sk)->icsk_bind_hash;
		spin_lock_bh(&head->lock);
		if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) {
			inet_ehash_nolisten(sk, NULL);
			spin_unlock_bh(&head->lock);
			return 0;
		}
		spin_unlock(&head->lock);
		ret = check_established(death_row, sk, port, NULL);
		local_bh_enable();
		return ret;
	}
	//获取可用的端口范围
	//该参数的默认值是 32768 61000,意味着端口总可用的数量是61000 - 32768 = 28232个 
	inet_get_local_port_range(net, &low, &high);
	high++; /* [32768, 60999] -> [32768, 61000[ */
	remaining = high - low; //28232
	if (likely(remaining > 1))
		remaining &= ~1U; //remaining必为偶数

	//选择一个随机的偏移量
	offset = (hint + port_offset) % remaining;
	//保证offset为奇数
	offset &= ~1U;
other_parity_scan:
	port = low + offset;//port也为奇数,port一定介于low 和 high之间
	//因为port为奇数,每次加2,还是奇数,因此先找奇数端口
	//如果奇数端口遍历完成,都还没有获取到可用的端口,会在后面再次 goto other_parity_scan,此时奇偶性反转,遍历偶数端口
	for (i = 0; i < remaining; i += 2, port += 2) {
		//unlikely为编译优化,表示不太可能成立
		if (unlikely(port >= high))
			port -= remaining;
		//如果是本地保留端口(给系统使用),则继续判断下一个端口
		if (inet_is_local_reserved_port(net, port))
			continue;
		//查看已经使用该端口的hash表
		//bhash存放所有绑定某端号的连接,key:本地端口,value:绑定该端口的sock
		head = &hinfo->bhash[inet_bhashfn(net, port,
						  hinfo->bhash_size)];
		spin_lock_bh(&head->lock);

		head = &hinfo->bhash[inet_bhashfn(net, port, hinfo->bhash_size)];
		inet_bind_bucket_for_each(tb, &head->chain) {
			//如果端口被使用了
			if (net_eq(ib_net(tb), net) && tb->port == port) {
				if (tb->fastreuse >= 0 ||
				    tb->fastreuseport >= 0)
					goto next_port;
				WARN_ON(hlist_empty(&tb->owners));
				//检测端口是否能复用,等于检测四元组是否相同(检测ehash中是否有相同的四元组),
				//如果存在,并且sock处于time_wait状态,表示可以复用,此时还会把处于time_wait状态的sock赋值给tw
				if (!check_established(death_row, sk,
						       port, &tw))
					goto ok;
				goto next_port;
			}
		}

		//如果端口没被别人使用,并且本次连接可以使用,就会创建inet_bind_bucket
		//然后加入到全局的连接信息(inet_hashinfo->bhash)
		tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
					     net, head, port);
		if (!tb) {
			spin_unlock_bh(&head->lock);
			return -ENOMEM;
		}
		tb->fastreuse = -1;
		tb->fastreuseport = -1;
		goto ok;
next_port:
		spin_unlock_bh(&head->lock);
		cond_resched();
	}

	offset++; //为了让奇数端口变为偶数端口
	if ((offset & 1) && remaining > 1)
		goto other_parity_scan;

	return -EADDRNOTAVAIL;

ok:
	hint += i + 2;

	inet_bind_hash(sk, tb, port); //将port和sock绑定起来
	if (sk_unhashed(sk)) {
		inet_sk(sk)->inet_sport = htons(port); //赋值源端口(已确定)
		inet_ehash_nolisten(sk, (struct sock *)tw);//将sock添加到ehash中
		//并且把之前绑定这个端口,但现在处于time_wait状态的sock从ehash中移除
	}
	if (tw)
		inet_twsk_bind_unhash(tw, hinfo);
	spin_unlock(&head->lock);
	if (tw)
		inet_twsk_deschedule_put(tw);
	local_bh_enable();
	return 0;
}

客户发送syn封包以及重传

确定四元组,设置初始化的 TCP 序列号后,就会调用tcp_output.c中的tcp_connect(struct sock *sk)函数,构建syn包并发送

tcp_connect源码

int tcp_connect(struct sock *sk)
{
	//tcp_sock对inet_connection_sock的进一步组合
	struct tcp_sock *tp = tcp_sk(sk);
	//无论是接受还是发送,在内核中都是通过sk_buff进行分包的构建和传输的
	struct sk_buff *buff;
	int err;

	if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
		return -EHOSTUNREACH; /* Routing failure or similar. */

	//对sock进行初始化化
	tcp_connect_init(sk);

	if (unlikely(tp->repair)) {
		tcp_finish_connect(sk, NULL);
		return 0;
	}

	//申请sk_buff
	buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);
	if (unlikely(!buff))
		return -ENOBUFS;

	//sk_buff初始化,write_seq就是初始序列号,TCPHDR_SYN就是tcp的标志(syn,ack,rst...)
	//这里其实就是将sk_buff声明成一个syn包
	tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
	//更新最近一次的发送数据报的时间
	tp->retrans_stamp = tcp_time_stamp;
	//把sk_buff放到sock的发送缓冲队列,当tcp发送数据时,就会从这个缓冲队列里面取数据,然后再发送出去
	tcp_connect_queue_skb(sk, buff);
	tcp_ecn_send_syn(sk, buff);

	//针对于Fast Open,采用tcp_send_syn_data,否则采用tcp_transmit_skb发送数据
	err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
	      tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
	if (err == -ECONNREFUSED)
		return err;

	//数据发送完成后(异步发送),需要更改对应的序号
	//snd_nxt表示发送窗口中可以窗口的第一个字节
	tp->snd_nxt = tp->write_seq;
	tp->pushed_seq = tp->write_seq;
	buff = tcp_send_head(sk);
	if (unlikely(buff)) {
		tp->snd_nxt	= TCP_SKB_CB(buff)->seq;
		tp->pushed_seq	= TCP_SKB_CB(buff)->seq;
	}
	TCP_INC_STATS(sock_net(sk), TCP_MIB_ACTIVEOPENS);
	//重启一个定时器,直到syn发送成功
	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
				  inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
	return 0;
}

如果syn发送失败,或者没有接受到对应的ack,还会涉及到syn包的重传
inet_csk_reset_xmit_timer源码

static inline void inet_csk_reset_xmit_timer(struct sock *sk, const int what,
					     unsigned long when,
					     const unsigned long max_when)
{
	//传入进来的参数what就表示需要重启的定时器,因为定时器有多个
	struct inet_connection_sock *icsk = inet_csk(sk);

	//设置超时时间,when为自己设置的超时时间,max_when为最大的超时时间
	if (when > max_when) {
#ifdef INET_CSK_DEBUG
		pr_debug("reset_xmit_timer: sk=%p %d when=0x%lx, caller=%p\n",
			 sk, what, when, current_text_addr());
#endif
		when = max_when;
	}

	if (what == ICSK_TIME_RETRANS || what == ICSK_TIME_PROBE0 ||
	    what == ICSK_TIME_EARLY_RETRANS || what ==  ICSK_TIME_LOSS_PROBE) {
	    //将事件记录到icsk_pending中表示启动的定时器是超时重传定时器
		icsk->icsk_pending = what;
		//下次进行重传的时间,jiffis为当前的时间戳,when表示时间间隔
		icsk->icsk_timeout = jiffies + when;
		//重启定时器,超时后就会触发
		sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
	} else if (what == ICSK_TIME_DACK) {
		icsk->icsk_ack.pending |= ICSK_ACK_TIMER;
		icsk->icsk_ack.timeout = jiffies + when;
		sk_reset_timer(sk, &icsk->icsk_delack_timer, icsk->icsk_ack.timeout);
	}
#ifdef INET_CSK_DEBUG
	else {
		pr_debug("%s", inet_csk_timer_bug_msg);
	}
#endif
}

当超时后,就会执行定时器对应的执行函数,那这些函数又是如何被注册到对应的sock中的呢?

首先sock在初始化时,会调用到tcp.c中的tcp_init_sock,然后调用tcp_init_xmit_timers,再调用inet_csk_init_xmit_timers

//tcp.c
void tcp_init_sock(struct sock *sk)
{
	...
	tcp_init_xmit_timers(sk);
	...
}


//tcp_timer.c
void tcp_init_xmit_timers(struct sock *sk)
{
	inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer,
				  &tcp_keepalive_timer);
}

//inet_connection_sock.c
//在这里会注册三个定时器
void inet_csk_init_xmit_timers(struct sock *sk,
			       void (*retransmit_handler)(unsigned long),
			       void (*delack_handler)(unsigned long),
			       void (*keepalive_handler)(unsigned long))
{
	struct inet_connection_sock *icsk = inet_csk(sk);

	setup_timer(&icsk->icsk_retransmit_timer, retransmit_handler,
			(unsigned long)sk);
	setup_timer(&icsk->icsk_delack_timer, delack_handler,
			(unsigned long)sk);
	setup_timer(&sk->sk_timer, keepalive_handler, (unsigned long)sk);
	//由于同一个定时器函数可以处理多个定时器,它们也需要进行区分,pending参数表示
	//当前需要处理的是哪个定时事件,0表示没有事件需要处理
	icsk->icsk_pending = icsk->icsk_ack.pending = 0;
}

再看看这个重传数据的定时器tcp_write_timer,主要做了哪些事?

//tcp_timer.c
//重传数据
static void tcp_write_timer(unsigned long data)
{
	struct sock *sk = (struct sock *)data;
	
	//定时器函数在软中断中执行,这里先锁定套接字
	bh_lock_sock(sk);

	//如果TCB没有被进程上下文锁定,那么就调用tcp_write_timer_handler做进一步处理
	if (!sock_owned_by_user(sk)) {
		//进一步调用
		tcp_write_timer_handler(sk);
	} else {
		/* delegate our work to tcp_release_cb() */
		if (!test_and_set_bit(TCP_WRITE_TIMER_DEFERRED, &tcp_sk(sk)->tsq_flags))
			sock_hold(sk);
	}
	bh_unlock_sock(sk);
	sock_put(sk);
}


void tcp_write_timer_handler(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	int event;

	//如果套接字已经关闭或者定时器根本就没有启动,退出
	if (((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN)) ||
	    !icsk->icsk_pending)
		goto out;
		
	//如果没有超时,重新设定超时时间
	if (time_after(icsk->icsk_timeout, jiffies)) {
		sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
		goto out;
	}
	
	//event 表示需要处理的事件
	event = icsk->icsk_pending;
	
	switch (event) {
	case ICSK_TIME_EARLY_RETRANS:
		tcp_resume_early_retransmit(sk);
		break;
	case ICSK_TIME_LOSS_PROBE:
		tcp_send_loss_probe(sk);
		break;
	case ICSK_TIME_RETRANS:
		//即将处理事件,将icsk_pending置为0,表示清除事件,后面如果需要会重新设定
		icsk->icsk_pending = 0;
		//超时重传由该函数处理
		tcp_retransmit_timer(sk);
		break;
	case ICSK_TIME_PROBE0:
		icsk->icsk_pending = 0;
		tcp_probe_timer(sk);
		break;
	}

out:
	//内存回收
	sk_mem_reclaim(sk);
}



void tcp_retransmit_timer(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct net *net = sock_net(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);

	//暂时不考虑fastopen
	if (tp->fastopen_rsk) {
		WARN_ON_ONCE(sk->sk_state != TCP_SYN_RECV &&
			     sk->sk_state != TCP_FIN_WAIT1);
		tcp_fastopen_synack_timer(sk);
		/* Before we receive ACK to our SYN-ACK don't retransmit
		 * anything else (e.g., data or FIN segments).
		 */
		return;
	}

	//如果根本就没有发送数据何来超时处理
	if (!tp->packets_out)
		goto out;

	//同上,发送队列是空的
	WARN_ON(tcp_write_queue_empty(sk));

	tp->tlp_high_seq = 0;

	//发送窗口为0;socket没有关闭;当前不再三次握手阶段(说明在连接态)。
	//这种情况下发生了超时,需要检查是否需要关闭套接字
	if (!tp->snd_wnd && !sock_flag(sk, SOCK_DEAD) &&
	    !((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV))) {
		struct inet_sock *inet = inet_sk(sk);
		if (sk->sk_family == AF_INET) {
			net_dbg_ratelimited("Peer %pI4:%u/%u unexpectedly shrunk window %u:%u (repaired)\n",
					    &inet->inet_daddr,
					    ntohs(inet->inet_dport),
					    inet->inet_num,
					    tp->snd_una, tp->snd_nxt);
		}
#if IS_ENABLED(CONFIG_IPV6)
		else if (sk->sk_family == AF_INET6) {
			net_dbg_ratelimited("Peer %pI6:%u/%u unexpectedly shrunk window %u:%u (repaired)\n",
					    &sk->sk_v6_daddr,
					    ntohs(inet->inet_dport),
					    inet->inet_num,
					    tp->snd_una, tp->snd_nxt);
		}
#endif
		//如果这条连接上已经有很长时间(超过TCP_RTO_MAX=120s)没有收到对端
		//的确认了,认为连接异常了,直接关闭该套接字
		if (tcp_time_stamp - tp->rcv_tstamp > TCP_RTO_MAX) {
			tcp_write_err(sk);
			goto out;
		}
		//这种情况说明对方已经很拥塞了,进入LOSS状态
		tcp_enter_loss(sk);
		//重传第一包数据
		tcp_retransmit_skb(sk, tcp_write_queue_head(sk), 1);
		__sk_dst_reset(sk);
		goto out_reset_timer;
	}

	//超时重传也不能无限重传下去,必须有截止时间,
	//该函数判断重传的次数是否超过最大限制,是否继续执行超时重传
	if (tcp_write_timeout(sk))
		goto out;

	//如果是第一次超时重传
	if (icsk->icsk_retransmits == 0) {
		int mib_idx;

		if (icsk->icsk_ca_state == TCP_CA_Recovery) {
			if (tcp_is_sack(tp))
				mib_idx = LINUX_MIB_TCPSACKRECOVERYFAIL;
			else
				mib_idx = LINUX_MIB_TCPRENORECOVERYFAIL;
		} else if (icsk->icsk_ca_state == TCP_CA_Loss) {
			mib_idx = LINUX_MIB_TCPLOSSFAILURES;
		} else if ((icsk->icsk_ca_state == TCP_CA_Disorder) ||
			   tp->sacked_out) {
			if (tcp_is_sack(tp))
				mib_idx = LINUX_MIB_TCPSACKFAILURES;
			else
				mib_idx = LINUX_MIB_TCPRENOFAILURES;
		} else {
			mib_idx = LINUX_MIB_TCPTIMEOUTS;
		}
		__NET_INC_STATS(sock_net(sk), mib_idx);
	}

	//切换拥塞状态为LOSS
	tcp_enter_loss(sk);

	//尝试重传第一包数据,如果发送失败说明本地发生了拥塞,这时不执行指数退避算法
	//一旦允许超时重传,那么只重发当前发送队列中的第一个包,然后按照指数退避算法重启定时器
	if (tcp_retransmit_skb(sk, tcp_write_queue_head(sk), 1) > 0) {
		if (!icsk->icsk_retransmits)
			icsk->icsk_retransmits = 1;
		//再次设定超时定时器
		inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
					  min(icsk->icsk_rto, TCP_RESOURCE_PROBE_INTERVAL),
					  TCP_RTO_MAX);
		goto out;
	}
	//累加指数退避次数和发生超时重传次数
	icsk->icsk_backoff++;
	icsk->icsk_retransmits++;

out_reset_timer:
	if (sk->sk_state == TCP_ESTABLISHED &&
	    (tp->thin_lto || sysctl_tcp_thin_linear_timeouts) &&
	    tcp_stream_is_thin(tp) &&
	    icsk->icsk_retransmits <= TCP_THIN_LINEAR_RETRIES) {
		icsk->icsk_backoff = 0;
		//执行指数退避算法,更新下次超时间隔记录到icsk_rto
		icsk->icsk_rto = min(__tcp_set_rto(tp), TCP_RTO_MAX);
	} else {
		icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);
	}
	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX);
	if (retransmits_timed_out(sk, net->ipv4.sysctl_tcp_retries1 + 1, 0, 0))
		__sk_dst_reset(sk);

out:;
}

小结一下:从上面可以看出,超时重传的处理逻辑还是很清晰的。1)首先检查是否还允许继续进行超时重传,这时综合考虑最大重传次数限制、系统拥塞限制、socket状态等因素;2)一旦允许超时重传,那么只重发当前发送队列中的第一个包,然后按照指数退避算法重启定时器。

服务端收到syn封包,如何处理

数据经过网卡,再到ip层,最终会调用到tcp_v4_rcv函数做处理

int tcp_v4_rcv(struct sk_buff *skb)
{
	struct net *net = dev_net(skb->dev);
	const struct iphdr *iph;
	const struct tcphdr *th;
	bool refcounted;
	struct sock *sk;
	int ret;

	//如果不是发往本机的就直接丢弃
	if (skb->pkt_type != PACKET_HOST)
		goto discard_it;

	/* Count it even if it's bad */
	__TCP_INC_STATS(net, TCP_MIB_INSEGS);

	/*如果一个TCP段在传输过程中被网络层分片,那么在目的端的网络层会重新组包,这会导致传给
	TCP的skb的分片结构中包含多个skb,这种情况下,该函数会将分片结构重组到线性数据区。如果发生异常,则丢弃该报文*/
	if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
		goto discard_it;

	//获得tcp头
	th = (const struct tcphdr *)skb->data;

	//如果 TCP 的首部长度小于不带数据的 TCP 的首部长度,则说明 TCP 数据异常。
	//统计相关信息后,丢弃。
	if (unlikely(th->doff < sizeof(struct tcphdr) / 4))
		goto bad_packet;
	//保证skb的线性区域至少包括实际的TCP首部
	if (!pskb_may_pull(skb, th->doff * 4))
		goto discard_it;


	//验证 TCP 首部中的校验和,如校验和有误,则说明报文已损坏,统计相关信息后丢弃
	if (skb_checksum_init(skb, IPPROTO_TCP, inet_compute_pseudo))
		goto csum_error;

	//tcp头
	th = (const struct tcphdr *)skb->data;
	iph = ip_hdr(skb);//ip头 skb->head(整个skb的起始位置) + skb->network_header(起始位置到ip头部的偏移量)

	memmove(&TCP_SKB_CB(skb)->header.h4, IPCB(skb),
		sizeof(struct inet_skb_parm));
	barrier();

	//初始化skb中的控制块
	TCP_SKB_CB(skb)->seq = ntohl(th->seq);
	TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
				    skb->len - th->doff * 4);
	TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
	TCP_SKB_CB(skb)->tcp_flags = tcp_flag_byte(th);
	TCP_SKB_CB(skb)->tcp_tw_isn = 0;
	TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph);
	TCP_SKB_CB(skb)->sacked	 = 0;

lookup:
	/* 在 ehash散列表中根据端口来查找传sock。
	如果在 ehash 中找到,则表示已经经历了三次握手并且已建立了连接,可以
	进行正常的通信。如果找不到就去listening_hash(监听的套接字)中找。
	如果在两个散列表中都查找不到,说明没有对应的socket,
	跳转到no_tcp_socket 处理。
	*/
	sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
			       th->dest, &refcounted);
	if (!sk)
		goto no_tcp_socket;

process:
	//TCP_TIME_WAIT需要做特殊处理,这里先不关注
	if (sk->sk_state == TCP_TIME_WAIT)
		goto do_time_wait;

	//如果服务端已经收到过客服端发的syn包
	if (sk->sk_state == TCP_NEW_SYN_RECV) {
		struct request_sock *req = inet_reqsk(sk);
		struct sock *nsk;

		sk = req->rsk_listener;
		if (unlikely(tcp_v4_inbound_md5_hash(sk, skb))) {
			sk_drops_add(sk, skb);
			reqsk_put(req);
			goto discard_it;
		}
		if (tcp_checksum_complete(skb)) {
			reqsk_put(req);
			goto csum_error;
		}
		if (unlikely(sk->sk_state != TCP_LISTEN)) {
			inet_csk_reqsk_queue_drop_and_put(sk, req);
			goto lookup;
		}

		sock_hold(sk);
		refcounted = true;
		nsk = tcp_check_req(sk, skb, req, false);
		if (!nsk) {
			reqsk_put(req);
			goto discard_and_relse;
		}
		if (nsk == sk) {
			reqsk_put(req);
		} else if (tcp_child_process(sk, nsk, skb)) {
			tcp_v4_send_reset(nsk, skb);
			goto discard_and_relse;
		} else {
			sock_put(sk);
			return 0;
		}
	}
	//ttl 小于给定的最小的 ttl
	if (unlikely(iph->ttl < inet_sk(sk)->min_ttl)) {
		__NET_INC_STATS(net, LINUX_MIB_TCPMINTTLDROP);
		goto discard_and_relse;
	}

	//查找 IPsec 数据库,如果查找失败,进行相应处理
	if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
		goto discard_and_relse;

	//md5 相关
	if (tcp_v4_inbound_md5_hash(sk, skb))
		goto discard_and_relse;

	nf_reset(skb);

	//TCP套接字过滤器,如果数据包被过滤掉了,结束处理过程
	if (tcp_filter(sk, skb))
		goto discard_and_relse;
	th = (const struct tcphdr *)skb->data;
	iph = ip_hdr(skb);

	//到了传输层,该字段已经没有意义,将其置为空
	skb->dev = NULL;

	//LISTEN 状态
	if (sk->sk_state == TCP_LISTEN) {
		ret = tcp_v4_do_rcv(sk, skb); //交由tcp_v4_do_rcv()处理
		goto put_and_return;
	}

	//先持锁,这样进程上下文和其它软中断则无法操作该TCB
	sk_incoming_cpu_update(sk);

	bh_lock_sock_nested(sk);
	tcp_segs_in(tcp_sk(sk), skb);
	ret = 0;
	//如果当前TCB没有被进程上下文锁定,首先尝试将数据包放入prequeue队列,
	//如果prequeue队列没有处理,再将其处理后放入receive队列。如果TCB已
	//经被进程上下文锁定,那么直接将数据包放入backlog队列
	if (!sock_owned_by_user(sk)) {
		if (!tcp_prequeue(sk, skb))
			ret = tcp_v4_do_rcv(sk, skb);
	} else if (tcp_add_backlog(sk, skb)) {//TCB被用户进程锁定,直接将数据包放入backlog队列
		goto discard_and_relse;
	}
	bh_unlock_sock(sk);//释放锁

put_and_return:
	if (refcounted)
		sock_put(sk);//释放TCB引用计数,当计数为 0 的时候,使用 sk_free 释放传输控制块

	return ret;//返回处理结果

//处理没有创建传输控制块收到报文,校验错误,坏包的情况,给对端发送 RST 报文。
no_tcp_socket:
	//查找 IPsec 数据库,如果查找失败,进行相应处理
	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
		goto discard_it;

	if (tcp_checksum_complete(skb)) {
csum_error:
		__TCP_INC_STATS(net, TCP_MIB_CSUMERRORS);
bad_packet:
		__TCP_INC_STATS(net, TCP_MIB_INERRS);
	} else {
		tcp_v4_send_reset(NULL, skb);
	}

discard_it:
	//丢弃帧
	kfree_skb(skb);
	return 0;

discard_and_relse:
	sk_drops_add(sk, skb);
	if (refcounted)
		sock_put(sk);
	goto discard_it;
//处理TIME_WAIT状态
do_time_wait:
	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
		inet_twsk_put(inet_twsk(sk));
		goto discard_it;
	}

	if (tcp_checksum_complete(skb)) {
		inet_twsk_put(inet_twsk(sk));
		goto csum_error;
	}
	//根据返回值进行相应处理
	switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
	case TCP_TW_SYN: {
		struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
							&tcp_hashinfo, skb,
							__tcp_hdrlen(th),
							iph->saddr, th->source,
							iph->daddr, th->dest,
							inet_iif(skb));
		if (sk2) {
			inet_twsk_deschedule_put(inet_twsk(sk));
			sk = sk2;
			refcounted = false;
			goto process;
		}
	}
	case TCP_TW_ACK:
		tcp_v4_timewait_ack(sk, skb);
		break;
	case TCP_TW_RST:
		tcp_v4_send_reset(sk, skb);
		inet_twsk_deschedule_put(inet_twsk(sk));
		goto discard_it;
	case TCP_TW_SUCCESS:;
	}
	goto discard_it;
}

因为服务端的sock处于listen状态,那么就会接着调用/net/ipv4/tcp_ipv4.c中的tcp_v4_do_rcv

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
	struct sock *rsk;

	//当状态为ESTABLISHED时,用tcp_rcv_established()接收处理
	if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
		struct dst_entry *dst = sk->sk_rx_dst;

		sock_rps_save_rxhash(sk, skb);
		sk_mark_napi_id(sk, skb);
		if (dst) {
			if (inet_sk(sk)->rx_dst_ifindex != skb->skb_iif ||
			    !dst->ops->check(dst, 0)) {
				dst_release(dst);
				sk->sk_rx_dst = NULL;
			}
		}
		//连接已建立时的处理路径
		tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len);
		return 0;
	}

	if (tcp_checksum_complete(skb))
		goto csum_err;

	//如果这个sock处于监听状态,被动打开时的处理,包括收到SYN或ACK
	if (sk->sk_state == TCP_LISTEN) {
		struct sock *nsk = tcp_v4_cookie_check(sk, skb);
		/*
		NULL,错误
		nsk == sk,没有找到新的TCB,所以收到的是第一次握手的SYN
		nsk != sk,找到了新的TCB,所以收到的是第三次握手的ACK
		*/
		if (!nsk)
			goto discard;
		if (nsk != sk) {
			sock_rps_save_rxhash(nsk, skb);
			sk_mark_napi_id(nsk, skb);
			if (tcp_child_process(sk, nsk, skb)) { //处理新的sock ,初始化子传输控制块
				rsk = nsk;
				goto reset; //失败时,给客户端发送 RST 段进行复位
			}
			return 0;
		}
	} else
		sock_rps_save_rxhash(sk, skb);
	
	//处理除了ESTABLISHED和TIME_WAIT之外的所有状态,处于TCP_LISTEN状态,也是有该函数处理
	if (tcp_rcv_state_process(sk, skb)) {
		rsk = sk;
		goto reset;
	}
	return 0;

reset:
	tcp_v4_send_reset(rsk, skb);//发送被动的RST包
discard:
	kfree_skb(skb);
	return 0;

csum_err:
	TCP_INC_STATS(sock_net(sk), TCP_MIB_CSUMERRORS);
	TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS);
	goto discard;
}

tcp_rcv_state_process函数在tcp_input.c中,它实现了TCP 状态机相对核心的一个部分。该函数可以处理除 ESTABLISHED 和 TIME_WAIT 状态以外的情况下的接收过程

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);
	const struct tcphdr *th = tcp_hdr(skb);
	struct request_sock *req;
	int queued = 0;
	bool acceptable;

	switch (sk->sk_state) {
	case TCP_CLOSE://CLOSE 状态的处理代码
		goto discard;

	//当前TCP套接字所在状态是LISTEN,说明这个套接字是一个服务器(server),它在等待一个连接请求
	case TCP_LISTEN://LISTEN 状态的处理代码
		//发送连接复位,因为客户端发送的包里只有syn为1,ack不可能为1,如果为1,则发生错误,需要服务端复位
		if (th->ack)
			return 1;

		//RST:连接由客户端复位,扔掉数据包
		if (th->rst)
			goto discard;

		//客户端来发送的连接请求,调用icsk_af_ops->conn_request函数完成连接请求处理。
		//将TCP连接状态切换至SYN_RECV。icsk_af_ops->conn_request函数指针在TCP协议实例中初始化为tcp_v4_conn_request
		if (th->syn) {
			if (th->fin)
				goto discard;
			rcu_read_lock();
			local_bh_disable();
			acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;//完成连接请求处理
			local_bh_enable();
			rcu_read_unlock();

			if (!acceptable)
				return 1;
			consume_skb(skb);
			return 0;
		}
		goto discard;

	//如果当前套接字状态是SYN_SENT,说明套接字为客户端,它发送了一个SYN数据包请求连接,并将自己设置为SYN_SENT状态
	case TCP_SYN_SENT:
		tp->rx_opt.saw_tstamp = 0;
		//这时我们必须查看输入数据段中的ACK或SYN标志,以确定是否将状态转换到ESTABLISHED
		queued = tcp_rcv_synsent_state_process(sk, skb, th);
		if (queued >= 0)
			return queued;

		/* Do step6 onward by hand. */
		tcp_urg(sk, skb, th);
		__kfree_skb(skb);
		tcp_data_snd_check(sk);
		return 0;
	}

	tp->rx_opt.saw_tstamp = 0;
	req = tp->fastopen_rsk;
	if (req) {
		WARN_ON_ONCE(sk->sk_state != TCP_SYN_RECV &&
		    sk->sk_state != TCP_FIN_WAIT1);

		if (!tcp_check_req(sk, skb, req, true))
			goto discard;
	}

	if (!th->ack && !th->rst && !th->syn)
		goto discard;

	//数据包有效性检查
	if (!tcp_validate_incoming(sk, skb, th, 0))
		return 0;

	//对收到的 ACK 段进行处理判断是否正确接收,如果正确接收就会发送返回非零值
	acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |
				      FLAG_UPDATE_TS_RECENT |
				      FLAG_NO_CHALLENGE_ACK) > 0;
	//如果回答是可接收的数据包,则将TCP连接状态转换到ESTABLISHED状态
	if (!acceptable) {
		//连接处于SYN_RECV
		if (sk->sk_state == TCP_SYN_RECV)
			return 1;	/* send one RST */
		tcp_send_challenge_ack(sk, skb);
		goto discard;
	}
	switch (sk->sk_state) {
	case TCP_SYN_RECV://服务端发送SYN+ACK第二次握手,等待客户端回复ACK第三次握手
		if (!tp->srtt_us)
			tcp_synack_rtt_meas(sk, req);
		if (req) {
			inet_csk(sk)->icsk_retransmits = 0;
			reqsk_fastopen_remove(sk, req, false);
		} else {
			/* Make sure socket is routed, for correct metrics. */
			icsk->icsk_af_ops->rebuild_header(sk);
			tcp_init_congestion_control(sk);

			tcp_mtup_init(sk);
			tp->copied_seq = tp->rcv_nxt;
			tcp_init_buffer_space(sk);
		}
		smp_mb();
		//进行一系列的初始化,开启相应拥塞控制等,并且将 TCP 的状态置为 TCP_ESTABLISHED
		tcp_set_state(sk, TCP_ESTABLISHED);
		sk->sk_state_change(sk);

		//发信号给那些将通过该套接口发送数据的进程,通知它们套接口目前已经可以发送数据了
		if (sk->sk_socket)
			sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
			
		//设置传输控制块tp的发送未确认序列号snd_una为ACK序列号
		//snd_una表示发送端已经发送,但是还没有收到ack的数据的第一个字节
		tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
		//设置发送窗口大小snd_wnd
		tp->snd_wnd = ntohs(th->window) << tp->rx_opt.snd_wscale;
		//初始化发送窗口的左边界
		tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
		//如果启用了时间戳选项,则调整最大段大小advmss
		if (tp->rx_opt.tstamp_ok)
			tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;

		if (req) {
			tcp_rearm_rto(sk);
		} else
			tcp_init_metrics(sk);

		if (!inet_csk(sk)->icsk_ca_ops->cong_control)
			tcp_update_pacing_rate(sk);
		
		//更新最近一次的发送数据报的时间
		tp->lsndtime = tcp_time_stamp;

		//对端有效发送MSS估值的初始化
		tcp_initialize_rcv_mss(sk);
		tcp_fast_path_on(tp);
		break;

	case TCP_FIN_WAIT1: {//发送FIN+ACK第一次挥手后,等待对方回复ACK第二次挥手
		struct dst_entry *dst;
		int tmo;

		if (req) {
			/* We no longer need the request sock. */
			reqsk_fastopen_remove(sk, req, false);
			tcp_rearm_rto(sk);
		}
		if (tp->snd_una != tp->write_seq)
			break;
		//由FIN_WAIT_1切换到FIN_WAIT_2
		tcp_set_state(sk, TCP_FIN_WAIT2);
		sk->sk_shutdown |= SEND_SHUTDOWN;

		dst = __sk_dst_get(sk);
		if (dst)
			dst_confirm(dst);

		if (!sock_flag(sk, SOCK_DEAD)) {
			sk->sk_state_change(sk);
			break;
		}

		if (tp->linger2 < 0 ||
		    (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
		     after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))) {
			tcp_done(sk);
			NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
			return 1;
		}

		tmo = tcp_fin_time(sk);
		if (tmo > TCP_TIMEWAIT_LEN) {
			inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);
		} else if (th->fin || sock_owned_by_user(sk)) {
			inet_csk_reset_keepalive_timer(sk, tmo);
		} else {
			tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
			goto discard;
		}
		break;
	}

	case TCP_CLOSING:
		if (tp->snd_una == tp->write_seq) {
			//在TCP_CLOSING状态下,收到了ACK后套接字直接进入到TIME_WAIT状态,说明当前已经没有要发生的数据了
			tcp_time_wait(sk, TCP_TIME_WAIT, 0);
			goto discard;
		}
		break;

	case TCP_LAST_ACK:
		// 如果套接字被迫关闭,则响应应用程序的close调用。
        // 在这个状态上接收到ACK意味着可以关闭套接字,所以调用tcp_done函数。
		if (tp->snd_una == tp->write_seq) {
			tcp_update_metrics(sk);
			tcp_done(sk);
			goto discard;
		}
		break;
	}

	//紧急数据处理
	tcp_urg(sk, skb, th);
	//理段中的数据内容
	switch (sk->sk_state) {
	case TCP_CLOSE_WAIT:
	case TCP_CLOSING:
	case TCP_LAST_ACK:
		if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
			break;
	case TCP_FIN_WAIT1:
	case TCP_FIN_WAIT2:
		if (sk->sk_shutdown & RCV_SHUTDOWN) {
			if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
			    after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) {
				NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
				tcp_reset(sk);
				return 1;
			}
		}
	//套接字为ESTABLISHED状态时收到常规数据段的处理,它调用tcp_data_queue函数把数据段放入套接字的输入缓冲队列
	case TCP_ESTABLISHED://在建立连接阶段一般不会收到 TCP 段
		tcp_data_queue(sk, skb);
		queued = 1;
		break;
	}

	//此时状态不为 CLOSE,故而就回去检测是否数据和 ACK 要发送。
	//其次,根据 queue 标志来确定是否释放接收到的 TCP 段,如果接收到的 TCP 段已添加到接收队列中,则不释放
	if (sk->sk_state != TCP_CLOSE) {
		tcp_data_snd_check(sk);
		tcp_ack_snd_check(sk);
	}

	if (!queued) {
discard:
		tcp_drop(sk, skb);
	}
	return 0;
}

如果客户端发送的第一次握手携带数据,那么服务端将返回一个rst包
如果客户端发送的第一次握手是rst,那么服务端将直接丢弃

不管是服务端还是客户端,在创建socket的时候都会调用到sk_alloc(),在这个函数内核会对sock中的icsk_af_ops赋值,sk->icsk_af_ops=&ipv4_specific,ipv4_specific则在tcp_ipv4.c里

在这里插入图片描述

icsk->icsk_af_ops->conn_request(sk, skb)其实就是调用的tcp_v4_conn_request

tcp_v4_conn_request源码

int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
	/* Never answer to SYNs send to broadcast or multicast */
	if (skb_rtable(skb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
		goto drop;

	return tcp_conn_request(&tcp_request_sock_ops,
				&tcp_request_sock_ipv4_ops, sk, skb);

drop:
	tcp_listendrop(sk);
	return 0;
}

tcp_conn_request源码

int tcp_conn_request(struct request_sock_ops *rsk_ops,
		     const struct tcp_request_sock_ops *af_ops,
		     struct sock *sk, struct sk_buff *skb)
{
	struct tcp_fastopen_cookie foc = { .len = -1 };
	__u32 isn = TCP_SKB_CB(skb)->tcp_tw_isn;
	struct tcp_options_received tmp_opt;
	struct tcp_sock *tp = tcp_sk(sk);
	struct net *net = sock_net(sk);
	struct sock *fastopen_sk = NULL;
	struct dst_entry *dst = NULL;
	struct request_sock *req;
	bool want_cookie = false;
	struct flowi fl;

	//检测到可能的SYN洪水攻击时,通过生成SYN Cookie来减轻攻击的影响
	if ((net->ipv4.sysctl_tcp_syncookies == 2 ||
	     inet_csk_reqsk_queue_is_full(sk)) && !isn) {
		want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name);
		if (!want_cookie)
			goto drop;
	}


	//如果accept接收队列已满,并且SYN请求队列中至少有一个请求还没有重传过SYWN+ACK包,则丢弃该新的SYN请求
	//个人理解这样设计的考虑是:因为SYN请求队列中有这种"年轻的SYN请求”,而且当前accept队列已满,
	//那么这种年轻的SYN请求很可能很快就会完成三次握手,进而需要添加到accept队列中,
	//所以此时如果接受该新的SYN请求,那么很可能会导致由于无法加入到accept队列而导致已经完成三次握手的TCP连接失败
	if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
		NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
		goto drop;
	}

	//分配struct tcp_reqeust_sock对象(表示半链接),并将tcp_request_sock_ops赋值给其rsk_ops
	//后续连接建立过程中会调用该结构指定的函数,
	req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie);
	if (!req)
		goto drop;

	tcp_rsk(req)->af_specific = af_ops;

	//解析SYN包携带的TCP选项
	tcp_clear_options(&tmp_opt);
	tmp_opt.mss_clamp = af_ops->mss_clamp;
	tmp_opt.user_mss  = tp->rx_opt.user_mss;
	tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc);

	//SYN cookie相关
	if (want_cookie && !tmp_opt.saw_tstamp)
		tcp_clear_options(&tmp_opt);

	//时间戳选项处理
	tmp_opt.tstamp_ok = tmp_opt.saw_tstamp;
	//根据SYN请求段中的字段和选项来初始化连接请求块
	tcp_openreq_init(req, &tmp_opt, skb, sk);
	inet_rsk(req)->no_srccheck = inet_sk(sk)->transparent;

	inet_rsk(req)->ir_iif = inet_request_bound_dev_if(sk, skb);

	af_ops->init_req(req, sk, skb);

	if (security_inet_conn_request(sk, skb, req))
		goto drop_and_free;

	//根据不同情况生成服务器端的初始发送序号
	if (!want_cookie && !isn) {
		if (tcp_death_row.sysctl_tw_recycle) {
			bool strict;

			dst = af_ops->route_req(sk, &fl, req, &strict);

			if (dst && strict &&
			    !tcp_peer_is_proven(req, dst, true,
						tmp_opt.saw_tstamp)) {
				NET_INC_STATS(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
				goto drop_and_release;
			}
		}
		else if (!net->ipv4.sysctl_tcp_syncookies &&
			 (sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
			  (sysctl_max_syn_backlog >> 2)) &&
			 !tcp_peer_is_proven(req, dst, false,
					     tmp_opt.saw_tstamp)) {

			pr_drop_req(req, ntohs(tcp_hdr(skb)->source),
				    rsk_ops->family);
			goto drop_and_release;
		}

		isn = af_ops->init_seq(skb);
	}
	if (!dst) {
		dst = af_ops->route_req(sk, &fl, req, NULL);
		if (!dst)
			goto drop_and_free;
	}

	tcp_ecn_create_request(req, skb, sk, dst);

	if (want_cookie) {
		isn = cookie_init_sequence(af_ops, sk, skb, &req->mss);
		req->cookie_ts = tmp_opt.tstamp_ok;
		if (!tmp_opt.tstamp_ok)
			inet_rsk(req)->ecn_ok = 0;
	}

	//将确定的初始序列号记录到TCP控制块中
	tcp_rsk(req)->snt_isn = isn;
	tcp_rsk(req)->txhash = net_tx_rndhash();
	tcp_openreq_init_rwin(req, sk, dst);
	if (!want_cookie) {
		tcp_reqsk_record_syn(sk, req, skb);
		fastopen_sk = tcp_try_fastopen(sk, skb, req, &foc, dst);
	}
	//如果开启了fastopen,直接加入到全链接队列里(accept队列)
	if (fastopen_sk) {
		af_ops->send_synack(fastopen_sk, dst, &fl, req,
				    &foc, TCP_SYNACK_FASTOPEN);
		/* Add the child socket directly into the accept queue */
		inet_csk_reqsk_queue_add(sk, req, fastopen_sk);
		sk->sk_data_ready(sk);
		bh_unlock_sock(fastopen_sk);
		sock_put(fastopen_sk);
	} else {
		tcp_rsk(req)->tfo_listener = false;
		if (!want_cookie)
			//把新创建的半连接加入到半链接队列里面,并启动SYN+ACK超时重传定时器
			inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
		//会调用到tcp_ipv4.c中的tcp_v4_send_synack函数,从而构建synack包,并且发送回去
		af_ops->send_synack(sk, dst, &fl, req, &foc,
				    !want_cookie ? TCP_SYNACK_NORMAL :
						   TCP_SYNACK_COOKIE);
		if (want_cookie) {
			reqsk_free(req);
			return 0;
		}
	}
	reqsk_put(req);
	return 0;

drop_and_release:
	dst_release(dst);
drop_and_free:
	reqsk_free(req);
drop:
	tcp_listendrop(sk);
	return 0;
}

inet_csk_reqsk_queue_hash_add函数

void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
				   unsigned long timeout)
{
	reqsk_queue_hash_req(req, timeout); //把半连接插入到ehash中
	inet_csk_reqsk_queue_added(sk); //
}


static void reqsk_queue_hash_req(struct request_sock *req,
				 unsigned long timeout)
{
	req->num_retrans = 0;
	req->num_timeout = 0;
	req->sk = NULL;

	setup_pinned_timer(&req->rsk_timer, reqsk_timer_handler,
			    (unsigned long)req);
	mod_timer(&req->rsk_timer, jiffies + timeout);
	
	//将半连接所对应的sock插入到全局的ehash中
	inet_ehash_insert(req_to_sk(req), NULL);

	smp_wmb();
	atomic_set(&req->rsk_refcnt, 2 + 1);
}

static inline void inet_csk_reqsk_queue_added(struct sock *sk)
{
	reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue);
}

static inline void reqsk_queue_added(struct request_sock_queue *queue)
{
	atomic_inc(&queue->young);//SYN请求队列中至少有一个请求还没有重传过SYN+ACK包的半连接个数加1
	atomic_inc(&queue->qlen);//半连接队列的长度加1
}

//用来表示监听套接字下的连接
struct request_sock_queue {
	spinlock_t		rskq_lock;
	u8			rskq_defer_accept;

	u32			synflood_warned;
	atomic_t		qlen;  //半链接队列sk个数
	atomic_t		young; //还没有重传过syn+ack半连接的个数

	struct request_sock	*rskq_accept_head;//全链接的头
	struct request_sock	*rskq_accept_tail;//全链接的尾
	struct fastopen_queue	fastopenq;  
};

客户端收到syn+ack封包的处理过程

客户端收到syn+ack封包时的处理流程和服务端收到syn封包处理的流程大致一样,数据经过网卡,再到ip层,最终会调用到tcp_v4_rcv函数做处理,再调用tcp_v4_do_rcv函数,最后再调用tcp_rcv_state_process(处理tcp的状态机)

因为前面已经谈过tcp_rcv_state_process,这里将省略部分源码

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
	...
	//如果当前套接字状态是SYN_SENT,说明套接字为客户端,它发送了一个SYN数据包请求连接,并将自己设置为SYN_SENT状态
	case TCP_SYN_SENT:
		tp->rx_opt.saw_tstamp = 0;
		//这时我们必须查看输入数据段中的ACK或SYN标志,以确定是否将状态转换到ESTABLISHED
		queued = tcp_rcv_synsent_state_process(sk, skb, th);
		//如果queued>=0,就会返回并发送RST
		if (queued >= 0)
			return queued;

		/* Do step6 onward by hand. */
		tcp_urg(sk, skb, th);
		__kfree_skb(skb);
		tcp_data_snd_check(sk);
		return 0;
	}
	...
}

调用tcp_rcv_synsent_state_process函数做进一步处理

static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
					 const struct tcphdr *th)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	struct tcp_fastopen_cookie foc = { .len = -1 };
	int saved_clamp = tp->rx_opt.mss_clamp;
	bool fastopen_fail;

	//全面解析skb携带的TCP选项 
	tcp_parse_options(skb, &tp->rx_opt, 0, &foc);
	if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr)
		tp->rx_opt.rcv_tsecr -= tp->tsoffset;

	//如果携带ACK标志,那么有可能是SYNACK
	if (th->ack) {
		//检查ack_seq:snd_una < ack_seq <= snd_nxt。
        //如果SYN段没有携带数据,那么此时ack_seq应该为本端的ISN + 1
		if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) ||
		    after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt))
			goto reset_and_undo;

		//如果使用了时间戳选项,那么回显的时间戳,必须落在
		//第一次发送SYN段的时间和当前时间之间。
		if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
		    !between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp,
			     tcp_time_stamp)) {
			NET_INC_STATS(sock_net(sk),
					LINUX_MIB_PAWSACTIVEREJECTED);
			goto reset_and_undo;
		}
		
		//如果既没有RST也没有SYN标志位,那么直接丢弃这个ACK
		if (th->rst) {
			tcp_reset(sk);
			goto discard;
		}

		if (!th->syn)
			goto discard_and_undo;

		//收到一个合法的SYNACK了,接下来要完成连接的建立了
		tcp_ecn_rcv_synack(tp, th);
		//记录最近更新发送窗口的ACK序号 
		tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
		//更新发送窗口,删除发送队列中已被确认的SYN段,并进行时延采样
		tcp_ack(sk, skb, FLAG_SLOWPATH);

		tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;//更新接收窗口的要接收的下一个序号
		tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;//更新接收窗口的左端

		//更新发送窗口,其实就是对端接收窗口的大小。在三次握手时,不使用窗口扩大因子
		tp->snd_wnd = ntohs(th->window);	

		//如果连接不支持窗口扩大因子选项
		if (!tp->rx_opt.wscale_ok) {
			tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0;
			tp->window_clamp = min(tp->window_clamp, 65535U);
		}

		//如果连接支持时间戳选项 
		if (tp->rx_opt.saw_tstamp) {
			tp->rx_opt.tstamp_ok	   = 1;
			tp->tcp_header_len =
				sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
			tp->advmss	    -= TCPOLEN_TSTAMP_ALIGNED;
			tcp_store_ts_recent(tp);//记录对端的时间戳,作为下次发送的回显值
		} else {
			tp->tcp_header_len = sizeof(struct tcphdr);
		}

		//使用SACK时,才能考虑是否使用FACK
		if (tcp_is_sack(tp) && sysctl_tcp_fack)
			tcp_enable_fack(tp);

		tcp_mtup_init(sk);//TCP的MTU初始化
		tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);//更新MSS
		tcp_initialize_rcv_mss(sk);//对端有效发送MSS估值的初始化

		tp->copied_seq = tp->rcv_nxt;//更新未读数据的左端

		smp_mb();

		
        /* 走到这里,连接算是成功建立了,接下来:
         * 把连接的状态设置为TCP_ESTABLISHED。
         * 唤醒调用connect()的进程。
         */
		tcp_finish_connect(sk, skb);

		//Fast Open选项处理 
		fastopen_fail = (tp->syn_fastopen || tp->syn_data) &&
				tcp_rcv_fastopen_synack(sk, skb, &foc);

		if (!sock_flag(sk, SOCK_DEAD)) {
			//指向sock_def_wakeup,唤醒调用connect()的进程
			sk->sk_state_change(sk);
			//如果使用了异步通知,则发送SIGIO通知进程可写
			sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
		}
		if (fastopen_fail)
			return -1;

		
        /* 符合以下任一条件,则使用延迟确认,不会马上发送ACK:
         * 目前有数据等待发送。
         * 使用TCP_DEFER_ACCEPT选项。
         * 延迟确认标志为1。
         */
		if (sk->sk_write_pending ||
		    icsk->icsk_accept_queue.rskq_defer_accept ||
		    icsk->icsk_ack.pingpong) {
			inet_csk_schedule_ack(sk);//设置ICSK_ACK_SCHED标志位,表示有ACK需要发送
			tcp_enter_quickack_mode(sk, TCP_MAX_QUICKACKS);//进入快速确认模式,之后会进行快速确认
			//激活延迟确认定时器,超时时间为200ms,也就是说最多延迟200ms
			inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
						  TCP_DELACK_MAX, TCP_RTO_MAX);

discard:
			tcp_drop(sk, skb);
			return 0;
		} else {
			tcp_send_ack(sk);//立即发送一个ACK,即三次握手的最后一个ACK
		}
		return -1;
	}

	//如果收到的段没有ACK标志,却设置了RST标志,那么直接丢掉
	if (th->rst) {

		goto discard_and_undo;
	}

	//PAWS check. 检查时间戳是否合法
	if (tp->rx_opt.ts_recent_stamp && tp->rx_opt.saw_tstamp &&
	    tcp_paws_reject(&tp->rx_opt, 0))
		goto discard_and_undo;

	//收到了SYN段,即同时打开
	if (th->syn) {
        /* 发送SYN后,状态为SYN_SENT,如果此时也收到SYN,
        * 状态则变为SYN_RECV。
        */
		tcp_set_state(sk, TCP_SYN_RECV);

		if (tp->rx_opt.saw_tstamp) {
			tp->rx_opt.tstamp_ok = 1;
			tcp_store_ts_recent(tp);//记录对端的时间戳,作为下次发送的回显值
			tp->tcp_header_len =
				sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
		} else {
			tp->tcp_header_len = sizeof(struct tcphdr);
		}

		tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;/* 更新接收窗口的要接收的下一个序号 */
		tp->copied_seq = tp->rcv_nxt;
		tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;/* 更新接收窗口的左端 */

		//更新对端接收窗口的大小。在三次握手时,不使用窗口扩大因子
		tp->snd_wnd    = ntohs(th->window);
		tp->snd_wl1    = TCP_SKB_CB(skb)->seq;
		tp->max_window = tp->snd_wnd;

		
        /* 如果对端支持ECN,SYN会同时设置ECE和CWR标志。
        * 否则,连接就不支持ECN显式拥塞通知了。
        */
		tcp_ecn_rcv_syn(tp, th);

		tcp_mtup_init(sk);/* TCP的MTU初始化 */
		tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); /* 更新MSS */
		tcp_initialize_rcv_mss(sk);/* 对端有效发送MSS估值的初始化 */
		/* 构造和发送SYNACK */
		tcp_send_synack(sk);
#if 0
		return -1;
#else
		goto discard;
#endif
	}

discard_and_undo:
	tcp_clear_options(&tp->rx_opt);
	tp->rx_opt.mss_clamp = saved_clamp;
	goto discard;

reset_and_undo:
	tcp_clear_options(&tp->rx_opt);
	tp->rx_opt.mss_clamp = saved_clamp;
	return 1;
}

总的来说tcp_rcv_synsent_state_process()用于SYN_SENT状态的处理,具体分以下几种情况

①收到的是syn+ack(合法)
检查ack_seq是否合法,如果使用了时间戳选项,检查回显的时间戳是否合法,检查TCP的标志位是否合法。如果都合法,更新sock的各种信息。把连接的状态设置为TCP_ESTABLISHED,唤醒调用connect()的进程。判断是马上发送ACK,还是延迟发送。

②收到的是syn+ack(合法)
也就是ack_seq不是本机的seq+1,或者如果使用了时间戳选项,syn+ack的时间戳不在本机第一次发送syn的时间和当前时间之间,该函数会返回1,上层会发送一个RST包

收到的是syn
把连接状态置为SYN_RECV,更新sock的各种信息,构造和发送syn+ack包

③收到的是rst+ack
则向用户发送error,然后进行链接重置

④收到的是rst
直接丢弃

⑤收到的是ack
直接丢弃

收到的是syn+ack(合法),最终会调用void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)函数将连接状态置为TCP_ESTABLISHED

void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);

	//连接状态从SYN_SENT变为ESTABLISHED
	tcp_set_state(sk, TCP_ESTABLISHED);
	icsk->icsk_ack.lrcvtime = tcp_time_stamp;

	if (skb) {
		icsk->icsk_af_ops->sk_rx_dst_set(sk, skb);
		security_inet_conn_established(sk, skb);
	}

	/* Make sure socket is routed, for correct metrics.  */
	icsk->icsk_af_ops->rebuild_header(sk);

	//根据路由缓存,初始化TCP相关变量
	tcp_init_metrics(sk);

	//获取默认的TCP拥塞控制算法
	tcp_init_congestion_control(sk);

	//更新最近一次的发送数据报的时间
	tp->lsndtime = tcp_time_stamp;

	//调整发送缓存和接收缓存的大小
	tcp_init_buffer_space(sk);

	//如果使用了SO_KEEPALIVE选项,激活保活定时器
	if (sock_flag(sk, SOCK_KEEPOPEN))
		inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp));

	//如果对端的窗口扩大因子为0
	if (!tp->rx_opt.snd_wscale)
		__tcp_fast_path_on(tp, tp->snd_wnd);
	else
		tp->pred_flags = 0;
}

服务端收到第三次握手(ACK)如何处理

服务端在收到第三次握手之前,所处状态为TCP_SYN_RECV,还是会通过tcp_rcv_state_process函数处理

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
	...
	case TCP_SYN_RECV://服务端发送SYN+ACK第二次握手,等待客户端回复ACK第三次握手
		if (!tp->srtt_us)
			tcp_synack_rtt_meas(sk, req);

		if (req) {
			inet_csk(sk)->icsk_retransmits = 0;
			reqsk_fastopen_remove(sk, req, false);
		} else {
			icsk->icsk_af_ops->rebuild_header(sk);
			tcp_init_congestion_control(sk);

			tcp_mtup_init(sk);
			tp->copied_seq = tp->rcv_nxt;
			tcp_init_buffer_space(sk);
		}
		smp_mb();
		//进行一系列的初始化,开启相应拥塞控制等,并且将 TCP 的状态置为 TCP_ESTABLISHED
		tcp_set_state(sk, TCP_ESTABLISHED);
		sk->sk_state_change(sk);

		//发信号给那些将通过该套接口发送数据的进程,通知它们套接口目前已经可以发送数据了
		if (sk->sk_socket)
			sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
			
		//设置传输控制块tp的发送未确认序列号snd_una为ACK序列号
		//snd_una表示发送端已经发送,但是还没有收到ack的数据的第一个字节
		tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
		//设置发送窗口大小snd_wnd
		tp->snd_wnd = ntohs(th->window) << tp->rx_opt.snd_wscale;
		//初始化发送窗口的左边界
		tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
		//如果启用了时间戳选项,则调整最大段大小advmss
		if (tp->rx_opt.tstamp_ok)
			tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;

		if (req) {
			tcp_rearm_rto(sk);
		} else
			tcp_init_metrics(sk);

		if (!inet_csk(sk)->icsk_ca_ops->cong_control)
			tcp_update_pacing_rate(sk);

		//更新最近一次的发送数据报的时间
		tp->lsndtime = tcp_time_stamp;

		//对端有效发送MSS估值的初始化
		tcp_initialize_rcv_mss(sk);
		tcp_fast_path_on(tp);
		break;
	...
}

5、整体流程图

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值