Linux系统中:RST与ACK标志的三种情况

66 views
Skip to first unread message

Gao Feng

unread,
Sep 22, 2016, 12:41:05 AM9/22/16
to 《linux环境编程》讨论组
在前段时间,查一个问题时,忽然注意到有一个TCP 的RST报文是没有ACK标志的,而我之前一直认为除了第一个SYN包外,每个TCP报文都要有ACK的。
然后检查所有的TCP RST,发现有的有ACK,有的没有ACK,于是就有了一个疑问:TCP RST到底要不要ACK?

查询了TCP的相关RFC,没有地方明确规定TCP RST是否要设置ACK。
那么什么时候RST带有ACK,什么时候不带ACK呢?

源码解释一切(基于最新的linux-stable):
首先Linux发送TCP RST分为2类情况:协议栈发送的RST;iptables发送的;

1. 协议栈发送:
永远带有ACK标志,可见tcp_send_active_reset
这个函数是TCP协议栈用于发送RST的,
void tcp_send_active_reset(struct sock *sk, gfp_t priority)
{
        struct sk_buff *skb;
        /* NOTE: No TCP options attached and we never retransmit this. */
        skb = alloc_skb(MAX_TCP_HEADER, priority);
        if (!skb) {
                NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTFAILED);
                return;
        }
        /* Reserve space for headers and prepare control bits. */
        skb_reserve(skb, MAX_TCP_HEADER);
                   /* 这里永远设置ACK */
        tcp_init_nondata_skb(skb, tcp_acceptable_seq(sk),
                             TCPHDR_ACK | TCPHDR_RST);

        skb_mstamp_get(&skb->skb_mstamp);
        /* Send it off. */
        if (tcp_transmit_skb(sk, skb, 0, priority))
                NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTFAILED);
        TCP_INC_STATS(sock_net(sk), TCP_MIB_OUTRSTS);
}

2. iptables规则发送的RST
nf_reject_ipv4.c文件中的nf_reject_ip_tcphdr_put

void nf_reject_ip_tcphdr_put(struct sk_buff *nskb, const struct sk_buff *oldskb,
                          const struct tcphdr *oth)
{
        struct iphdr *niph = ip_hdr(nskb);
        struct tcphdr *tcph;
        skb_reset_transport_header(nskb);
        tcph = (struct tcphdr *)skb_put(nskb, sizeof(struct tcphdr));
        memset(tcph, 0, sizeof(*tcph));
        tcph->source    = oth->dest;
        tcph->dest      = oth->source;
        tcph->doff      = sizeof(struct tcphdr) / 4;
                   /* 
                  收到的包,有ACK,则RST没有ACK标志;
                  收到的包,没有ACK,则RST有ACK —— 只有syn包的情况
                  */ 
        if (oth->ack) {
                tcph->seq = oth->ack_seq;
        } else {
                tcph->ack_seq = htonl(ntohl(oth->seq) + oth->syn + oth->fin +
                                      oldskb->len - ip_hdrlen(oldskb) -
                                      (oth->doff << 2));
                tcph->ack = 1;
        }
        tcph->rst       = 1;
        tcph->check = ~tcp_v4_check(sizeof(struct tcphdr), niph->saddr,
                                    niph->daddr, 0);
        nskb->ip_summed = CHECKSUM_PARTIAL;
        nskb->csum_start = (unsigned char *)tcph - nskb->head;
        nskb->csum_offset = offsetof(struct tcphdr, check);
}

我看的虽然是最新版本的Linux内核源码,但老版本的内核也是这样的逻辑,只不过函数名称可能不同。






Reply all
Reply to author
Forward
0 new messages