ack oscillate

 拓扑: client--->AA-tproxy-BB----------server

client server 之间使用tproxy 代理

从抓取报文来看,在管理连接的时候出现大量ack报文,以上不是完整报文

直接给结论吧:

  • server主动关闭发出FIN, BB 收到FIN后,发出Fin-ack,同时去AA 主动关闭;所以socket 状态为:BB为last_ack
  • server 收到BB 的fin_ack后,主动发出ack 此时server 进入time_wait 状态, 同时BB收到ack 就进入close状态
  • client 收到fin 后 内核协议栈回复ack 同时应用层会发出fin,如果此时client 的被动FIN 延时发送, 同时 AA socket 处于Fin2 状态 如果FIN2 超时被回收, 此时server 处于time_wait 状态
  • client 响应发送FIN 此时 tproxy 代理没有找到之前的socket,FIN2 下超时回收,此时应该怎么处理??
  • 如果不转发 就会触发client的超时重传最后回收
  • 如果转发就转发到server 端 ,但是server 端处于time-wait 状态,此时出现ack 应答震荡??

目前想到的解决办法是:使用rst 关闭或者 不转发了这个 肯定能解决问题!

但是不是真的震荡呢?看代码

/*
 * * Main purpose of TIME-WAIT state is to close connection gracefully,
 *   when one of ends sits in LAST-ACK or CLOSING retransmitting FIN
 *   (and, probably, tail of data) and one or more our ACKs are lost.
 * * What is TIME-WAIT timeout? It is associated with maximal packet
 *   lifetime in the internet, which results in wrong conclusion, that
 *   it is set to catch "old duplicate segments" wandering out of their path.
 *   It is not quite correct. This timeout is calculated so that it exceeds
 *   maximal retransmission timeout enough to allow to lose one (or more)
 *   segments sent by peer and our ACKs. This time may be calculated from RTO.
 * * When TIME-WAIT socket receives RST, it means that another end
 *   finally closed and we are allowed to kill TIME-WAIT too.
 * * Second purpose of TIME-WAIT is catching old duplicate segments.
 *   Well, certainly it is pure paranoia, but if we load TIME-WAIT
 *   with this semantics, we MUST NOT kill TIME-WAIT state with RSTs.
 * * If we invented some more clever way to catch duplicates
 *   (f.e. based on PAWS), we could truncate TIME-WAIT to several RTOs.
 *
 * The algorithm below is based on FORMAL INTERPRETATION of RFCs.
 * When you compare it to RFCs, please, read section SEGMENT ARRIVES
 * from the very beginning.
 *
 * NOTE. With recycling (and later with fin-wait-2) TW bucket
 * is _not_ stateless. It means, that strictly speaking we must
 * spinlock it. I do not want! Well, probability of misbehaviour
 * is ridiculously low and, seems, we could use some mb() tricks
 * to avoid misread sequence numbers, states etc.  --ANK
 *
 * We don't need to initialize tmp_out.sack_ok as we don't use the results
 */
enum tcp_tw_status
tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb,
               const struct tcphdr *th)
{
    struct tcp_options_received tmp_opt;
    struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);
    bool paws_reject = false;

    tmp_opt.saw_tstamp = 0;
    if (th->doff > (sizeof(*th) >> 2) && tcptw->tw_ts_recent_stamp) {
        tcp_parse_options(skb, &tmp_opt, 0, NULL);

        if (tmp_opt.saw_tstamp) {
            tmp_opt.rcv_tsecr    -= tcptw->tw_ts_offset;
            tmp_opt.ts_recent    = tcptw->tw_ts_recent;
            tmp_opt.ts_recent_stamp    = tcptw->tw_ts_recent_stamp;
            paws_reject = tcp_paws_reject(&tmp_opt, th->rst);
        }
    }
