假定客户端主动打开,发送syn包到服务器,服务器创建连接请求控制块加入到队列,进入TCP_NEW_SYN_RECV 状态,发送syn+ack给客户端,并启动定时器,等待客户端回复最后一个握手ack;

tcp_v4_rcv上来的包,会判断连接状态,当状态为TCP_NEW_SYN_RECV时,期望得到对端发来的ack,以完成三次握手正式建立连接;函数通过调用tcp_check_req处理ack,成功会返回新建的子控制块,然后调用tcp_child_process进行进一步的处理,包括更新状态为已连接状态,通知正在等待的应用程序等;

 int tcp_v4_rcv(struct sk_buff *skb)
{
/* 省略一些无关代码 */ 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;
} /* 不是listen状态 */
if (unlikely(sk->sk_state != TCP_LISTEN)) {
/* 从连接队列移除控制块 */
inet_csk_reqsk_queue_drop_and_put(sk, req); /* 根据skb参数重新查找控制块 */
goto lookup;
}
/* We own a reference on the listener, increase it again
* as we might lose it too soon.
*/
sock_hold(sk);
refcounted = true; /* 处理第三次握手ack,成功返回新控制块 */
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)) {
/* 失败发送rst */
tcp_v4_send_reset(nsk, skb);
goto discard_and_relse;
} else {
sock_put(sk);
return ;
}
} /* 省略一些无关代码 */
}

tcp_check_req为处理ack的核心流程,除了各种状态的检查之外,最主要的是在状态检查通过之后(1)调用child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, req, &own_req);创建子控制块,这里需要注意,子控制块的状态为TCP_SYN_RECV,这与刚收到syn建立的控制块状态不一样,那时创建的控制块为TCP_NEW_SYN_RECV;然后(2)将请求控制块从未完成连接队列中删除,加入到已完成连接队列中;

 struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
