Commit f9dd09c7 authored by Jozsef Kadlecsik's avatar Jozsef Kadlecsik Committed by David S. Miller
Browse files

netfilter: nf_nat: fix NAT issue in 2.6.30.4+



Vitezslav Samel discovered that since 2.6.30.4+ active FTP can not work
over NAT. The "cause" of the problem was a fix of unacknowledged data
detection with NAT (commit a3a9f79e).
However, actually, that fix uncovered a long standing bug in TCP conntrack:
when NAT was enabled, we simply updated the max of the right edge of
the segments we have seen (td_end), by the offset NAT produced with
changing IP/port in the data. However, we did not update the other parameter
(td_maxend) which is affected by the NAT offset. Thus that could drift
away from the correct value and thus resulted breaking active FTP.

The patch below fixes the issue by *not* updating the conntrack parameters
from NAT, but instead taking into account the NAT offsets in conntrack in a
consistent way. (Updating from NAT would be more harder and expensive because
it'd need to re-calculate parameters we already calculated in conntrack.)

Signed-off-by: default avatarJozsef Kadlecsik <kadlec@blackhole.kfki.hu>
Signed-off-by: default avatarPatrick McHardy <kaber@trash.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent f5209b44
Loading
Loading
Loading
Loading
+3 −5
Original line number Diff line number Diff line
@@ -255,11 +255,9 @@ static inline bool nf_ct_kill(struct nf_conn *ct)
}

/* These are for NAT.  Icky. */
/* Update TCP window tracking data when NAT mangles the packet */
extern void nf_conntrack_tcp_update(const struct sk_buff *skb,
				    unsigned int dataoff,
				    struct nf_conn *ct, int dir,
				    s16 offset);
extern s16 (*nf_ct_nat_offset)(const struct nf_conn *ct,
			       enum ip_conntrack_dir dir,
			       u32 seq);

/* Fake conntrack entry for untracked connections */
extern struct nf_conn nf_conntrack_untracked;
+4 −0
Original line number Diff line number Diff line
@@ -32,4 +32,8 @@ extern int (*nf_nat_seq_adjust_hook)(struct sk_buff *skb,
 * to port ct->master->saved_proto. */
extern void nf_nat_follow_master(struct nf_conn *ct,
				 struct nf_conntrack_expect *this);

extern s16 nf_nat_get_offset(const struct nf_conn *ct,
			     enum ip_conntrack_dir dir,
			     u32 seq);
#endif
+3 −0
Original line number Diff line number Diff line
@@ -750,6 +750,8 @@ static int __init nf_nat_init(void)
	BUG_ON(nfnetlink_parse_nat_setup_hook != NULL);
	rcu_assign_pointer(nfnetlink_parse_nat_setup_hook,
			   nfnetlink_parse_nat_setup);
	BUG_ON(nf_ct_nat_offset != NULL);
	rcu_assign_pointer(nf_ct_nat_offset, nf_nat_get_offset);
	return 0;

 cleanup_extend:
@@ -764,6 +766,7 @@ static void __exit nf_nat_cleanup(void)
	nf_ct_extend_unregister(&nat_extend);
	rcu_assign_pointer(nf_nat_seq_adjust_hook, NULL);
	rcu_assign_pointer(nfnetlink_parse_nat_setup_hook, NULL);
	rcu_assign_pointer(nf_ct_nat_offset, NULL);
	synchronize_net();
}

+23 −11
Original line number Diff line number Diff line
@@ -73,6 +73,28 @@ adjust_tcp_sequence(u32 seq,
	DUMP_OFFSET(this_way);
}

/* Get the offset value, for conntrack */
s16 nf_nat_get_offset(const struct nf_conn *ct,
		      enum ip_conntrack_dir dir,
		      u32 seq)
{
	struct nf_conn_nat *nat = nfct_nat(ct);
	struct nf_nat_seq *this_way;
	s16 offset;

	if (!nat)
		return 0;

	this_way = &nat->seq[dir];
	spin_lock_bh(&nf_nat_seqofs_lock);
	offset = after(seq, this_way->correction_pos)
		 ? this_way->offset_after : this_way->offset_before;
	spin_unlock_bh(&nf_nat_seqofs_lock);

	return offset;
}
EXPORT_SYMBOL_GPL(nf_nat_get_offset);

/* Frobs data inside this packet, which is linear. */
static void mangle_contents(struct sk_buff *skb,
			    unsigned int dataoff,
@@ -189,11 +211,6 @@ nf_nat_mangle_tcp_packet(struct sk_buff *skb,
		adjust_tcp_sequence(ntohl(tcph->seq),
				    (int)rep_len - (int)match_len,
				    ct, ctinfo);
		/* Tell TCP window tracking about seq change */
		nf_conntrack_tcp_update(skb, ip_hdrlen(skb),
					ct, CTINFO2DIR(ctinfo),
					(int)rep_len - (int)match_len);

		nf_conntrack_event_cache(IPCT_NATSEQADJ, ct);
	}
	return 1;
@@ -415,12 +432,7 @@ nf_nat_seq_adjust(struct sk_buff *skb,
	tcph->seq = newseq;
	tcph->ack_seq = newack;

	if (!nf_nat_sack_adjust(skb, tcph, ct, ctinfo))
		return 0;

	nf_conntrack_tcp_update(skb, ip_hdrlen(skb), ct, dir, seqoff);

	return 1;
	return nf_nat_sack_adjust(skb, tcph, ct, ctinfo);
}

/* Setup NAT on this expected conntrack so it follows master. */
+8 −0
Original line number Diff line number Diff line
@@ -1350,6 +1350,11 @@ err_stat:
	return ret;
}

s16 (*nf_ct_nat_offset)(const struct nf_conn *ct,
			enum ip_conntrack_dir dir,
			u32 seq);
EXPORT_SYMBOL_GPL(nf_ct_nat_offset);

int nf_conntrack_init(struct net *net)
{
	int ret;
@@ -1367,6 +1372,9 @@ int nf_conntrack_init(struct net *net)
		/* For use by REJECT target */
		rcu_assign_pointer(ip_ct_attach, nf_conntrack_attach);
		rcu_assign_pointer(nf_ct_destroy, destroy_conntrack);

		/* Howto get NAT offsets */
		rcu_assign_pointer(nf_ct_nat_offset, NULL);
	}
	return 0;

Loading