tcp rst报文_TCP报文格式

tcp rst报文_TCP报文格式RESET报文的接收和检查处理。客户端握手阶段对于TCP客户端,在发送完SYN报文之后,如果接收到的回复报文同时设置了ACK和RST标志,在检查完ACK的合法性之后,处理RST标志,关闭套接口。对于ACK确认序号,其应当大于第一个未确认序号(snd_una),并且,确认序号不应大于未发送数据的序号(snd_nxt)。通常情况下ACK确认序号应当等于snd_una加一(SYN占用一个序号),但是,如果SYN报文中带有数据(例如:TFO),ACK确认序号会更大。以上情况向对端发送reset报文,但是,如果

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/186968.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Java学习之servlet篇

    Java学习之servlet篇0x00前言这里就后面正式步入到javaEE的阶段了,记录一下学习内容。0x01Java中间件在Java里面常用的几个中间件在这里列出来一下:*webSp

    2021年12月12日
  • c++ 实现键盘钩子

    c++ 实现键盘钩子一.总体概述  主要实现的是将windows活跃或是顶层窗口的键盘输入的记录下来储存在txt文件中。主要用到的知识windows操作系统的消息机制,动态库等一些知识二.具体的实现  首先我们要重新建立一个windows桌面应用程序,然后我们运行一下我们会看到一个窗口,我们创建桌面应用程序而不创建控制台程序是因为桌面应用程序,这里面最主要的原因控制应用程序模拟DOS系统的那种CUI操作,…

  • 查看mysql慢日志_docker查看实时日志的命令

    查看mysql慢日志_docker查看实时日志的命令慢查询日志是否开启showvariableslike’%slow_query_log%’;#如果结果中包含slow_query_log|OFF,则说明慢日志已经关闭#开启慢查询日志的方式:setglobalslow_query_log=1;慢查询sql的设置时间查看慢查询sql的设置时间,默认10s,sql执行时间大于该时间的才是慢sql,才会记录到慢查询…

    2022年10月12日
  • 对委托书内容的更改声明_更改证明怎么开

    对委托书内容的更改声明_更改证明怎么开更改CSDN博客昵称

  • mybatis的拦截器_拦截所有来电怎么设置

    mybatis的拦截器_拦截所有来电怎么设置一、官网介绍MyBatis允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis允许使用插件来拦截的方法调用包括:Executor(update,query,flushStatements,commit,rollback,getTransaction,close,isClosed)拦截执行器的方法; ParameterHandler(ge…

  • 每天一个linux命令(34):du 命令

    每天一个linux命令(34):du 命令

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号