/*
tcp_timewait_state_process函数处理流程中,如果TIME_WAIT的子状态为FIN_WAIT_2,
并且收到了合法的FIN之后,会进入真正的TIME_WAIT状态,即子状态也为TIME_WAIT,
并且设置TIME_WAIT定时器;
*/
    if (tw->tw_substate == TCP_FIN_WAIT2) {
        /* Just repeat all the checks of tcp_rcv_state_process() */

        /* Out of window, send ACK */
        if (paws_reject ||
            !tcp_in_window(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq,
                   tcptw->tw_rcv_nxt,
                   tcptw->tw_rcv_nxt + tcptw->tw_rcv_wnd))
            return tcp_timewait_check_oow_rate_limit(
                tw, skb, LINUX_MIB_TCPACKSKIPPEDFINWAIT2);

        if (th->rst)
            goto kill;
          /* syn && 序号>= 期望接收序号??? */
        if (th->syn && !before(TCP_SKB_CB(skb)->seq, tcptw->tw_rcv_nxt))
            return TCP_TW_RST;

        /* Dup ACK? *//* 非ack || 以前的ack || 新的无数据ack */
        if (!th->ack ||
            !after(TCP_SKB_CB(skb)->end_seq, tcptw->tw_rcv_nxt) ||
            TCP_SKB_CB(skb)->end_seq == TCP_SKB_CB(skb)->seq) {
            inet_twsk_put(tw);
            return TCP_TW_SUCCESS;
        }

        /* New data or FIN. If new data arrive after half-duplex close,
         * reset.
         */
        if (!th->fin ||
            TCP_SKB_CB(skb)->end_seq != tcptw->tw_rcv_nxt + 1)
            return TCP_TW_RST;

        /* FIN arrived, enter true time-wait state. */
        tw->tw_substate      = TCP_TIME_WAIT;
        tcptw->tw_rcv_nxt = TCP_SKB_CB(skb)->end_seq;/* 设置下一次接收序号 */
        if (tmp_opt.saw_tstamp) {
            tcptw->tw_ts_recent_stamp = get_seconds();
            tcptw->tw_ts_recent      = tmp_opt.rcv_tsval;
        }

        if (tcp_death_row.sysctl_tw_recycle &&
            tcptw->tw_ts_recent_stamp &&
            tcp_tw_remember_stamp(tw))
            inet_twsk_reschedule(tw, tw->tw_timeout);
        else
            inet_twsk_reschedule(tw, TCP_TIMEWAIT_LEN);
        return TCP_TW_ACK;
    }

    /*
     *    Now real TIME-WAIT state.
     *
     *    RFC 1122:
     *    "When a connection is [...] on TIME-WAIT state [...]
     *    [a TCP] MAY accept a new SYN from the remote TCP to
     *    reopen the connection directly, if it:
     *
     *    (1)  assigns its initial sequence number for the new
     *    connection to be larger than the largest sequence
     *    number it used on the previous connection incarnation,
     *    and
     *
     *    (2)  returns to TIME-WAIT state if the SYN turns out
     *    to be an old duplicate".
     */

    if (!paws_reject &&
        (TCP_SKB_CB(skb)->seq == tcptw->tw_rcv_nxt &&
         (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq || th->rst))) {
        /* In window segment, it may be only reset or bare ack. */

        if (th->rst) {
            /* This is TIME_WAIT assassination, in two flavors.
             * Oh well... nobody has a sufficient solution to this
             * protocol bug yet.
             */
            if (sysctl_tcp_rfc1337 == 0) {
kill:
                inet_twsk_deschedule_put(tw);
                return TCP_TW_SUCCESS;
            }
        }
        inet_twsk_reschedule(tw, TCP_TIMEWAIT_LEN);

        if (tmp_opt.saw_tstamp) {
            tcptw->tw_ts_recent      = tmp_opt.rcv_tsval;
            tcptw->tw_ts_recent_stamp = get_seconds();
        }

        inet_twsk_put(tw);
        return TCP_TW_SUCCESS;
    }

    /* Out of window segment.

       All the segments are ACKed immediately.

       The only exception is new SYN. We accept it, if it is
       not old duplicate and we are not in danger to be killed
       by delayed old duplicates. RFC check is that it has
       newer sequence number works at rates <40Mbit/sec.
       However, if paws works, it is reliable AND even more,
       we even may relax silly seq space cutoff.

       RED-PEN: we violate main RFC requirement, if this SYN will appear
       old duplicate (i.e. we receive RST in reply to SYN-ACK),
       we must return socket to time-wait state. It is not good,
       but not fatal yet.
     */

    if (th->syn && !th->rst && !th->ack && !paws_reject &&
        (after(TCP_SKB_CB(skb)->seq, tcptw->tw_rcv_nxt) ||
         (tmp_opt.saw_tstamp &&
          (s32)(tcptw->tw_ts_recent - tmp_opt.rcv_tsval) < 0))) {
        u32 isn = tcptw->tw_snd_nxt + 65535 + 2;
        if (isn == 0)
            isn++;
        TCP_SKB_CB(skb)->tcp_tw_isn = isn;
        return TCP_TW_SYN;
    }

    if (paws_reject)
        __NET_INC_STATS(twsk_net(tw), LINUX_MIB_PAWSESTABREJECTED);

    if (!th->rst) {
        /* In this case we must reset the TIMEWAIT timer.
         *
         * If it is ACKless SYN it may be both old duplicate
         * and new good SYN with random sequence number <rcv_nxt.
         * Do not reschedule in the last case.
         */
        if (paws_reject || th->ack)
            inet_twsk_reschedule(tw, TCP_TIMEWAIT_LEN);

        return tcp_timewait_check_oow_rate_limit(
            tw, skb, LINUX_MIB_TCPACKSKIPPEDTIMEWAIT);
    }
    inet_twsk_put(tw);
    return TCP_TW_SUCCESS;
}
View Code

