client 对server的rst 的处理

目前遇到一个问题:

server 端回复报文!

设备端回复报文:

  

  也就是盒子设备 收到http 请求转发出去时,server 回复http 200 ok后,代理设备回复了ack ,server 收到ack 立即发出rst;

所以来看看代理设备收到rst会怎么处理?如果收包队列中还有报文没有读走怎么处理?

其协议栈收包流程为:

/* Does PAWS and seqno based validation of an incoming segment, flags will
 * play significant role here.
 */
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;

    -------------------------

    /* 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;
        } 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);
        else {
            /* Disable TFO if RST is out-of-order
             * and no data has been received
             * for current active TFO socket
             */
            if (tp->syn_fastopen && !tp->data_segs_in &&
                sk->sk_state == TCP_ESTABLISHED)
                tcp_fastopen_active_disable(sk);
            tcp_send_challenge_ack(sk, skb);
        }
        goto discard;
    }

    --------------------------------------

    return true;

discard:
    tcp_drop(sk, skb);
    return false;
}
  •  当其序号等于本端套接口待接收的序号时才认为是个合法的RST 报文,防止被RST攻击;
  • 如果RST报文的序号不等于套接口的待接收序号,当时等于待接收序号减一,并且套接口之前接收到了FIN报文,对于主动关闭端,接收到FIN报文之后将进入TCPF_CLOSING状态。对于被动关闭端,接收到FIN报文之后,进入TCPF_CLOSE_WAIT状态,在套接口close之后,转换到TCPF_LAST_ACK状态;  满足这些状态的时候才认为是合法的RST报文
  • 当启用SACK时,仅当报文序号等于所有SACK块中最大的数据序号时,才认为是合法的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));
}

内核认为不合法的RST报文或者为恶意的RST攻击,回应挑战ACK报文,以使得发送不合法报文的对端得以恢复;

如上逻辑是按照RFC5961的要求处理SYN报文和RST报文,以防止Blind In-Window Attack

  Blind In-Window Attack简介:这个攻击的基本原理是攻击者通过发送伪造的RST包或SYN包导致TCP通信两端中的一个认为包非法而发送RST,从而异常结束连接。而这个攻击能够奏效的前提是所伪造的包的序列号必须在窗口范围内,要保证这一点攻击者必须发送许多攻击包进行猜测,因此得名。防御这种攻击的基本方法是,对于RST包,仔细检查其序列号,只有其序列号真正等于tp->rcv_nxt时才发送RST(攻击者能够伪造出符合这一特征的RST包的概率是很低的);而对于建立状态下SYN包,只是发送ACK,不发RST

从抓包看:目前server 回复的合法RST 报文

此时sk会被REST 也就是执行如下动作:

/* When we get a reset we do this. */
void tcp_reset(struct sock *sk)
{
    ---------------------
    
    sk->sk_err = ECONNRESET;
    
    /* This barrier is coupled with smp_rmb() in tcp_poll() */
    smp_wmb();

    if (!sock_flag(sk, SOCK_DEAD))
        sk->sk_error_report(sk);------->sock_def_error_report-->

    tcp_done(sk);
}
void tcp_done(struct sock *sk)
{
    struct request_sock *req = tcp_sk(sk)->fastopen_rsk;

    

    tcp_set_state(sk, TCP_CLOSE);
    tcp_clear_xmit_timers(sk);
    if (req)
        reqsk_fastopen_remove(sk, req, false);

    sk->sk_shutdown = SHUTDOWN_MASK;

    if (!sock_flag(sk, SOCK_DEAD))
        sk->sk_state_change(sk);
    else
        inet_csk_destroy_sock(sk);
}
View Code
  • 设置sk的错误码sk->sk_err = ECONNRESET;
  •  sock_def_error_report  唤醒休眠等待的进程;等待 getsockopt读取sk_err错误码
  • 设置tcp的sk状态为TCP_CLOSE  tcp_set_state(sk, TCP_CLOSE);
  • 清除重传定时器
  • 设置关闭掩码sk->sk_shutdown = SHUTDOWN_MASK;
  • 执行唤醒动作或者 销毁sk

来看tcp 读取报文的时候会遇到什么问题:主要函数为:tcp_recvmsg

 读不到数据!!!也就是数据在队列中没有及时读走,如果此时socket 被RST 了 那么SOCKET就回设置为DEAD 等待用户进程释放然后close掉

所以如果有sk->sk_receive_que中有skb_buf的时候首先去读取报文而不是首先检测sk状态, 也就是:

1、检测sk->sk_receive_queue是否有没有报文没有读取走;有 就读取报文返回

2、如果sk->sk_receive_queue中没有报文, 再去检测sk的状态

这样问题就解决了

添加逻辑为:

skb_queue_walk(&sk->sk_receive_queue, skb) {
   ----------------- 
}

同时 应用程序 使用recv的时候注意读取数据后处理数据,数据处理完 在处理错误!!!

关于TCP在建立链接情况下的 SYN攻击:可以参考https://zhuanlan.zhihu.com/p/337410466

 

1、seq 在合法范围内

2、ack number必须有效范围内:[SND.UNA-(231-1),SND.NXT]  SND表示发送方,UNA表示已发送未确认收到的seq NXT表示下一个要发送包的seq 

具体读取RFC吧:

https://tools.ietf.org/html/rfc5961#page-12

http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子
原文地址:https://www.cnblogs.com/codestack/p/14684704.html