bool fastopen)
{
struct tcp_options_received tmp_opt;
struct sock *child;
const struct tcphdr *th = tcp_hdr(skb);
__be32 flg = tcp_flag_word(th) & (TCP_FLAG_RST|TCP_FLAG_SYN|TCP_FLAG_ACK);
bool paws_reject = false;
bool own_req; tmp_opt.saw_tstamp = ; /* 如果有tcp选项 */
if (th->doff > (sizeof(struct tcphdr)>>)) { /* 解析选项 */
tcp_parse_options(skb, &tmp_opt, , NULL); /* 有时间戳选项处理 */
if (tmp_opt.saw_tstamp) {
tmp_opt.ts_recent = req->ts_recent;
if (tmp_opt.rcv_tsecr)
tmp_opt.rcv_tsecr -= tcp_rsk(req)->ts_off;
/* We do not store true stamp, but it is not required,
* it can be estimated (approximately)
* from another data.
*/
/* 序号回绕检查 */
tmp_opt.ts_recent_stamp = get_seconds() - ((TCP_TIMEOUT_INIT/HZ)<<req->num_timeout);
paws_reject = tcp_paws_reject(&tmp_opt, th->rst);
}
} /* Check for pure retransmitted SYN. */
/* 客户端重传的syn包 */
if (TCP_SKB_CB(skb)->seq == tcp_rsk(req)->rcv_isn &&
flg == TCP_FLAG_SYN &&
!paws_reject) {
/*
* RFC793 draws (Incorrectly! It was fixed in RFC1122)
* this case on figure 6 and figure 8, but formal
* protocol description says NOTHING.
* To be more exact, it says that we should send ACK,
* because this segment (at least, if it has no data)
* is out of window.
*
* CONCLUSION: RFC793 (even with RFC1122) DOES NOT
* describe SYN-RECV state. All the description
* is wrong, we cannot believe to it and should
* rely only on common sense and implementation
* experience.
*
* Enforce "SYN-ACK" according to figure 8, figure 6
* of RFC793, fixed by RFC1122.
*
* Note that even if there is new data in the SYN packet
* they will be thrown away too.
*
* Reset timer after retransmitting SYNACK, similar to
* the idea of fast retransmit in recovery.
*/
/* 限速检查 */
if (!tcp_oow_rate_limited(sock_net(sk), skb,
LINUX_MIB_TCPACKSKIPPEDSYNRECV,
&tcp_rsk(req)->last_oow_ack_time) &&
/* 重新发送syn+ack */
!inet_rtx_syn_ack(sk, req)) { /* 计算超时时间,调整定时器 */
unsigned long expires = jiffies; expires += min(TCP_TIMEOUT_INIT << req->num_timeout,
TCP_RTO_MAX);
if (!fastopen)
mod_timer_pending(&req->rsk_timer, expires);
else
req->rsk_timer.expires = expires;
} /* 处理完毕,无需后续处理 */
return NULL;
} /* Further reproduces section "SEGMENT ARRIVES"
for state SYN-RECEIVED of RFC793.
It is broken, however, it does not work only
when SYNs are crossed. You would think that SYN crossing is impossible here, since
we should have a SYN_SENT socket (from connect()) on our end,
but this is not true if the crossed SYNs were sent to both
ends by a malicious third party. We must defend against this,
and to do that we first verify the ACK (as per RFC793, page
36) and reset if it is invalid. Is this a true full defense?
To convince ourselves, let us consider a way in which the ACK
test can still pass in this 'malicious crossed SYNs' case.
Malicious sender sends identical SYNs (and thus identical sequence
numbers) to both A and B: A: gets SYN, seq=7
B: gets SYN, seq=7 By our good fortune, both A and B select the same initial
send sequence number of seven :-) A: sends SYN|ACK, seq=7, ack_seq=8
B: sends SYN|ACK, seq=7, ack_seq=8 So we are now A eating this SYN|ACK, ACK test passes. So
does sequence test, SYN is truncated, and thus we consider
it a bare ACK. If icsk->icsk_accept_queue.rskq_defer_accept, we silently drop this
bare ACK. Otherwise, we create an established connection. Both
ends (listening sockets) accept the new incoming connection and try
to talk to each other. 8-) Note: This case is both harmless, and rare. Possibility is about the
same as us discovering intelligent life on another plant tomorrow. But generally, we should (RFC lies!) to accept ACK
from SYNACK both here and in tcp_rcv_state_process().
tcp_rcv_state_process() does not, hence, we do not too. Note that the case is absolutely generic:
we cannot optimize anything here without
violating protocol. All the checks must be made
before attempt to create socket.
*/ /* RFC793 page 36: "If the connection is in any non-synchronized state ...
* and the incoming segment acknowledges something not yet
* sent (the segment carries an unacceptable ACK) ...
* a reset is sent."
*
* Invalid ACK: reset will be sent by listening socket.
* Note that the ACK validity check for a Fast Open socket is done
* elsewhere and is checked directly against the child socket rather
* than req because user data may have been sent out.
*/
/* ACK但是序号对不上,返回原有控制块,外面不做处理 */
if ((flg & TCP_FLAG_ACK) && !fastopen &&
(TCP_SKB_CB(skb)->ack_seq !=
tcp_rsk(req)->snt_isn + ))
return sk; /* Also, it would be not so bad idea to check rcv_tsecr, which
* is essentially ACK extension and too early or too late values
* should cause reset in unsynchronized states.
*/ /* RFC793: "first check sequence number". */
/* 无效序号,且接收数据不在窗口范围内 */
if (paws_reject || !tcp_in_window(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq,
tcp_rsk(req)->rcv_nxt, tcp_rsk(req)->rcv_nxt + req->rsk_rcv_wnd)) {
/* Out of window: send ACK and drop. */
/* 如果不是rst,则给对端发送ack */
if (!(flg & TCP_FLAG_RST) &&
!tcp_oow_rate_limited(sock_net(sk), skb,
LINUX_MIB_TCPACKSKIPPEDSYNRECV,
&tcp_rsk(req)->last_oow_ack_time))
req->rsk_ops->send_ack(sk, skb, req);
if (paws_reject)
__NET_INC_STATS(sock_net(sk), LINUX_MIB_PAWSESTABREJECTED);
return NULL;
} /* In sequence, PAWS is OK. */ /* 有时间戳选项,序号合法,则记录时间戳 */
if (tmp_opt.saw_tstamp && !after(TCP_SKB_CB(skb)->seq, tcp_rsk(req)->rcv_nxt))
req->ts_recent = tmp_opt.rcv_tsval; /*如果序号是syn序号,已经在窗口外,清除syn标记 */
if (TCP_SKB_CB(skb)->seq == tcp_rsk(req)->rcv_isn) {
/* Truncate SYN, it is out of window starting
at tcp_rsk(req)->rcv_isn + 1. */
flg &= ~TCP_FLAG_SYN;
} /* RFC793: "second check the RST bit" and
* "fourth, check the SYN bit"
*/
/*
有rst标记或者syn标记,上面已经检查了syn重传包了,
这里有syn一定是问题包,
则需要复位未完成的连接
*/
if (flg & (TCP_FLAG_RST|TCP_FLAG_SYN)) {
__TCP_INC_STATS(sock_net(sk), TCP_MIB_ATTEMPTFAILS);
goto embryonic_reset;
} /* ACK sequence verified above, just make sure ACK is
* set. If ACK not set, just silently drop the packet.
*
* XXX (TFO) - if we ever allow "data after SYN", the
* following check needs to be removed.
*/ /* 上面流程保证了有ack,若没有,直接返回 */
if (!(flg & TCP_FLAG_ACK))
return NULL; /* For Fast Open no more processing is needed (sk is the
* child socket).
*/
if (fastopen)
return sk; /* While TCP_DEFER_ACCEPT is active, drop bare ACK. */
/* 设置了DEFER_ACCEPT,直接丢弃该ack,后面有数据的包在建立连接 */
if (req->num_timeout < inet_csk(sk)->icsk_accept_queue.rskq_defer_accept &&
TCP_SKB_CB(skb)->end_seq == tcp_rsk(req)->rcv_isn + ) {
inet_rsk(req)->acked = ;
__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPDEFERACCEPTDROP);
return NULL;
} /* OK, ACK is valid, create big socket and
* feed this segment to it. It will repeat all
* the tests. THIS SEGMENT MUST MOVE SOCKET TO
* ESTABLISHED STATE. If it will be dropped after
* socket is created, wait for troubles.
*/
/* ack有效,创建子控制块,注意子控制块的状态为TCP_SYN_RECV */
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL,
req, &own_req);
/* 创建失败 */
if (!child)
goto listen_overflow; sock_rps_save_rxhash(child, skb);
/* 计算三次握手中synack-ack消耗的时间 */
tcp_synack_rtt_meas(child, req);
/* 从未完成队列删除原控制块,加入到已完成队列 */
return inet_csk_complete_hashdance(sk, child, req, own_req); listen_overflow:
/* 服务器原因未建立连接的,打个标记,后续再发送syn+ack */
if (!sysctl_tcp_abort_on_overflow) {
inet_rsk(req)->acked = ;
return NULL;
} embryonic_reset: /* 不合法的syn包,发送rst */
if (!(flg & TCP_FLAG_RST)) {
/* Received a bad SYN pkt - for TFO We try not to reset
* the local connection unless it's really necessary to
* avoid becoming vulnerable to outside attack aiming at
* resetting legit local connections.
*/
req->rsk_ops->send_reset(sk, skb);
} else if (fastopen) { /* received a valid RST pkt */
reqsk_fastopen_remove(sk, req, true);
tcp_reset(sk);
} /* 从连接请求队列删除控制块 */
if (!fastopen) {
inet_csk_reqsk_queue_drop(sk, req);
__NET_INC_STATS(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);
}
return NULL;
}

