在前段时间,查一个问题时,忽然注意到有一个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内核源码,但老版本的内核也是这样的逻辑,只不过函数名称可能不同。