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

文章目录

  • 1、bind 源码剖析
  • 2、listen 源码剖析
  • 3、accept 源码剖析
  • 4、connect 源码剖析
    • 客户端调用connect成功,但三次握手并未完成,进程是如何阻塞自己
    • 客户端在connect时,如何选择源端口
    • 客户发送syn封包以及重传
    • 服务端收到syn封包,如何处理
    • 客户端收到syn+ack封包的处理过程
    • 服务端收到第三次握手(ACK)如何处理
  • 5、整体流程图

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对应的filestruct fd f = fdget(fd);struct socket *sock;*err = -EBADF;if (f.file) {//通过file获取socksock = 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获取socketsock = 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);//面向连接的sockstruct 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状态,所以不可能有链接到来,因此当前全链接队列的大小为0inet_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;}//如果失败,那么就把状态该为closesk->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 获取对应的socketsock = sockfd_lookup_light(fd, &err, &fput_needed);if (!sock)goto out;err = -ENFILE;newsock = sock_alloc(); //为新链接申请socketif (!newsock)goto out_put;//将listen fd所对应的socket的type和ops赋值给新链接的socketnewsock->type = sock->type;newsock->ops = sock->ops;__module_get(newsock->ops->owner);//为新链接申请fdnewfd = 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的sockint err = -EINVAL;//sk2是新链接的sock//调用tcp实现的accept获取sock赋值给listen的sock//tcp.ipv4.c中的inet_csk_acceptstruct 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-->sockstruct 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--; 全链接队列的个数减1queue->rskq_accept_head = req->dl_next; //rskq_accept_head=rskq_accept_head->dl_next 删除队头元素//如果只有一个元素,取完之后,头尾都是NULLif (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获得与之对应socketsock = 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_structreturn 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_CLOSEtcp_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:表示该连接的sockstruct inet_bind_hashbucket	*bhash; //存放所有绑定某端号的连接,key:本地端口,value:绑定该端口的sockstruct 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的sockstruct 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; //28232if (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:绑定该端口的sockhead = &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赋值给twif (!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_buffbuff = 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_DEBUGpr_debug("reset_xmit_timer: sk=%p %d when=0x%lx, caller=%p\n",sk, what, when, current_text_addr());
#endifwhen = 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_DEBUGelse {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);//暂时不考虑fastopenif (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;elsemib_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;elsemib_idx = LINUX_MIB_TCPRENOFAILURES;} else {mib_idx = LINUX_MIB_TCPTIMEOUTS;}__NET_INC_STATS(sock_net(sk), mib_idx);}//切换拥塞状态为LOSStcp_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_rtoicsk->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 或 bhash 散列表中根据地址和端口来查找传输控制块。如果在 ehash 中找到,则表示已经经历了三次握手并且已建立了连接,可以进行正常的通信。如果在 bhash 中找到,则表示已经绑定已经绑定了端口,处于侦听状态。如果在两个散列表中都查找不到,说明此时对应的传输控制块还没有创建,跳转到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 小于给定的最小的 ttlif (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;}//先持锁,这样进程上下文和其它软中断则无法操作该TCBsk_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或ACKif (sk->sk_state == TCP_LISTEN) {struct sock *nsk = tcp_v4_cookie_check(sk, skb);/*NULL,错误nsk == sk,没有找到新的TCB,所以收到的是第一次握手的SYNnsk != 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;}} elsesock_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_requestif (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标志,以确定是否将状态转换到ESTABLISHEDqueued = 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_RECVif (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_ESTABLISHEDtcp_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_wndtp->snd_wnd = ntohs(th->window) << tp->rx_opt.snd_wscale;//初始化发送窗口的左边界tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);//如果启用了时间戳选项,则调整最大段大小advmssif (tp->rx_opt.tstamp_ok)tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;if (req) {tcp_rearm_rto(sk);} elsetcp_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_2tcp_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包的半连接个数加1atomic_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标志,以确定是否将状态转换到ESTABLISHEDqueued = tcp_rcv_synsent_state_process(sk, skb, th);//如果queued>=0,就会返回并发送RSTif (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标志,那么有可能是SYNACKif (th->ack) {//检查ack_seq:snd_una < ack_seq <= snd_nxt。//如果SYN段没有携带数据,那么此时ack_seq应该为本端的ISN + 1if (!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标志位,那么直接丢弃这个ACKif (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时,才能考虑是否使用FACKif (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);//更新MSStcp_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,也就是说最多延迟200msinet_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 0return -1;
#elsegoto 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变为ESTABLISHEDtcp_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));//如果对端的窗口扩大因子为0if (!tp->rx_opt.snd_wscale)__tcp_fast_path_on(tp, tp->snd_wnd);elsetp->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_ESTABLISHEDtcp_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_wndtp->snd_wnd = ntohs(th->window) << tp->rx_opt.snd_wscale;//初始化发送窗口的左边界tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);//如果启用了时间戳选项,则调整最大段大小advmssif (tp->rx_opt.tstamp_ok)tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;if (req) {tcp_rearm_rto(sk);} elsetcp_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、整体流程图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/765559.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法公式汇总

文章目录 三角函数定义式诱导公式平方关系两角和与差的三角函数积化和差公式和差化积公式倍角公式半角公式万能公式其他公式反三角函数恒等式 三角函数定义式 三角函数 定义式 余切&#xff1a; c o t A 1 t a n A \text { 余切&#xff1a;} \ cotA \frac{1}{tanA} 余切&a…

x-zse-96安卓端纯算,魔改AES还原

两天前发了一个x-zse-96的文章,当时遇到了点问题,只分析到了最后一个白盒AES函数里面,并且当时用dfa攻击还原出了秘钥,IV也确定了,但是加密结果不对,本来打算把下文鸽掉的,因为当时unidbg没跑起来,用frida去hook白盒AES中的每一行汇编有点麻烦,没有unidbg方便.后来小白大佬说un…

内网渗透(一)必须了解Windows工作组

★★免责声明★★ 文章中涉及的程序(方法)可能带有攻击性&#xff0c;仅供安全研究与学习之用&#xff0c;读者将信息做其他用途&#xff0c;由Ta承担全部法律及连带责任&#xff0c;文章作者不承担任何法律及连带责任。 1、内网渗透测试简介 内网也叫局域网&#xff0c;是指在…

《自动机理论、语言和计算导论》阅读笔记:p1-p4

《自动机理论、语言和计算导论》学习第1天&#xff0c;p1-p4&#xff0c;总计4页。这只是个人的学习记录&#xff0c;因为很多东西不懂&#xff0c;难免存在理解错误的地方。 一、技术总结 1.有限自动机(finite automata)示例 1.software for checking digital circuits。 …

工作需求,Vue实现登录

加油&#xff0c;新时代打工人&#xff01; vue 2.x Element UI <template><div class"body" :style"{background-image: url(${require(/assets/images/login.png)})}"><el-form :rules"rules" ref"loginForm" :mode…

MySQL 中的索引

MySQL 中的索引 一、索引的创建和删除1.主键会自动添加索引2.unique 约束的字段自动添加索引3.给指定的字段添加索引4.删除指定索引5.查询表上的索引 二、索引的分类三、MySQL索引采用了B树数据结构1.B树的经典面试题 四、其他索引及相关调优1.Hash索引2.聚集索引和非聚集索引3…

【linux线程(四)】初识线程池手撕线程池

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux线程池 1. 前言2. 什么是…

Python 从0开始 一步步基于Django创建项目(3)使用Admin site管理数据模型

本文内容建立在《Python 从0开始 一步步基于Django创建项目&#xff08;2&#xff09;创建应用程序&数据模型》的基础上。 Django提供的admin site&#xff0c;使得网站管理员&#xff0c;能够轻松管理网站的数据模型。 本文首先创建‘管理员账户’&#xff0c;即超级用户…

华为OD机22道试题

华为OD机试题 2.查找小朋友的好朋友位置 在学校中&#xff0c;N 个小朋友站成一队&#xff0c;第 i 个小朋友的身高为 height[i]&#xff0c;第 i 个小朋友可以看到第一个比自己身高更高的小朋友j&#xff0c;那么 j 是 i 的好朋友 (要求&#xff1a;j>i) 。 请重新生成一个…

202305 CSP认证

202305-1 重复局面 第一题直接干 #include<bits/stdc.h> using namespace std; unordered_map<string, int> chess; int main() {ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);string line, str ""; int n;cin >> n;while(n --){str …

数据结构:链式队列

1.设计思想&#xff1a; 我们可以设计出以上五种队列&#xff0c;但是基于时间复杂度&#xff0c;和空间复杂度的最优解&#xff0c;我们选择入队和出队均为O(1)的&#xff0c;也就是第五种 2.结构设计 typedef struct LPNode//数据节点 {int data;//数据struct LPNode* next…

Redis消息队列与thinkphp/queue操作

业务场景 场景一 用户完成注册后需要发送欢迎注册的问候邮件、同时后台要发送实时消息给用户对应的业务员有新的客户注册、最后将用户的注册数据通过接口推送到一个营销用的第三方平台。 遇到两个问题&#xff1a; 由于代码是串行方式&#xff0c;流程大致为&#xff1a;开…

基于STC12C5A60S2系列1T 8051单片机可编程计数阵列CCP/PCA/PWM模块的捕获模式(外部中断)应用

基于STC12C5A60S2系列1T 8051单片机可编程计数阵列CCP/PCA/PWM模块的捕获模式(外部中断)应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍STC12C5A60S2系列1T 805…

【Langchain-Chatchat】部署ChatGLM3-6B-32K教程

介绍 Langchain-Chatchat这个框架可以帮助我们更容易的部署大语言模型&#xff0c;之前也写过ChatGLM传统的部署教程&#xff0c;有兴趣的可以参考 【ChatGLM3】第三代大语言模型多GPU部署指南【ChatGLM2-6B】从0到1部署GPU版本 借助Langchain-Chatchat框架&#xff0c;可以…

32串口学习

基于之前的GPIO等工程&#xff0c;后面的上手难度就简单多了&#xff0c;主要是相关寄存器的设置。 void USART1_Config(void) {GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;/* config USART1 clock */RCC_APB2PeriphClockCmd(RCC_APB2Periph…

计算机网络:信道复用技术概念解析

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

数据结构从入门到精通——希尔排序

希尔排序 前言一、希尔排序( 缩小增量排序 )二、希尔排序的特性总结三、希尔排序动画演示四、希尔排序具体代码实现test.c 前言 希尔排序是一种基于插入排序的算法&#xff0c;通过比较相距一定间隔的元素来工作&#xff0c;各趟比较所用的距离随着算法的进行而减小&#xff0…

【前端】Web API

1.Web API 简介 JS分为三大部分&#xff1a; ESCMScript&#xff1a;基础语法部分DOM API&#xff1a;操作页面结构BOM API&#xff1a;操作浏览器 Web API包含 DOM BOM 2.DOM基本概念 DOM全称 Document Object Mod…

最短路算法

数据结构、算法总述&#xff1a;数据结构/算法 C/C-CSDN博客 目录 朴素dijkstra算法 堆优化版dijkstra算法 Bellman-Ford算法 spfa 算法&#xff08;队列优化的Bellman-Ford算法&#xff09; spfa判断图中是否存在负环 floyd算法 朴素dijkstra算法 思路&#xff1a; 集合…

Linux相关命令(2)

1、W &#xff1a;主要是查看当前登录的用户 在上面这个截图里面呢&#xff0c; 第一列 user &#xff0c;代表登录的用户&#xff0c; 第二列&#xff0c; tty 代表用户登录的终端号&#xff0c;因为在 linux 中并不是只有一个终端的&#xff0c; pts/2 代表是图形界面的第…