tcp_child_process对新控制块进行进一步处理,在控制块未被用户进程锁定的情况下,调用tcp_rcv_state_process进行相关初始化,并将连接状态更新到TCP_ESTABLISHED已连接状态,之后通知等待进程;如果控制块被用户进程锁住,则将数据加入到控制块的后备队列中延后处理;

 /*
* Queue segment on the new socket if the new socket is active,
* otherwise we just shortcircuit this and continue with
* the new socket.
*
* For the vast majority of cases child->sk_state will be TCP_SYN_RECV
* when entering. But other states are possible due to a race condition
* where after __inet_lookup_established() fails but before the listener
* locked is obtained, other packets cause the same connection to
* be created.
*/ int tcp_child_process(struct sock *parent, struct sock *child,
struct sk_buff *skb)
{
int ret = ;
int state = child->sk_state; /* record NAPI ID of child */
sk_mark_napi_id(child, skb); /* 记录数据分段数 */
tcp_segs_in(tcp_sk(child), skb); /* 未被用户层锁住 */
if (!sock_owned_by_user(child)) { /* 子控制块状态的进一步处理 */
ret = tcp_rcv_state_process(child, skb);
/* Wakeup parent, send SIGIO */
/* 唤醒该套接口的等待进程 */
if (state == TCP_SYN_RECV && child->sk_state != state)
parent->sk_data_ready(parent);
}
/* 被用户层锁住,加入后备队列 */
else {
/* Alas, it is possible again, because we do lookup
* in main socket hash table and lock on listening
* socket does not protect us more.
*/
__sk_add_backlog(child, skb);
} bh_unlock_sock(child);
sock_put(child);
return ret;
}

