--- linux-2.4.26-wt4.1/net/ipv4/netfilter/ip_conntrack_proto_tcp.c Sun May 2 21:34:54 2004 +++ linux-2.4.26-wt4.1-rst/net/ipv4/netfilter/ip_conntrack_proto_tcp.c Fri May 7 21:13:11 2004 @@ -704,7 +704,7 @@ enum ip_conntrack_dir dir; struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph + iph->ihl); unsigned long timeout; - unsigned int index; + unsigned int index, old_index; /* Do not handle unclean packets, which could cause false alarms */ if (unclean(iph, len)) @@ -738,6 +738,28 @@ conntrack->timeout.function((unsigned long)conntrack); return -NF_ACCEPT; } + + /* A SYN thrown into the window would trigger an RST from the + * target which would kill the session. This is obviously an + * invalid situation. + */ + + if (index == TCP_SYN_SET + && !after(ntohl(tcph->seq), conntrack->proto.tcp.seen[dir].td_maxend)) { + WRITE_UNLOCK(&tcp_lock); + if (NET_RATELIMIT(ip_ct_tcp_log_invalid)) + nf_log_ip((char *)iph, len, + "ip_conntrack_tcp: INVALID: invalid SYN "); + return -NF_ACCEPT; + } + + /* This is a special case : imagine a remote dialup user who + * dirtily hangs up. A new dialup user coming from the same + * IP/ports will try to connect, so the firewall may be nice + * and let its SYN reach the server. We store that information + * so that we can later match an RST or a SYN/ACK from the + * server and clean the session entry. + */ conntrack->proto.tcp.stored_seq = index; conntrack->proto.tcp.last_dir = dir; conntrack->proto.tcp.last_seq = ntohl(tcph->seq); @@ -768,25 +790,75 @@ } break; case TCP_CONNTRACK_CLOSE: - if (index == TCP_RST_SET - && test_bit(IPS_SEEN_REPLY_BIT, &conntrack->status) - && conntrack->proto.tcp.stored_seq <= TCP_SYNACK_SET - && after(ntohl(tcph->ack), conntrack->proto.tcp.last_seq)) { - /* Ignore RST closing down invalid SYN we had let trough. */ - WRITE_UNLOCK(&tcp_lock); - if (NET_RATELIMIT(ip_ct_tcp_log_invalid)) - nf_log_ip((char *)iph, len, - "ip_conntrack_tcp: INVALID: invalid RST (ignored) "); - return NF_ACCEPT; + if (index == TCP_RST_SET) { + /* This is a special case : we let an RST pass through + * to tell the other end that its SYN was not appropriate + * within this session. The RST must match the last + * SEQ +1. This can happen when a server reboots, leaving + * sessions established on both the client and the + * firewall. We cannot rely on the client to close the + * session (think about dialup users who hang up). + * When a new client will come up with a new SYN from the + * same IP/ports, his SYN must pass and the server must + * be able to tell it that it doesn't like the session. + */ + if (conntrack->proto.tcp.stored_seq <= TCP_SYNACK_SET + && conntrack->proto.tcp.last_dir != dir + && test_bit(IPS_SEEN_REPLY_BIT, &conntrack->status) + && tcph->ack + && ntohl(tcph->ack_seq) == conntrack->proto.tcp.last_seq + 1) { + /* Ignore RST closing down invalid SYN we had let through. */ + WRITE_UNLOCK(&tcp_lock); + if (NET_RATELIMIT(ip_ct_tcp_log_invalid)) + nf_log_ip((char *)iph, len, + "ip_conntrack_tcp: INVALID: RST for invalid SYN (ignored) "); + return NF_ACCEPT; + } + + /* Two possibilities here */ + /* 1) if an ACK is set with the RST, it must match the + * other dir's td_end, but there are no expectations + * on its seq number. This case happens when an RST + * is sent in response to a SYN. + * + * 2) There's no ACK, so SEQ must match the other one's + * ACK. Since we would have no reason to let an ACK + * go through without a previous packet, we know that + * there must have been a previous packet in the same + * direction with an END equal to the RST's SEQ. + * This case happens on reconnections, when ACKs are + * sent in responses to SYNs, which lead to an RST + * from the SYN author. + * + * However, a null td_end always matches because the + * firewall might have taken over an established + * connection without knowing td_end yet, and that the + * server does not want anymore. + */ + if ((tcph->ack && conntrack->proto.tcp.seen[!dir].td_end != 0 + && ntohl(tcph->ack_seq) != conntrack->proto.tcp.seen[!dir].td_end) + || (!tcph->ack && + (!test_bit(IPS_SEEN_REPLY_BIT, &conntrack->status) + || conntrack->proto.tcp.seen[dir].td_end != 0 + && ntohl(tcph->seq) != conntrack->proto.tcp.seen[dir].td_end))) { + WRITE_UNLOCK(&tcp_lock); + if (NET_RATELIMIT(ip_ct_tcp_log_invalid)) + nf_log_ip((char *)iph, len, + "ip_conntrack_tcp: INVALID: invalid RST "); + + return -NF_ACCEPT; + } } /* Just fall trough */ default: /* Keep compilers happy. */ } + old_index = conntrack->proto.tcp.stored_seq; conntrack->proto.tcp.stored_seq = index; if (!tcp_in_window(&conntrack->proto.tcp, dir, iph, len, tcph)) { - /* Invalid packet */ + /* Invalid packet, restore previous state */ + conntrack->proto.tcp.stored_seq = old_index; WRITE_UNLOCK(&tcp_lock); return -NF_ACCEPT; }