目前看应该走进此逻辑:

    if (!th->rst) {
        /* In this case we must reset the TIMEWAIT timer.
         *
         * If it is ACKless SYN it may be both old duplicate
         * and new good SYN with random sequence number <rcv_nxt.
         * Do not reschedule in the last case.
         */
        if (paws_reject || th->ack)
            inet_twsk_reschedule(tw, TCP_TIMEWAIT_LEN);

        return tcp_timewait_check_oow_rate_limit(
            tw, skb, LINUX_MIB_TCPACKSKIPPEDTIMEWAIT);
    }
static enum tcp_tw_status
tcp_timewait_check_oow_rate_limit(struct inet_timewait_sock *tw,
                  const struct sk_buff *skb, int mib_idx)
{
    struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);

    if (!tcp_oow_rate_limited(twsk_net(tw), skb, mib_idx,
                  &tcptw->tw_last_oow_ack_time)) {
        /* Send ACK. Note, we do not put the bucket,
         * it will be released by caller.
         */
        return TCP_TW_ACK;
    }

    /* We are rate-limiting, so just release the tw sock and drop skb. */
    inet_twsk_put(tw);
    return TCP_TW_SUCCESS;
}


/* Return true if we're currently rate-limiting out-of-window ACKs and
 * thus shouldn't send a dupack right now. We rate-limit dupacks in
 * response to out-of-window SYNs or ACKs to mitigate ACK loops or DoS
 * attacks that send repeated SYNs or ACKs for the same connection. To
 * do this, we do not send a duplicate SYNACK or ACK if the remote
 * endpoint is sending out-of-window SYNs or pure ACKs at a high rate.
 */
bool tcp_oow_rate_limited(struct net *net, const struct sk_buff *skb,
              int mib_idx, u32 *last_oow_ack_time)
{
    /* Data packets without SYNs are not likely part of an ACK loop. */
    if ((TCP_SKB_CB(skb)->seq != TCP_SKB_CB(skb)->end_seq) &&
        !tcp_hdr(skb)->syn)
        return false;

    return __tcp_oow_rate_limited(net, mib_idx, last_oow_ack_time);
}

static bool __tcp_oow_rate_limited(struct net *net, int mib_idx,
                   u32 *last_oow_ack_time)
{
    if (*last_oow_ack_time) {
        s32 elapsed = (s32)(tcp_time_stamp - *last_oow_ack_time);

        if (0 <= elapsed && elapsed < sysctl_tcp_invalid_ratelimit) {
            NET_INC_STATS(net, mib_idx);
            return true;    /* rate-limited: don't send yet! */
        }
    }

    *last_oow_ack_time = tcp_time_stamp;

    return false;    /* not rate-limited: go ahead, send dupack now! */
}

跟住上面的注释 应该就是此处触发 回复ack  !!   但是不能肯定 下次再仔细看这部分代码吧!!

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