tcp_rcv_state_process对于TCP_SYN_RECV的处理主要是完成连接建立之前的必要初始化,以及将连接状态更新为TCP_ESTABLISHED,通知进程可写入数据,判断并标记快慢路等;其中前后的公共流程,这里没有给出;

 int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
/* step 5: check the ACK field */
acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |
FLAG_UPDATE_TS_RECENT) > ; switch (sk->sk_state) {
case TCP_SYN_RECV: /* ack处理失败 */
if (!acceptable)
return ; /* RTT */
if (!tp->srtt_us)
tcp_synack_rtt_meas(sk, req); /* Once we leave TCP_SYN_RECV, we no longer need req
* so release it.
*/
if (req) {
inet_csk(sk)->icsk_retransmits = ;
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);
/* 路径mtu发现初始化 */
tcp_mtup_init(sk);
/* 用户待读取数据初始化 */
tp->copied_seq = tp->rcv_nxt;
/* 调整接收发送缓存以及窗口等 */
tcp_init_buffer_space(sk);
}
smp_mb(); /* 连接更新为已连接状态 */
tcp_set_state(sk, TCP_ESTABLISHED);
sk->sk_state_change(sk); /* Note, that this wakeup is only for marginal crossed SYN case.
* Passively open sockets are not waked up, because
* sk->sk_sleep == NULL and sk->sk_socket == NULL.
*/
/* 通知进程可以发送数据 */
if (sk->sk_socket)
sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT); /* 初始化窗口相关字段 */
tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
tp->snd_wnd = ntohs(th->window) << tp->rx_opt.snd_wscale;
tcp_init_wl(tp, TCP_SKB_CB(skb)->seq); /* 如果有时间戳,mss减去时间戳选项长度 */
if (tp->rx_opt.tstamp_ok)
tp->advmss -= TCPOLEN_TSTAMP_ALIGNED; if (req) {
/* Re-arm the timer because data may have been sent out.
* This is similar to the regular data transmission case
* when new data has just been ack'ed.
*
* (TFO) - we could try to be more aggressive and
* retransmitting any data sooner based on when they
* are sent out.
*/
tcp_rearm_rto(sk);
}
/* 根据路由缓存信息初始化控制块 */
else
tcp_init_metrics(sk); if (!inet_csk(sk)->icsk_ca_ops->cong_control)
tcp_update_pacing_rate(sk); /* Prevent spurious tcp_cwnd_restart() on first data packet */
tp->lsndtime = tcp_time_stamp; /* 初始化rcv_mss */
tcp_initialize_rcv_mss(sk); /* 快路检查和标记 */
tcp_fast_path_on(tp);
break;
}

