conntrack: RFC5961 challenge ACK confuse conntrack LAST-ACK transition
In compliance with RFC5961, the network stack send challenge ACK in response to spurious SYN packets, since commit0c228e833c
("tcp: Restore RFC5961-compliant behavior for SYN packets"). This pose a problem for netfilter conntrack in state LAST_ACK, because this challenge ACK is (falsely) seen as ACKing last FIN, causing a false state transition (into TIME_WAIT). The challenge ACK is hard to distinguish from real last ACK. Thus, solution introduce a flag that tracks the potential for seeing a challenge ACK, in case a SYN packet is let through and current state is LAST_ACK. When conntrack transition LAST_ACK to TIME_WAIT happens, this flag is used for determining if we are expecting a challenge ACK. Scapy based reproducer script avail here: https://github.com/netoptimizer/network-testing/blob/master/scapy/tcp_hacks_3WHS_LAST_ACK.py Fixes:0c228e833c
("tcp: Restore RFC5961-compliant behavior for SYN packets") Signed-off-by: Jesper Dangaard Brouer <brouer@redhat.com> Acked-by: Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
Родитель
595ca5880b
Коммит
b3cad287d1
|
@ -42,6 +42,9 @@ enum tcp_conntrack {
|
||||||
/* The field td_maxack has been set */
|
/* The field td_maxack has been set */
|
||||||
#define IP_CT_TCP_FLAG_MAXACK_SET 0x20
|
#define IP_CT_TCP_FLAG_MAXACK_SET 0x20
|
||||||
|
|
||||||
|
/* Marks possibility for expected RFC5961 challenge ACK */
|
||||||
|
#define IP_CT_EXP_CHALLENGE_ACK 0x40
|
||||||
|
|
||||||
struct nf_ct_tcp_flags {
|
struct nf_ct_tcp_flags {
|
||||||
__u8 flags;
|
__u8 flags;
|
||||||
__u8 mask;
|
__u8 mask;
|
||||||
|
|
|
@ -202,7 +202,7 @@ static const u8 tcp_conntracks[2][6][TCP_CONNTRACK_MAX] = {
|
||||||
* sES -> sES :-)
|
* sES -> sES :-)
|
||||||
* sFW -> sCW Normal close request answered by ACK.
|
* sFW -> sCW Normal close request answered by ACK.
|
||||||
* sCW -> sCW
|
* sCW -> sCW
|
||||||
* sLA -> sTW Last ACK detected.
|
* sLA -> sTW Last ACK detected (RFC5961 challenged)
|
||||||
* sTW -> sTW Retransmitted last ACK. Remain in the same state.
|
* sTW -> sTW Retransmitted last ACK. Remain in the same state.
|
||||||
* sCL -> sCL
|
* sCL -> sCL
|
||||||
*/
|
*/
|
||||||
|
@ -261,7 +261,7 @@ static const u8 tcp_conntracks[2][6][TCP_CONNTRACK_MAX] = {
|
||||||
* sES -> sES :-)
|
* sES -> sES :-)
|
||||||
* sFW -> sCW Normal close request answered by ACK.
|
* sFW -> sCW Normal close request answered by ACK.
|
||||||
* sCW -> sCW
|
* sCW -> sCW
|
||||||
* sLA -> sTW Last ACK detected.
|
* sLA -> sTW Last ACK detected (RFC5961 challenged)
|
||||||
* sTW -> sTW Retransmitted last ACK.
|
* sTW -> sTW Retransmitted last ACK.
|
||||||
* sCL -> sCL
|
* sCL -> sCL
|
||||||
*/
|
*/
|
||||||
|
@ -906,6 +906,7 @@ static int tcp_packet(struct nf_conn *ct,
|
||||||
1 : ct->proto.tcp.last_win;
|
1 : ct->proto.tcp.last_win;
|
||||||
ct->proto.tcp.seen[ct->proto.tcp.last_dir].td_scale =
|
ct->proto.tcp.seen[ct->proto.tcp.last_dir].td_scale =
|
||||||
ct->proto.tcp.last_wscale;
|
ct->proto.tcp.last_wscale;
|
||||||
|
ct->proto.tcp.last_flags &= ~IP_CT_EXP_CHALLENGE_ACK;
|
||||||
ct->proto.tcp.seen[ct->proto.tcp.last_dir].flags =
|
ct->proto.tcp.seen[ct->proto.tcp.last_dir].flags =
|
||||||
ct->proto.tcp.last_flags;
|
ct->proto.tcp.last_flags;
|
||||||
memset(&ct->proto.tcp.seen[dir], 0,
|
memset(&ct->proto.tcp.seen[dir], 0,
|
||||||
|
@ -923,7 +924,9 @@ static int tcp_packet(struct nf_conn *ct,
|
||||||
* may be in sync but we are not. In that case, we annotate
|
* may be in sync but we are not. In that case, we annotate
|
||||||
* the TCP options and let the packet go through. If it is a
|
* the TCP options and let the packet go through. If it is a
|
||||||
* valid SYN packet, the server will reply with a SYN/ACK, and
|
* valid SYN packet, the server will reply with a SYN/ACK, and
|
||||||
* then we'll get in sync. Otherwise, the server ignores it. */
|
* then we'll get in sync. Otherwise, the server potentially
|
||||||
|
* responds with a challenge ACK if implementing RFC5961.
|
||||||
|
*/
|
||||||
if (index == TCP_SYN_SET && dir == IP_CT_DIR_ORIGINAL) {
|
if (index == TCP_SYN_SET && dir == IP_CT_DIR_ORIGINAL) {
|
||||||
struct ip_ct_tcp_state seen = {};
|
struct ip_ct_tcp_state seen = {};
|
||||||
|
|
||||||
|
@ -939,6 +942,13 @@ static int tcp_packet(struct nf_conn *ct,
|
||||||
ct->proto.tcp.last_flags |=
|
ct->proto.tcp.last_flags |=
|
||||||
IP_CT_TCP_FLAG_SACK_PERM;
|
IP_CT_TCP_FLAG_SACK_PERM;
|
||||||
}
|
}
|
||||||
|
/* Mark the potential for RFC5961 challenge ACK,
|
||||||
|
* this pose a special problem for LAST_ACK state
|
||||||
|
* as ACK is intrepretated as ACKing last FIN.
|
||||||
|
*/
|
||||||
|
if (old_state == TCP_CONNTRACK_LAST_ACK)
|
||||||
|
ct->proto.tcp.last_flags |=
|
||||||
|
IP_CT_EXP_CHALLENGE_ACK;
|
||||||
}
|
}
|
||||||
spin_unlock_bh(&ct->lock);
|
spin_unlock_bh(&ct->lock);
|
||||||
if (LOG_INVALID(net, IPPROTO_TCP))
|
if (LOG_INVALID(net, IPPROTO_TCP))
|
||||||
|
@ -970,6 +980,25 @@ static int tcp_packet(struct nf_conn *ct,
|
||||||
nf_log_packet(net, pf, 0, skb, NULL, NULL, NULL,
|
nf_log_packet(net, pf, 0, skb, NULL, NULL, NULL,
|
||||||
"nf_ct_tcp: invalid state ");
|
"nf_ct_tcp: invalid state ");
|
||||||
return -NF_ACCEPT;
|
return -NF_ACCEPT;
|
||||||
|
case TCP_CONNTRACK_TIME_WAIT:
|
||||||
|
/* RFC5961 compliance cause stack to send "challenge-ACK"
|
||||||
|
* e.g. in response to spurious SYNs. Conntrack MUST
|
||||||
|
* not believe this ACK is acking last FIN.
|
||||||
|
*/
|
||||||
|
if (old_state == TCP_CONNTRACK_LAST_ACK &&
|
||||||
|
index == TCP_ACK_SET &&
|
||||||
|
ct->proto.tcp.last_dir != dir &&
|
||||||
|
ct->proto.tcp.last_index == TCP_SYN_SET &&
|
||||||
|
(ct->proto.tcp.last_flags & IP_CT_EXP_CHALLENGE_ACK)) {
|
||||||
|
/* Detected RFC5961 challenge ACK */
|
||||||
|
ct->proto.tcp.last_flags &= ~IP_CT_EXP_CHALLENGE_ACK;
|
||||||
|
spin_unlock_bh(&ct->lock);
|
||||||
|
if (LOG_INVALID(net, IPPROTO_TCP))
|
||||||
|
nf_log_packet(net, pf, 0, skb, NULL, NULL, NULL,
|
||||||
|
"nf_ct_tcp: challenge-ACK ignored ");
|
||||||
|
return NF_ACCEPT; /* Don't change state */
|
||||||
|
}
|
||||||
|
break;
|
||||||
case TCP_CONNTRACK_CLOSE:
|
case TCP_CONNTRACK_CLOSE:
|
||||||
if (index == TCP_RST_SET
|
if (index == TCP_RST_SET
|
||||||
&& (ct->proto.tcp.seen[!dir].flags & IP_CT_TCP_FLAG_MAXACK_SET)
|
&& (ct->proto.tcp.seen[!dir].flags & IP_CT_TCP_FLAG_MAXACK_SET)
|
||||||
|
|
Загрузка…
Ссылка в новой задаче