大家好,又见面了,我是你们的朋友全栈君。
RESET报文的接收和检查处理。
客户端握手阶段
对于TCP客户端,在发送完SYN报文之后,如果接收到的回复报文同时设置了ACK和RST标志,在检查完ACK的合法性之后,处理RST标志,关闭套接口。对于ACK确认序号,其应当大于第一个未确认序号(snd_una),并且,确认序号不应大于未发送数据的序号(snd_nxt)。
通常情况下ACK确认序号应当等于snd_una加一(SYN占用一个序号),但是,如果SYN报文中带有数据(例如:TFO),ACK确认序号会更大。以上情况向对端发送reset报文,但是,如果当前报文不仅只有ACK标志位,还设置了RST位,将不发送reset报文。
代码语言:javascript复制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);
...
if (th->ack) {
/* rfc793:
* "If the state is SYN-SENT then
* first check the ACK bit
* If the ACK bit is set
* If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send
* a reset (unless the RST bit is set, if so drop
* the segment and return)"
*/
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报文复用了retrans_stamp字段,记录其发送的时间戳。所以回复报文中TCP时间戳选项中返回的时间戳应当位于retrans_stamp和当前时间之间,否则记录PAWS错误。
只有在以上ACK报文判断合法之后,才能检查RST标志位,认为是一个合法的RST,执行关闭连接。
代码语言:javascript复制 if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
!between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp,
tcp_time_stamp(tp))) {
NET_INC_STATS(sock_net(sk),
LINUX_MIB_PAWSACTIVEREJECTED);
goto reset_and_undo;
}
/* Now ACK is acceptable.
*
* "If the RST bit is set
* If the ACK was acceptable then signal the user "error:
* connection reset", drop the segment, enter CLOSED state,
* delete TCB, and return."
*/
if (th->rst) {
tcp_reset(sk);
goto discard;
}
服务端握手阶段
对于TCP服务器端,在接收到三次握手的第三个ACK报文时,由函数tcp_check_req进行检查。在经过序号检查、PAWS检查之后,如果发现此报文设置了TCP_FLAG_RST或者TCP_FLAG_SYN标志位,判断为非法报文,跳转到embryonic_reset。
代码语言:javascript复制struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
struct request_sock *req, bool fastopen, bool *req_stolen)
{
...
/* RFC793: "second check the RST bit" and
* "fourth, check the SYN bit"
*/
if (flg & (TCP_FLAG_RST|TCP_FLAG_SYN)) {
__TCP_INC_STATS(sock_net(sk), TCP_MIB_ATTEMPTFAILS);
goto embryonic_reset;
}
对于TFO,如果仅是错误的设置了SYN标志位,复位当前的连接请求,但是不复位本地的TFO连接。否则,如果设置了RST标志位,需要复位本地的TFO连接。对于非TFO的情况,接收到RST报文,由accept队列(icsk_accept_queue)中删除连接请求结构。
代码语言:javascript复制embryonic_reset:
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);
}
通信阶段
除了以上的reset报文处理,在TCP通信过程中,函数tcp_validate_incoming也将检查报文的RST标志,进行相应处理。与以上的事先检查不同,即使PAWS检查没有通过,RST标志也是有效的。
代码语言:javascript复制static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th, int syn_inerr)
{
struct tcp_sock *tp = tcp_sk(sk);
bool rst_seq_match = false;
/* RFC1323: H1. Apply PAWS check first. */
if (tcp_fast_parse_options(sock_net(sk), skb, th, tp) &&
tp->rx_opt.saw_tstamp &&
tcp_paws_discard(sk, skb)) {
if (!th->rst) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_PAWSESTABREJECTED);
...
goto discard;
}
/* Reset is accepted even if it did not pass PAWS. */
}
如果序号检查没有通过,只有tcp_reset_check检查通过之后,才会处理报文的RST位,复位当前连接。
代码语言:javascript复制 /* Step 1: check sequence number */
if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
/* RFC793, page 37: "In all states except SYN-SENT, all reset
* (RST) segments are validated by checking their SEQ-fields."
* And page 69: "If an incoming segment is not acceptable,
* an acknowledgment should be sent in reply (unless the RST
* bit is set, if so drop the segment and return)".
*/
if (!th->rst) {
...
} else if (tcp_reset_check(sk, skb)) {
tcp_reset(sk);
}
goto discard;
}
即使以上的序号检查通过,还是需要满足以下条件才能复位本地连接:
1) 当前报文的序号等于本地连接下一个要接收的序号; 2) 或者,通过tcp_reset_check函数的检查
代码语言:javascript复制 /* Step 2: check RST bit */
if (th->rst) {
/* RFC 5961 3.2 (extend to match against (RCV.NXT - 1) after a
* FIN and SACK too if available):
* If seq num matches RCV.NXT or (RCV.NXT - 1) after a FIN, or
* the right-most SACK block,
* then
* RESET the connection
* else
* Send a challenge ACK
*/
if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt ||
tcp_reset_check(sk, skb)) {
rst_seq_match = true;
如果以上两个条件都不成立,对于包含SACK块的SACK报文,找到其中所有块中最大的序号,如果最大序号等于报文的序号,也认为是有效的RST报文,复位本地TCP连接。
代码语言:javascript复制 } else if (tcp_is_sack(tp) && tp->rx_opt.num_sacks > 0) {
struct tcp_sack_block *sp = &tp->selective_acks[0];
int max_sack = sp[0].end_seq;
int this_sack;
for (this_sack = 1; this_sack < tp->rx_opt.num_sacks; this_sack) {
max_sack = after(sp[this_sack].end_seq,
max_sack) ?
sp[this_sack].end_seq : max_sack;
}
if (TCP_SKB_CB(skb)->seq == max_sack)
rst_seq_match = true;
}
if (rst_seq_match)
tcp_reset(sk);
复位报文合法性检查函数tcp_reset_check如下,如果复位报文的序号等于待接收序号减一(rcv_nxt – 1),Mac OSX会发生这种情况,在FIN报文之后紧跟一个RST报文,由于在接收到FIN之后,RCV.NXT增加了一,但是Mac OSX发出的这个RST报文与之前FIN具有相同的序号,即RCV.NXT-1。这种情况下,如果套接口状态为TCPF_CLOSE_WAIT、TCPF_LAST_ACK或者TCPF_CLOSING,即确认本地接收到了FIN报文,认为此RST有效。
代码语言:javascript复制/* Accept RST for rcv_nxt - 1 after a FIN.
* When tcp connections are abruptly terminated from Mac OSX (via ^C), a
* FIN is sent followed by a RST packet. The RST is sent with the same
* sequence number as the FIN, and thus according to RFC 5961 a challenge
* ACK should be sent. However, Mac OSX rate limits replies to challenge
* ACKs on the closed socket. In addition middleboxes can drop either the
* challenge ACK or a subsequent RST.
*/
static bool tcp_reset_check(const struct sock *sk, const struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
return unlikely(TCP_SKB_CB(skb)->seq == (tp->rcv_nxt - 1) &&
(1 << sk->sk_state) & (TCPF_CLOSE_WAIT | TCPF_LAST_ACK |
TCPF_CLOSING));
}
连接断开阶段
在连接断开过程中,如果本地已经停止接收(RCV_SHUTDOWN),又接收到数据,当做接收到了reset报文,关闭TCP连接,并发送reset报文到对端。
代码语言:javascript复制int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
...
if (!th->ack && !th->rst && !th->syn)
goto discard;
if (!tcp_validate_incoming(sk, skb, th, 0))
return 0;
...
/* step 7: process the segment text */
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;
/* fall through */
case TCP_FIN_WAIT1:
case TCP_FIN_WAIT2:
/* RFC 793 says to queue data in these states,
* RFC 1122 says we MUST send a reset.
* BSD 4.4 also does reset.
*/
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;
}
}
内核版本 5.0
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/186968.html原文链接:https://javaforall.cn