TCP被动打开 之 第三次握手-接收ACK的更多相关文章

  1. TCP主动打开 之 第三次握手-发送ACK

    假定客户端执行主动打开,并且已经收到服务器发送的第二次握手包SYN+ACK,在经过一系列处理之后,客户端发送第三次握手包ACK到服务器:其流程比较简单,主要是分配skb,初始化ack包并发送:需要注意 ...

  2. TCP常见的定时器及三次握手与四次挥手

    1.TCP常见的定时器 在TCP协议中有的时候需要定期或者按照某个算法对某个事件进行触发,那么这个时候,TCP协议是使用定时器进行实现的.在TCP中,会有七种定时器: 建立连接定时器(connecti ...

  3. 为什么TCP建立连接协议是三次握手,而关闭连接却是四次握手呢?

    看到了一道面试题:"为什么TCP建立连接协议是三次握手,而关闭连接却是四次握手呢?为什么不能用两次握手进行连接?",想想最近也到金三银四了,所以就查阅了相关资料,整理出来了这篇文章 ...

  4. 为什么 TCP 连接的建立需要三次握手

    TCP 的通讯双方需要发送 3 个包(即:三次握手)才能建立连接,本文将通过 3 副图来解释为什么需要 3 次握手才能建立连接. TCP 连接的建立过程本质是通信双方确认自己和对方都具有通信能力的过程 ...

  5. 【Java面试】TCP协议为什么要设计三次握手?

    一个工作5年的粉丝,最近去面试了很多公司,每次都被各种技术原理题问得语无伦次. 由于找了快1个月时间的工作,有点焦虑,来向我求助. 我能做的只是保证每天更新一个面试题,然后问他印象最深刻的一个面试题是 ...

  6. TCP被动打开 之 第一次握手-接收SYN

    假定客户端执行主动打开,服务器执行被动打开,客户端发送syn包到服务器,服务器接收该包,进行建立连接请求的相关处理,即第一次握手:本文主要分析第一次握手中被动打开端的处理流程,主动打开端的处理请查阅本 ...

  7. TCP的十一种状态与三次握手分析(有图)

    我们知道TCP是面向连接的,我们只知道有连接断开,其实内部还有一些比较复杂的状态.去了解各个状态之间的切换有助于我们更加深入的了解TCP.下面我们就来分析各个状态. 1.如下图示(图源百度)图中显示出 ...

  8. TCP连接的建立(三次握手和四次挥手)

    写到最后发现我描述的挺水的,这个老哥的用语比较专业一点https://blog.csdn.net/qq_38950316/article/details/81087809  (老哥这篇有些许错别字 大 ...

  9. 小故事理解TCP/IP连接时的三次握手

    在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接,示意图如下: 下面通过一个小故事简单理解一下这三次握手的具体含义: 一天,快递员小客(客户端)准备去小服(服务器)家去送快递(准备与服务 ...

随机推荐

  1. CTF 常见操作总结

    一般流程 首先看header, veiwsource, 目录扫描 有登陆, 尝试sql注入&爆破 有数据库, 必然sql注入? 普通sql注入 判断是否存在回显异常 尝试单双引号 查是字符型? ...

  2. javascript的隐式类型转换(使(a==1&&a==2&&a==3) 成立)

    一些团队规定禁用 == 运算符换用=== 严格相等.以工程标准衡量,== 带来的便利性抵不上其带来的成本,团队协作时候你看到别人代码中的 ==,有些时候需要判断清楚作者的代码意图是确实需要转型,还是无 ...

  3. vue中ref-父主动取值值;

    多用月input标签 定义的时候 直接写ref=“id” <el-input placeholder="请输入内容" style="width: 150px&quo ...

  4. easyui-datagrid 编辑模式详解——combobox

    用于列表显示号了,需要改动某一列的值,而且根据每一行的数据去加载data数据,放在这个列中供别人选择 //-------------------- 代码可变区//---------- 数据定义区var ...

  5. 使用PyQt5自制文件查找工具,并生成EXE文件

    一.工作中,有一个关键词查找工作,查找开发版本中使用的文本,有哪些词语是非法的,一个一个去查太累了,所以想到了用代码来实现.可后来想想,能否做成简单的小工具,大家都可以使用. 于是就着手编写工具.原来 ...

  6. Linux :ssh sftp scp

    SSH 概述 1 SSH协议,Secure Shell ,为客户提供安全的shel环境,默认端口22 OpenSSH服务 服务名称:sshd 主程序:/usr/bin/sshd    /usr/bin ...

  7. 使用cJSON解析JSON

    cJSON获取数组元素的每个值 { "operType": 0x5, "field": ["time","matchRule&qu ...

  8. 第11章 Spring Boot使用Actuator

    在生产环境中,需要实时或定期监控服务的可用性,spring-Boot的Actuator 功能提供了很多监控所需的接口. Actuator是Spring Boot提供的对应用系统的自省和监控的集成功能, ...

  9. PLSQL功能一览(1/2)

    用了Oracle几年了,除了PLSQL几乎就没用过别的工具.临时起义想看看PLSQL有哪些功能是我平时没注意的,别是一直有好办法,我却用着笨办法. 本文针对PLSQL12.0.7 1.登录以后使用My ...

  10. HNOI 世界树 虚树

    //virtual tree /*Huyyt*/ #include<bits/stdc++.h> #define mem(a,b) memset(a,b,sizeof(a)) #defin ...