TCP合法RST报文

RESET报文的接收和检查处理。

客户端握手阶段

对于TCP客户端,在发送完SYN报文之后,如果接收到的回复报文同时设置了ACK和RST标志,在检查完ACK的合法性之后,处理RST标志,关闭套接口。对于ACK确认序号,其应当大于第一个未确认序号(snd_una),并且,确认序号不应大于未发送数据的序号(snd_nxt)。

通常情况下ACK确认序号应当等于snd_una加一(SYN占用一个序号),但是,如果SYN报文中带有数据(例如:TFO),ACK确认序号会更大。以上情况向对端发送reset报文,但是,如果当前报文不仅只有ACK标志位,还设置了RST位,将不发送reset报文。

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,执行关闭连接。

        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。

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)中删除连接请求结构。

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标志也是有效的。

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位,复位当前连接。

    /* 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函数的检查

    /* 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连接。

        } 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有效。

/* 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报文到对端。

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

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页