diff options
Diffstat (limited to 'net/ipv4/netfilter/nf_nat_core.c')
-rw-r--r-- | net/ipv4/netfilter/nf_nat_core.c | 130 |
1 files changed, 89 insertions, 41 deletions
diff --git a/net/ipv4/netfilter/nf_nat_core.c b/net/ipv4/netfilter/nf_nat_core.c index ea02f00d2dac..deab27facbad 100644 --- a/net/ipv4/netfilter/nf_nat_core.c +++ b/net/ipv4/netfilter/nf_nat_core.c @@ -12,7 +12,6 @@ #include <linux/types.h> #include <linux/timer.h> #include <linux/skbuff.h> -#include <linux/vmalloc.h> #include <net/checksum.h> #include <net/icmp.h> #include <net/ip.h> @@ -32,20 +31,15 @@ #include <net/netfilter/nf_conntrack_l3proto.h> #include <net/netfilter/nf_conntrack_l4proto.h> -#if 0 -#define DEBUGP printk -#else -#define DEBUGP(format, args...) -#endif - static DEFINE_RWLOCK(nf_nat_lock); static struct nf_conntrack_l3proto *l3proto = NULL; /* Calculated at init based on memory size */ static unsigned int nf_nat_htable_size; +static int nf_nat_vmalloced; -static struct list_head *bysource; +static struct hlist_head *bysource; #define MAX_IP_NAT_PROTO 256 static struct nf_nat_protocol *nf_nat_protos[MAX_IP_NAT_PROTO]; @@ -83,23 +77,11 @@ static inline unsigned int hash_by_src(const struct nf_conntrack_tuple *tuple) { /* Original src, to ensure we map it consistently if poss. */ - return jhash_3words((__force u32)tuple->src.u3.ip, tuple->src.u.all, + return jhash_3words((__force u32)tuple->src.u3.ip, + (__force u32)tuple->src.u.all, tuple->dst.protonum, 0) % nf_nat_htable_size; } -/* Noone using conntrack by the time this called. */ -static void nf_nat_cleanup_conntrack(struct nf_conn *conn) -{ - struct nf_conn_nat *nat; - if (!(conn->status & IPS_NAT_DONE_MASK)) - return; - - nat = nfct_nat(conn); - write_lock_bh(&nf_nat_lock); - list_del(&nat->info.bysource); - write_unlock_bh(&nf_nat_lock); -} - /* Is this tuple already taken? (not by us) */ int nf_nat_used_tuple(const struct nf_conntrack_tuple *tuple, @@ -166,10 +148,11 @@ find_appropriate_src(const struct nf_conntrack_tuple *tuple, unsigned int h = hash_by_src(tuple); struct nf_conn_nat *nat; struct nf_conn *ct; + struct hlist_node *n; read_lock_bh(&nf_nat_lock); - list_for_each_entry(nat, &bysource[h], info.bysource) { - ct = (struct nf_conn *)((char *)nat - offsetof(struct nf_conn, data)); + hlist_for_each_entry(nat, n, &bysource[h], bysource) { + ct = nat->ct; if (same_src(ct, tuple)) { /* Copy source part from reply tuple. */ nf_ct_invert_tuplepr(result, @@ -254,7 +237,7 @@ get_unique_tuple(struct nf_conntrack_tuple *tuple, manips not an issue. */ if (maniptype == IP_NAT_MANIP_SRC) { if (find_appropriate_src(orig_tuple, tuple, range)) { - DEBUGP("get_unique_tuple: Found current src map\n"); + pr_debug("get_unique_tuple: Found current src map\n"); if (!(range->flags & IP_NAT_RANGE_PROTO_RANDOM)) if (!nf_nat_used_tuple(tuple, ct)) return; @@ -296,11 +279,20 @@ nf_nat_setup_info(struct nf_conn *ct, unsigned int hooknum) { struct nf_conntrack_tuple curr_tuple, new_tuple; - struct nf_conn_nat *nat = nfct_nat(ct); - struct nf_nat_info *info = &nat->info; + struct nf_conn_nat *nat; int have_to_hash = !(ct->status & IPS_NAT_DONE_MASK); enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum); + /* nat helper or nfctnetlink also setup binding */ + nat = nfct_nat(ct); + if (!nat) { + nat = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC); + if (nat == NULL) { + pr_debug("failed to add NAT extension\n"); + return NF_ACCEPT; + } + } + NF_CT_ASSERT(hooknum == NF_IP_PRE_ROUTING || hooknum == NF_IP_POST_ROUTING || hooknum == NF_IP_LOCAL_IN || @@ -337,7 +329,10 @@ nf_nat_setup_info(struct nf_conn *ct, srchash = hash_by_src(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); write_lock_bh(&nf_nat_lock); - list_add(&info->bysource, &bysource[srchash]); + /* nf_conntrack_alter_reply might re-allocate exntension aera */ + nat = nfct_nat(ct); + nat->ct = ct; + hlist_add_head(&nat->bysource, &bysource[srchash]); write_unlock_bh(&nf_nat_lock); } @@ -462,8 +457,9 @@ int nf_nat_icmp_reply_translation(struct nf_conn *ct, return 0; } - DEBUGP("icmp_reply_translation: translating error %p manp %u dir %s\n", - *pskb, manip, dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY"); + pr_debug("icmp_reply_translation: translating error %p manip %u " + "dir %s\n", *pskb, manip, + dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY"); /* rcu_read_lock()ed by nf_hook_slow */ l4proto = __nf_ct_l4proto_find(PF_INET, inside->ip.protocol); @@ -590,17 +586,69 @@ nf_nat_port_nfattr_to_range(struct nfattr *tb[], struct nf_nat_range *range) EXPORT_SYMBOL_GPL(nf_nat_port_range_to_nfattr); #endif +/* Noone using conntrack by the time this called. */ +static void nf_nat_cleanup_conntrack(struct nf_conn *ct) +{ + struct nf_conn_nat *nat = nf_ct_ext_find(ct, NF_CT_EXT_NAT); + + if (nat == NULL || nat->ct == NULL) + return; + + NF_CT_ASSERT(nat->ct->status & IPS_NAT_DONE_MASK); + + write_lock_bh(&nf_nat_lock); + hlist_del(&nat->bysource); + nat->ct = NULL; + write_unlock_bh(&nf_nat_lock); +} + +static void nf_nat_move_storage(struct nf_conn *conntrack, void *old) +{ + struct nf_conn_nat *new_nat = nf_ct_ext_find(conntrack, NF_CT_EXT_NAT); + struct nf_conn_nat *old_nat = (struct nf_conn_nat *)old; + struct nf_conn *ct = old_nat->ct; + unsigned int srchash; + + if (!(ct->status & IPS_NAT_DONE_MASK)) + return; + + srchash = hash_by_src(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); + + write_lock_bh(&nf_nat_lock); + hlist_replace_rcu(&old_nat->bysource, &new_nat->bysource); + new_nat->ct = ct; + write_unlock_bh(&nf_nat_lock); +} + +static struct nf_ct_ext_type nat_extend __read_mostly = { + .len = sizeof(struct nf_conn_nat), + .align = __alignof__(struct nf_conn_nat), + .destroy = nf_nat_cleanup_conntrack, + .move = nf_nat_move_storage, + .id = NF_CT_EXT_NAT, + .flags = NF_CT_EXT_F_PREALLOC, +}; + static int __init nf_nat_init(void) { size_t i; + int ret; + + ret = nf_ct_extend_register(&nat_extend); + if (ret < 0) { + printk(KERN_ERR "nf_nat_core: Unable to register extension\n"); + return ret; + } /* Leave them the same for the moment. */ nf_nat_htable_size = nf_conntrack_htable_size; - /* One vmalloc for both hash tables */ - bysource = vmalloc(sizeof(struct list_head) * nf_nat_htable_size); - if (!bysource) - return -ENOMEM; + bysource = nf_ct_alloc_hashtable(&nf_nat_htable_size, + &nf_nat_vmalloced); + if (!bysource) { + ret = -ENOMEM; + goto cleanup_extend; + } /* Sew in builtin protocols. */ write_lock_bh(&nf_nat_lock); @@ -612,18 +660,18 @@ static int __init nf_nat_init(void) write_unlock_bh(&nf_nat_lock); for (i = 0; i < nf_nat_htable_size; i++) { - INIT_LIST_HEAD(&bysource[i]); + INIT_HLIST_HEAD(&bysource[i]); } - /* FIXME: Man, this is a hack. <SIGH> */ - NF_CT_ASSERT(rcu_dereference(nf_conntrack_destroyed) == NULL); - rcu_assign_pointer(nf_conntrack_destroyed, nf_nat_cleanup_conntrack); - /* Initialize fake conntrack so that NAT will skip it */ nf_conntrack_untracked.status |= IPS_NAT_DONE_MASK; l3proto = nf_ct_l3proto_find_get((u_int16_t)AF_INET); return 0; + + cleanup_extend: + nf_ct_extend_unregister(&nat_extend); + return ret; } /* Clear NAT section of all conntracks, in case we're loaded again. */ @@ -641,10 +689,10 @@ static int clean_nat(struct nf_conn *i, void *data) static void __exit nf_nat_cleanup(void) { nf_ct_iterate_cleanup(&clean_nat, NULL); - rcu_assign_pointer(nf_conntrack_destroyed, NULL); synchronize_rcu(); - vfree(bysource); + nf_ct_free_hashtable(bysource, nf_nat_vmalloced, nf_nat_htable_size); nf_ct_l3proto_put(l3proto); + nf_ct_extend_unregister(&nat_extend); } MODULE_LICENSE("GPL"); |