文章目录
- 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对应的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 或 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 小于给定的最小的 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;
...
}