diff -Nur linux-2.6.0-test11.org/include/linux/netfilter_ipv4/ip_conntrack_tuple.h linux-2.6.0-test11/include/linux/netfilter_ipv4/ip_conntrack_tuple.h --- linux-2.6.0-test11.org/include/linux/netfilter_ipv4/ip_conntrack_tuple.h 2003-11-26 21:44:58.000000000 +0100 +++ linux-2.6.0-test11/include/linux/netfilter_ipv4/ip_conntrack_tuple.h 2003-12-17 14:02:02.000000000 +0100 @@ -14,7 +14,7 @@ union ip_conntrack_manip_proto { /* Add other protocols here. */ - u_int16_t all; + u_int32_t all; struct { u_int16_t port; @@ -25,6 +25,9 @@ struct { u_int16_t id; } icmp; + struct { + u_int32_t key; + } gre; }; /* The manipulable part of the tuple. */ @@ -44,7 +47,7 @@ u_int32_t ip; union { /* Add other protocols here. */ - u_int16_t all; + u_int64_t all; struct { u_int16_t port; @@ -55,6 +58,11 @@ struct { u_int8_t type, code; } icmp; + struct { + u_int16_t protocol; + u_int8_t version; + u_int32_t key; + } gre; } u; /* The protocol. */ @@ -80,10 +88,16 @@ #ifdef __KERNEL__ #define DUMP_TUPLE(tp) \ -DEBUGP("tuple %p: %u %u.%u.%u.%u:%hu -> %u.%u.%u.%u:%hu\n", \ +DEBUGP("tuple %p: %u %u.%u.%u.%u:%u -> %u.%u.%u.%u:%u\n", \ (tp), (tp)->dst.protonum, \ - NIPQUAD((tp)->src.ip), ntohs((tp)->src.u.all), \ - NIPQUAD((tp)->dst.ip), ntohs((tp)->dst.u.all)) + NIPQUAD((tp)->src.ip), ntohl((tp)->src.u.all), \ + NIPQUAD((tp)->dst.ip), ntohl((tp)->dst.u.all)) + +#define DUMP_TUPLE_RAW(x) \ + DEBUGP("tuple %p: %u %u.%u.%u.%u:0x%08x -> %u.%u.%u.%u:0x%08x\n",\ + (x), (x)->dst.protonum, \ + NIPQUAD((x)->src.ip), ntohl((x)->src.u.all), \ + NIPQUAD((x)->dst.ip), ntohl((x)->dst.u.all)) #define CTINFO2DIR(ctinfo) ((ctinfo) >= IP_CT_IS_REPLY ? IP_CT_DIR_REPLY : IP_CT_DIR_ORIGINAL) diff -Nur linux-2.6.0-test11.org/include/linux/netfilter_ipv4/ip_conntrack_tuple.h.orig linux-2.6.0-test11/include/linux/netfilter_ipv4/ip_conntrack_tuple.h.orig --- linux-2.6.0-test11.org/include/linux/netfilter_ipv4/ip_conntrack_tuple.h.orig 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.0-test11/include/linux/netfilter_ipv4/ip_conntrack_tuple.h.orig 2003-11-26 21:44:58.000000000 +0100 @@ -0,0 +1,139 @@ +#ifndef _IP_CONNTRACK_TUPLE_H +#define _IP_CONNTRACK_TUPLE_H + +/* A `tuple' is a structure containing the information to uniquely + identify a connection. ie. if two packets have the same tuple, they + are in the same connection; if not, they are not. + + We divide the structure along "manipulatable" and + "non-manipulatable" lines, for the benefit of the NAT code. +*/ + +/* The protocol-specific manipulable parts of the tuple: always in + network order! */ +union ip_conntrack_manip_proto +{ + /* Add other protocols here. */ + u_int16_t all; + + struct { + u_int16_t port; + } tcp; + struct { + u_int16_t port; + } udp; + struct { + u_int16_t id; + } icmp; +}; + +/* The manipulable part of the tuple. */ +struct ip_conntrack_manip +{ + u_int32_t ip; + union ip_conntrack_manip_proto u; +}; + +/* This contains the information to distinguish a connection. */ +struct ip_conntrack_tuple +{ + struct ip_conntrack_manip src; + + /* These are the parts of the tuple which are fixed. */ + struct { + u_int32_t ip; + union { + /* Add other protocols here. */ + u_int16_t all; + + struct { + u_int16_t port; + } tcp; + struct { + u_int16_t port; + } udp; + struct { + u_int8_t type, code; + } icmp; + } u; + + /* The protocol. */ + u_int16_t protonum; + } dst; +}; + +/* This is optimized opposed to a memset of the whole structure. Everything we + * really care about is the source/destination unions */ +#define IP_CT_TUPLE_U_BLANK(tuple) \ + do { \ + (tuple)->src.u.all = 0; \ + (tuple)->dst.u.all = 0; \ + } while (0) + +enum ip_conntrack_dir +{ + IP_CT_DIR_ORIGINAL, + IP_CT_DIR_REPLY, + IP_CT_DIR_MAX +}; + +#ifdef __KERNEL__ + +#define DUMP_TUPLE(tp) \ +DEBUGP("tuple %p: %u %u.%u.%u.%u:%hu -> %u.%u.%u.%u:%hu\n", \ + (tp), (tp)->dst.protonum, \ + NIPQUAD((tp)->src.ip), ntohs((tp)->src.u.all), \ + NIPQUAD((tp)->dst.ip), ntohs((tp)->dst.u.all)) + +#define CTINFO2DIR(ctinfo) ((ctinfo) >= IP_CT_IS_REPLY ? IP_CT_DIR_REPLY : IP_CT_DIR_ORIGINAL) + +/* If we're the first tuple, it's the original dir. */ +#define DIRECTION(h) ((enum ip_conntrack_dir)(&(h)->ctrack->tuplehash[1] == (h))) + +/* Connections have two entries in the hash table: one for each way */ +struct ip_conntrack_tuple_hash +{ + struct list_head list; + + struct ip_conntrack_tuple tuple; + + /* this == &ctrack->tuplehash[DIRECTION(this)]. */ + struct ip_conntrack *ctrack; +}; + +#endif /* __KERNEL__ */ + +static inline int ip_ct_tuple_src_equal(const struct ip_conntrack_tuple *t1, + const struct ip_conntrack_tuple *t2) +{ + return t1->src.ip == t2->src.ip + && t1->src.u.all == t2->src.u.all; +} + +static inline int ip_ct_tuple_dst_equal(const struct ip_conntrack_tuple *t1, + const struct ip_conntrack_tuple *t2) +{ + return t1->dst.ip == t2->dst.ip + && t1->dst.u.all == t2->dst.u.all + && t1->dst.protonum == t2->dst.protonum; +} + +static inline int ip_ct_tuple_equal(const struct ip_conntrack_tuple *t1, + const struct ip_conntrack_tuple *t2) +{ + return ip_ct_tuple_src_equal(t1, t2) && ip_ct_tuple_dst_equal(t1, t2); +} + +static inline int ip_ct_tuple_mask_cmp(const struct ip_conntrack_tuple *t, + const struct ip_conntrack_tuple *tuple, + const struct ip_conntrack_tuple *mask) +{ + return !(((t->src.ip ^ tuple->src.ip) & mask->src.ip) + || ((t->dst.ip ^ tuple->dst.ip) & mask->dst.ip) + || ((t->src.u.all ^ tuple->src.u.all) & mask->src.u.all) + || ((t->dst.u.all ^ tuple->dst.u.all) & mask->dst.u.all) + || ((t->dst.protonum ^ tuple->dst.protonum) + & mask->dst.protonum)); +} + +#endif /* _IP_CONNTRACK_TUPLE_H */ diff -Nur linux-2.6.0-test11.org/net/ipv4/netfilter/Makefile linux-2.6.0-test11/net/ipv4/netfilter/Makefile --- linux-2.6.0-test11.org/net/ipv4/netfilter/Makefile 2003-11-26 21:43:25.000000000 +0100 +++ linux-2.6.0-test11/net/ipv4/netfilter/Makefile 2003-12-17 14:02:02.000000000 +0100 @@ -19,6 +19,15 @@ # connection tracking obj-$(CONFIG_IP_NF_CONNTRACK) += ip_conntrack.o +# connection tracking protocol helpers +obj-$(CONFIG_IP_NF_CT_PROTO_GRE) += ip_conntrack_proto_gre.o +ifdef CONFIG_IP_NF_CT_PROTO_GRE + export-objs += ip_conntrack_proto_gre.o +endif + +# NAT protocol helpers +obj-$(CONFIG_IP_NF_NAT_PROTO_GRE) += ip_nat_proto_gre.o + # connection tracking helpers obj-$(CONFIG_IP_NF_AMANDA) += ip_conntrack_amanda.o obj-$(CONFIG_IP_NF_TFTP) += ip_conntrack_tftp.o diff -Nur linux-2.6.0-test11.org/net/ipv4/netfilter/Makefile.orig linux-2.6.0-test11/net/ipv4/netfilter/Makefile.orig --- linux-2.6.0-test11.org/net/ipv4/netfilter/Makefile.orig 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.0-test11/net/ipv4/netfilter/Makefile.orig 2003-11-26 21:43:25.000000000 +0100 @@ -0,0 +1,96 @@ +# +# Makefile for the netfilter modules on top of IPv4. +# + +# objects for the conntrack and NAT core (used by standalone and backw. compat) +ip_nf_conntrack-objs := ip_conntrack_core.o ip_conntrack_proto_generic.o ip_conntrack_proto_tcp.o ip_conntrack_proto_udp.o ip_conntrack_proto_icmp.o +ip_nf_nat-objs := ip_nat_core.o ip_nat_helper.o ip_nat_proto_unknown.o ip_nat_proto_tcp.o ip_nat_proto_udp.o ip_nat_proto_icmp.o + +# objects for the standalone - connection tracking / NAT +ip_conntrack-objs := ip_conntrack_standalone.o $(ip_nf_conntrack-objs) +iptable_nat-objs := ip_nat_standalone.o ip_nat_rule.o $(ip_nf_nat-objs) + +# objects for backwards compatibility mode +ip_nf_compat-objs := ip_fw_compat.o ip_fw_compat_redir.o ip_fw_compat_masq.o $(ip_nf_conntrack-objs) $(ip_nf_nat-objs) + +ipfwadm-objs := $(ip_nf_compat-objs) ipfwadm_core.o +ipchains-objs := $(ip_nf_compat-objs) ipchains_core.o + +# connection tracking +obj-$(CONFIG_IP_NF_CONNTRACK) += ip_conntrack.o + +# connection tracking helpers +obj-$(CONFIG_IP_NF_AMANDA) += ip_conntrack_amanda.o +obj-$(CONFIG_IP_NF_TFTP) += ip_conntrack_tftp.o +obj-$(CONFIG_IP_NF_FTP) += ip_conntrack_ftp.o +obj-$(CONFIG_IP_NF_IRC) += ip_conntrack_irc.o + +# NAT helpers +obj-$(CONFIG_IP_NF_NAT_AMANDA) += ip_nat_amanda.o +obj-$(CONFIG_IP_NF_NAT_TFTP) += ip_nat_tftp.o +obj-$(CONFIG_IP_NF_NAT_FTP) += ip_nat_ftp.o +obj-$(CONFIG_IP_NF_NAT_IRC) += ip_nat_irc.o + +# generic IP tables +obj-$(CONFIG_IP_NF_IPTABLES) += ip_tables.o + +# the three instances of ip_tables +obj-$(CONFIG_IP_NF_FILTER) += iptable_filter.o +obj-$(CONFIG_IP_NF_MANGLE) += iptable_mangle.o +obj-$(CONFIG_IP_NF_NAT) += iptable_nat.o + +# matches +obj-$(CONFIG_IP_NF_MATCH_HELPER) += ipt_helper.o +obj-$(CONFIG_IP_NF_MATCH_LIMIT) += ipt_limit.o +obj-$(CONFIG_IP_NF_MATCH_MARK) += ipt_mark.o +obj-$(CONFIG_IP_NF_MATCH_MAC) += ipt_mac.o +obj-$(CONFIG_IP_NF_MATCH_IPRANGE) += ipt_iprange.o + +obj-$(CONFIG_IP_NF_MATCH_PKTTYPE) += ipt_pkttype.o +obj-$(CONFIG_IP_NF_MATCH_MULTIPORT) += ipt_multiport.o +obj-$(CONFIG_IP_NF_MATCH_OWNER) += ipt_owner.o +obj-$(CONFIG_IP_NF_MATCH_TOS) += ipt_tos.o + +obj-$(CONFIG_IP_NF_MATCH_RECENT) += ipt_recent.o + +obj-$(CONFIG_IP_NF_MATCH_ECN) += ipt_ecn.o +obj-$(CONFIG_IP_NF_MATCH_DSCP) += ipt_dscp.o +obj-$(CONFIG_IP_NF_MATCH_AH_ESP) += ipt_ah.o ipt_esp.o + +obj-$(CONFIG_IP_NF_MATCH_LENGTH) += ipt_length.o + +obj-$(CONFIG_IP_NF_MATCH_TTL) += ipt_ttl.o +obj-$(CONFIG_IP_NF_MATCH_STATE) += ipt_state.o +obj-$(CONFIG_IP_NF_MATCH_CONNTRACK) += ipt_conntrack.o +obj-$(CONFIG_IP_NF_MATCH_TCPMSS) += ipt_tcpmss.o + +obj-$(CONFIG_IP_NF_MATCH_PHYSDEV) += ipt_physdev.o + +# targets +obj-$(CONFIG_IP_NF_TARGET_REJECT) += ipt_REJECT.o +obj-$(CONFIG_IP_NF_TARGET_TOS) += ipt_TOS.o +obj-$(CONFIG_IP_NF_TARGET_ECN) += ipt_ECN.o +obj-$(CONFIG_IP_NF_TARGET_DSCP) += ipt_DSCP.o +obj-$(CONFIG_IP_NF_TARGET_MARK) += ipt_MARK.o +obj-$(CONFIG_IP_NF_TARGET_MASQUERADE) += ipt_MASQUERADE.o +obj-$(CONFIG_IP_NF_TARGET_REDIRECT) += ipt_REDIRECT.o +obj-$(CONFIG_IP_NF_TARGET_NETMAP) += ipt_NETMAP.o +obj-$(CONFIG_IP_NF_TARGET_SAME) += ipt_SAME.o +obj-$(CONFIG_IP_NF_TARGET_CLASSIFY) += ipt_CLASSIFY.o +obj-$(CONFIG_IP_NF_NAT_SNMP_BASIC) += ip_nat_snmp_basic.o +obj-$(CONFIG_IP_NF_TARGET_LOG) += ipt_LOG.o +obj-$(CONFIG_IP_NF_TARGET_ULOG) += ipt_ULOG.o +obj-$(CONFIG_IP_NF_TARGET_TCPMSS) += ipt_TCPMSS.o + +# generic ARP tables +obj-$(CONFIG_IP_NF_ARPTABLES) += arp_tables.o +obj-$(CONFIG_IP_NF_ARP_MANGLE) += arpt_mangle.o + +# just filtering instance of ARP tables for now +obj-$(CONFIG_IP_NF_ARPFILTER) += arptable_filter.o + +# backwards compatibility +obj-$(CONFIG_IP_NF_COMPAT_IPCHAINS) += ipchains.o +obj-$(CONFIG_IP_NF_COMPAT_IPFWADM) += ipfwadm.o + +obj-$(CONFIG_IP_NF_QUEUE) += ip_queue.o diff -Nur linux-2.6.0-test11.org/net/ipv4/netfilter/ip_conntrack_core.c linux-2.6.0-test11/net/ipv4/netfilter/ip_conntrack_core.c --- linux-2.6.0-test11.org/net/ipv4/netfilter/ip_conntrack_core.c 2003-11-26 21:42:40.000000000 +0100 +++ linux-2.6.0-test11/net/ipv4/netfilter/ip_conntrack_core.c 2003-12-17 14:02:02.000000000 +0100 @@ -150,6 +150,8 @@ inverse->dst.ip = orig->src.ip; inverse->dst.protonum = orig->dst.protonum; + inverse->src.u.all = inverse->dst.u.all = 0; + return protocol->invert_tuple(inverse, orig); } @@ -925,8 +927,8 @@ * so there is no need to use the tuple lock too */ DEBUGP("ip_conntrack_expect_related %p\n", related_to); - DEBUGP("tuple: "); DUMP_TUPLE(&expect->tuple); - DEBUGP("mask: "); DUMP_TUPLE(&expect->mask); + DEBUGP("tuple: "); DUMP_TUPLE_RAW(&expect->tuple); + DEBUGP("mask: "); DUMP_TUPLE_RAW(&expect->mask); old = LIST_FIND(&ip_conntrack_expect_list, resent_expect, struct ip_conntrack_expect *, &expect->tuple, @@ -1051,15 +1053,14 @@ MUST_BE_READ_LOCKED(&ip_conntrack_lock); WRITE_LOCK(&ip_conntrack_expect_tuple_lock); - DEBUGP("change_expect:\n"); - DEBUGP("exp tuple: "); DUMP_TUPLE(&expect->tuple); - DEBUGP("exp mask: "); DUMP_TUPLE(&expect->mask); - DEBUGP("newtuple: "); DUMP_TUPLE(newtuple); + DEBUGP("exp tuple: "); DUMP_TUPLE_RAW(&expect->tuple); + DEBUGP("exp mask: "); DUMP_TUPLE_RAW(&expect->mask); + DEBUGP("newtuple: "); DUMP_TUPLE_RAW(newtuple); if (expect->ct_tuple.dst.protonum == 0) { /* Never seen before */ DEBUGP("change expect: never seen before\n"); - if (!ip_ct_tuple_equal(&expect->tuple, newtuple) + if (!ip_ct_tuple_mask_cmp(&expect->tuple, newtuple, &expect->mask) && LIST_FIND(&ip_conntrack_expect_list, expect_clash, struct ip_conntrack_expect *, newtuple, &expect->mask)) { /* Force NAT to find an unused tuple */ diff -Nur linux-2.6.0-test11.org/net/ipv4/netfilter/ip_conntrack_core.c.orig linux-2.6.0-test11/net/ipv4/netfilter/ip_conntrack_core.c.orig --- linux-2.6.0-test11.org/net/ipv4/netfilter/ip_conntrack_core.c.orig 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.0-test11/net/ipv4/netfilter/ip_conntrack_core.c.orig 2003-11-26 21:42:40.000000000 +0100 @@ -0,0 +1,1430 @@ +/* Connection state tracking for netfilter. This is separated from, + but required by, the NAT layer; it can also be used by an iptables + extension. */ + +/* (c) 1999 Paul `Rusty' Russell. Licenced under the GNU General + * Public Licence. + * + * 23 Apr 2001: Harald Welte + * - new API and handling of conntrack/nat helpers + * - now capable of multiple expectations for one master + * 16 Jul 2002: Harald Welte + * - add usage/reference counts to ip_conntrack_expect + * - export ip_conntrack[_expect]_{find_get,put} functions + * */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* For ERR_PTR(). Yeah, I know... --RR */ +#include + +/* This rwlock protects the main hash table, protocol/helper/expected + registrations, conntrack timers*/ +#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_conntrack_lock) +#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_conntrack_lock) + +#include +#include +#include +#include +#include + +#define IP_CONNTRACK_VERSION "2.1" + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +DECLARE_RWLOCK(ip_conntrack_lock); +DECLARE_RWLOCK(ip_conntrack_expect_tuple_lock); + +void (*ip_conntrack_destroyed)(struct ip_conntrack *conntrack) = NULL; +LIST_HEAD(ip_conntrack_expect_list); +LIST_HEAD(protocol_list); +static LIST_HEAD(helpers); +unsigned int ip_conntrack_htable_size = 0; +int ip_conntrack_max; +static atomic_t ip_conntrack_count = ATOMIC_INIT(0); +struct list_head *ip_conntrack_hash; +static kmem_cache_t *ip_conntrack_cachep; + +extern struct ip_conntrack_protocol ip_conntrack_generic_protocol; + +static inline int proto_cmpfn(const struct ip_conntrack_protocol *curr, + u_int8_t protocol) +{ + return protocol == curr->proto; +} + +struct ip_conntrack_protocol *__ip_ct_find_proto(u_int8_t protocol) +{ + struct ip_conntrack_protocol *p; + + MUST_BE_READ_LOCKED(&ip_conntrack_lock); + p = LIST_FIND(&protocol_list, proto_cmpfn, + struct ip_conntrack_protocol *, protocol); + if (!p) + p = &ip_conntrack_generic_protocol; + + return p; +} + +struct ip_conntrack_protocol *ip_ct_find_proto(u_int8_t protocol) +{ + struct ip_conntrack_protocol *p; + + READ_LOCK(&ip_conntrack_lock); + p = __ip_ct_find_proto(protocol); + READ_UNLOCK(&ip_conntrack_lock); + return p; +} + +inline void +ip_conntrack_put(struct ip_conntrack *ct) +{ + IP_NF_ASSERT(ct); + IP_NF_ASSERT(ct->infos[0].master); + /* nf_conntrack_put wants to go via an info struct, so feed it + one at random. */ + nf_conntrack_put(&ct->infos[0]); +} + +static int ip_conntrack_hash_rnd_initted; +static unsigned int ip_conntrack_hash_rnd; + +static u_int32_t +hash_conntrack(const struct ip_conntrack_tuple *tuple) +{ +#if 0 + dump_tuple(tuple); +#endif + return (jhash_3words(tuple->src.ip, + (tuple->dst.ip ^ tuple->dst.protonum), + (tuple->src.u.all | (tuple->dst.u.all << 16)), + ip_conntrack_hash_rnd) % ip_conntrack_htable_size); +} + +int +get_tuple(const struct iphdr *iph, + const struct sk_buff *skb, + unsigned int dataoff, + struct ip_conntrack_tuple *tuple, + const struct ip_conntrack_protocol *protocol) +{ + /* Never happen */ + if (iph->frag_off & htons(IP_OFFSET)) { + printk("ip_conntrack_core: Frag of proto %u.\n", + iph->protocol); + return 0; + } + + tuple->src.ip = iph->saddr; + tuple->dst.ip = iph->daddr; + tuple->dst.protonum = iph->protocol; + + return protocol->pkt_to_tuple(skb, dataoff, tuple); +} + +static int +invert_tuple(struct ip_conntrack_tuple *inverse, + const struct ip_conntrack_tuple *orig, + const struct ip_conntrack_protocol *protocol) +{ + inverse->src.ip = orig->dst.ip; + inverse->dst.ip = orig->src.ip; + inverse->dst.protonum = orig->dst.protonum; + + return protocol->invert_tuple(inverse, orig); +} + + +/* ip_conntrack_expect helper functions */ + +/* Compare tuple parts depending on mask. */ +static inline int expect_cmp(const struct ip_conntrack_expect *i, + const struct ip_conntrack_tuple *tuple) +{ + MUST_BE_READ_LOCKED(&ip_conntrack_expect_tuple_lock); + return ip_ct_tuple_mask_cmp(tuple, &i->tuple, &i->mask); +} + +static void +destroy_expect(struct ip_conntrack_expect *exp) +{ + DEBUGP("destroy_expect(%p) use=%d\n", exp, atomic_read(&exp->use)); + IP_NF_ASSERT(atomic_read(&exp->use)); + IP_NF_ASSERT(!timer_pending(&exp->timeout)); + + kfree(exp); +} + + +inline void ip_conntrack_expect_put(struct ip_conntrack_expect *exp) +{ + IP_NF_ASSERT(exp); + + if (atomic_dec_and_test(&exp->use)) { + /* usage count dropped to zero */ + destroy_expect(exp); + } +} + +static inline struct ip_conntrack_expect * +__ip_ct_expect_find(const struct ip_conntrack_tuple *tuple) +{ + MUST_BE_READ_LOCKED(&ip_conntrack_lock); + MUST_BE_READ_LOCKED(&ip_conntrack_expect_tuple_lock); + return LIST_FIND(&ip_conntrack_expect_list, expect_cmp, + struct ip_conntrack_expect *, tuple); +} + +/* Find a expectation corresponding to a tuple. */ +struct ip_conntrack_expect * +ip_conntrack_expect_find_get(const struct ip_conntrack_tuple *tuple) +{ + struct ip_conntrack_expect *exp; + + READ_LOCK(&ip_conntrack_lock); + READ_LOCK(&ip_conntrack_expect_tuple_lock); + exp = __ip_ct_expect_find(tuple); + if (exp) + atomic_inc(&exp->use); + READ_UNLOCK(&ip_conntrack_expect_tuple_lock); + READ_UNLOCK(&ip_conntrack_lock); + + return exp; +} + +/* remove one specific expectation from all lists and drop refcount, + * does _NOT_ delete the timer. */ +static void __unexpect_related(struct ip_conntrack_expect *expect) +{ + DEBUGP("unexpect_related(%p)\n", expect); + MUST_BE_WRITE_LOCKED(&ip_conntrack_lock); + + /* we're not allowed to unexpect a confirmed expectation! */ + IP_NF_ASSERT(!expect->sibling); + + /* delete from global and local lists */ + list_del(&expect->list); + list_del(&expect->expected_list); + + /* decrement expect-count of master conntrack */ + if (expect->expectant) + expect->expectant->expecting--; + + ip_conntrack_expect_put(expect); +} + +/* remove one specific expecatation from all lists, drop refcount + * and expire timer. + * This function can _NOT_ be called for confirmed expects! */ +static void unexpect_related(struct ip_conntrack_expect *expect) +{ + IP_NF_ASSERT(expect->expectant); + IP_NF_ASSERT(expect->expectant->helper); + /* if we are supposed to have a timer, but we can't delete + * it: race condition. __unexpect_related will + * be calledd by timeout function */ + if (expect->expectant->helper->timeout + && !del_timer(&expect->timeout)) + return; + + __unexpect_related(expect); +} + +/* delete all unconfirmed expectations for this conntrack */ +static void remove_expectations(struct ip_conntrack *ct, int drop_refcount) +{ + struct list_head *exp_entry, *next; + struct ip_conntrack_expect *exp; + + DEBUGP("remove_expectations(%p)\n", ct); + + list_for_each_safe(exp_entry, next, &ct->sibling_list) { + exp = list_entry(exp_entry, struct ip_conntrack_expect, + expected_list); + + /* we skip established expectations, as we want to delete + * the un-established ones only */ + if (exp->sibling) { + DEBUGP("remove_expectations: skipping established %p of %p\n", exp->sibling, ct); + if (drop_refcount) { + /* Indicate that this expectations parent is dead */ + ip_conntrack_put(exp->expectant); + exp->expectant = NULL; + } + continue; + } + + IP_NF_ASSERT(list_inlist(&ip_conntrack_expect_list, exp)); + IP_NF_ASSERT(exp->expectant == ct); + + /* delete expectation from global and private lists */ + unexpect_related(exp); + } +} + +static void +clean_from_lists(struct ip_conntrack *ct) +{ + unsigned int ho, hr; + + DEBUGP("clean_from_lists(%p)\n", ct); + MUST_BE_WRITE_LOCKED(&ip_conntrack_lock); + + ho = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); + hr = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple); + LIST_DELETE(&ip_conntrack_hash[ho], &ct->tuplehash[IP_CT_DIR_ORIGINAL]); + LIST_DELETE(&ip_conntrack_hash[hr], &ct->tuplehash[IP_CT_DIR_REPLY]); + + /* Destroy all un-established, pending expectations */ + remove_expectations(ct, 1); +} + +static void +destroy_conntrack(struct nf_conntrack *nfct) +{ + struct ip_conntrack *ct = (struct ip_conntrack *)nfct, *master = NULL; + struct ip_conntrack_protocol *proto; + + DEBUGP("destroy_conntrack(%p)\n", ct); + IP_NF_ASSERT(atomic_read(&nfct->use) == 0); + IP_NF_ASSERT(!timer_pending(&ct->timeout)); + + /* To make sure we don't get any weird locking issues here: + * destroy_conntrack() MUST NOT be called with a write lock + * to ip_conntrack_lock!!! -HW */ + proto = ip_ct_find_proto(ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.protonum); + if (proto && proto->destroy) + proto->destroy(ct); + + if (ip_conntrack_destroyed) + ip_conntrack_destroyed(ct); + + WRITE_LOCK(&ip_conntrack_lock); + /* Delete us from our own list to prevent corruption later */ + list_del(&ct->sibling_list); + + /* Delete our master expectation */ + if (ct->master) { + if (ct->master->expectant) { + /* can't call __unexpect_related here, + * since it would screw up expect_list */ + list_del(&ct->master->expected_list); + master = ct->master->expectant; + } + kfree(ct->master); + } + WRITE_UNLOCK(&ip_conntrack_lock); + + if (master) + ip_conntrack_put(master); + + DEBUGP("destroy_conntrack: returning ct=%p to slab\n", ct); + kmem_cache_free(ip_conntrack_cachep, ct); + atomic_dec(&ip_conntrack_count); +} + +static void death_by_timeout(unsigned long ul_conntrack) +{ + struct ip_conntrack *ct = (void *)ul_conntrack; + + WRITE_LOCK(&ip_conntrack_lock); + clean_from_lists(ct); + WRITE_UNLOCK(&ip_conntrack_lock); + ip_conntrack_put(ct); +} + +static inline int +conntrack_tuple_cmp(const struct ip_conntrack_tuple_hash *i, + const struct ip_conntrack_tuple *tuple, + const struct ip_conntrack *ignored_conntrack) +{ + MUST_BE_READ_LOCKED(&ip_conntrack_lock); + return i->ctrack != ignored_conntrack + && ip_ct_tuple_equal(tuple, &i->tuple); +} + +static struct ip_conntrack_tuple_hash * +__ip_conntrack_find(const struct ip_conntrack_tuple *tuple, + const struct ip_conntrack *ignored_conntrack) +{ + struct ip_conntrack_tuple_hash *h; + unsigned int hash = hash_conntrack(tuple); + + MUST_BE_READ_LOCKED(&ip_conntrack_lock); + h = LIST_FIND(&ip_conntrack_hash[hash], + conntrack_tuple_cmp, + struct ip_conntrack_tuple_hash *, + tuple, ignored_conntrack); + return h; +} + +/* Find a connection corresponding to a tuple. */ +struct ip_conntrack_tuple_hash * +ip_conntrack_find_get(const struct ip_conntrack_tuple *tuple, + const struct ip_conntrack *ignored_conntrack) +{ + struct ip_conntrack_tuple_hash *h; + + READ_LOCK(&ip_conntrack_lock); + h = __ip_conntrack_find(tuple, ignored_conntrack); + if (h) + atomic_inc(&h->ctrack->ct_general.use); + READ_UNLOCK(&ip_conntrack_lock); + + return h; +} + +static inline struct ip_conntrack * +__ip_conntrack_get(struct nf_ct_info *nfct, enum ip_conntrack_info *ctinfo) +{ + struct ip_conntrack *ct + = (struct ip_conntrack *)nfct->master; + + /* ctinfo is the index of the nfct inside the conntrack */ + *ctinfo = nfct - ct->infos; + IP_NF_ASSERT(*ctinfo >= 0 && *ctinfo < IP_CT_NUMBER); + return ct; +} + +/* Return conntrack and conntrack_info given skb->nfct->master */ +struct ip_conntrack * +ip_conntrack_get(struct sk_buff *skb, enum ip_conntrack_info *ctinfo) +{ + if (skb->nfct) + return __ip_conntrack_get(skb->nfct, ctinfo); + return NULL; +} + +/* Confirm a connection given skb->nfct; places it in hash table */ +int +__ip_conntrack_confirm(struct nf_ct_info *nfct) +{ + unsigned int hash, repl_hash; + struct ip_conntrack *ct; + enum ip_conntrack_info ctinfo; + + ct = __ip_conntrack_get(nfct, &ctinfo); + + /* ipt_REJECT uses ip_conntrack_attach to attach related + ICMP/TCP RST packets in other direction. Actual packet + which created connection will be IP_CT_NEW or for an + expected connection, IP_CT_RELATED. */ + if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL) + return NF_ACCEPT; + + hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); + repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple); + + /* We're not in hash table, and we refuse to set up related + connections for unconfirmed conns. But packet copies and + REJECT will give spurious warnings here. */ + /* IP_NF_ASSERT(atomic_read(&ct->ct_general.use) == 1); */ + + /* No external references means noone else could have + confirmed us. */ + IP_NF_ASSERT(!is_confirmed(ct)); + DEBUGP("Confirming conntrack %p\n", ct); + + WRITE_LOCK(&ip_conntrack_lock); + /* See if there's one in the list already, including reverse: + NAT could have grabbed it without realizing, since we're + not in the hash. If there is, we lost race. */ + if (!LIST_FIND(&ip_conntrack_hash[hash], + conntrack_tuple_cmp, + struct ip_conntrack_tuple_hash *, + &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, NULL) + && !LIST_FIND(&ip_conntrack_hash[repl_hash], + conntrack_tuple_cmp, + struct ip_conntrack_tuple_hash *, + &ct->tuplehash[IP_CT_DIR_REPLY].tuple, NULL)) { + list_prepend(&ip_conntrack_hash[hash], + &ct->tuplehash[IP_CT_DIR_ORIGINAL]); + list_prepend(&ip_conntrack_hash[repl_hash], + &ct->tuplehash[IP_CT_DIR_REPLY]); + /* Timer relative to confirmation time, not original + setting time, otherwise we'd get timer wrap in + weird delay cases. */ + ct->timeout.expires += jiffies; + add_timer(&ct->timeout); + atomic_inc(&ct->ct_general.use); + set_bit(IPS_CONFIRMED_BIT, &ct->status); + WRITE_UNLOCK(&ip_conntrack_lock); + return NF_ACCEPT; + } + + WRITE_UNLOCK(&ip_conntrack_lock); + return NF_DROP; +} + +/* Returns true if a connection correspondings to the tuple (required + for NAT). */ +int +ip_conntrack_tuple_taken(const struct ip_conntrack_tuple *tuple, + const struct ip_conntrack *ignored_conntrack) +{ + struct ip_conntrack_tuple_hash *h; + + READ_LOCK(&ip_conntrack_lock); + h = __ip_conntrack_find(tuple, ignored_conntrack); + READ_UNLOCK(&ip_conntrack_lock); + + return h != NULL; +} + +/* Returns conntrack if it dealt with ICMP, and filled in skb fields */ +struct ip_conntrack * +icmp_error_track(struct sk_buff *skb, + enum ip_conntrack_info *ctinfo, + unsigned int hooknum) +{ + struct ip_conntrack_tuple innertuple, origtuple; + struct { + struct icmphdr icmp; + struct iphdr ip; + } inside; + struct ip_conntrack_protocol *innerproto; + struct ip_conntrack_tuple_hash *h; + int dataoff; + + IP_NF_ASSERT(skb->nfct == NULL); + + /* Not enough header? */ + if (skb_copy_bits(skb, skb->nh.iph->ihl*4, &inside, sizeof(inside))!=0) + return NULL; + + if (inside.icmp.type != ICMP_DEST_UNREACH + && inside.icmp.type != ICMP_SOURCE_QUENCH + && inside.icmp.type != ICMP_TIME_EXCEEDED + && inside.icmp.type != ICMP_PARAMETERPROB + && inside.icmp.type != ICMP_REDIRECT) + return NULL; + + /* Ignore ICMP's containing fragments (shouldn't happen) */ + if (inside.ip.frag_off & htons(IP_OFFSET)) { + DEBUGP("icmp_error_track: fragment of proto %u\n", + inside.ip.protocol); + return NULL; + } + + innerproto = ip_ct_find_proto(inside.ip.protocol); + dataoff = skb->nh.iph->ihl*4 + sizeof(inside.icmp) + inside.ip.ihl*4; + /* Are they talking about one of our connections? */ + if (!get_tuple(&inside.ip, skb, dataoff, &origtuple, innerproto)) { + DEBUGP("icmp_error: ! get_tuple p=%u", inside.ip.protocol); + return NULL; + } + + /* Ordinarily, we'd expect the inverted tupleproto, but it's + been preserved inside the ICMP. */ + if (!invert_tuple(&innertuple, &origtuple, innerproto)) { + DEBUGP("icmp_error_track: Can't invert tuple\n"); + return NULL; + } + + *ctinfo = IP_CT_RELATED; + + h = ip_conntrack_find_get(&innertuple, NULL); + if (!h) { + /* Locally generated ICMPs will match inverted if they + haven't been SNAT'ed yet */ + /* FIXME: NAT code has to handle half-done double NAT --RR */ + if (hooknum == NF_IP_LOCAL_OUT) + h = ip_conntrack_find_get(&origtuple, NULL); + + if (!h) { + DEBUGP("icmp_error_track: no match\n"); + return NULL; + } + /* Reverse direction from that found */ + if (DIRECTION(h) != IP_CT_DIR_REPLY) + *ctinfo += IP_CT_IS_REPLY; + } else { + if (DIRECTION(h) == IP_CT_DIR_REPLY) + *ctinfo += IP_CT_IS_REPLY; + } + + /* Update skb to refer to this connection */ + skb->nfct = &h->ctrack->infos[*ctinfo]; + return h->ctrack; +} + +/* There's a small race here where we may free a just-assured + connection. Too bad: we're in trouble anyway. */ +static inline int unreplied(const struct ip_conntrack_tuple_hash *i) +{ + return !(test_bit(IPS_ASSURED_BIT, &i->ctrack->status)); +} + +static int early_drop(struct list_head *chain) +{ + /* Traverse backwards: gives us oldest, which is roughly LRU */ + struct ip_conntrack_tuple_hash *h; + int dropped = 0; + + READ_LOCK(&ip_conntrack_lock); + h = LIST_FIND_B(chain, unreplied, struct ip_conntrack_tuple_hash *); + if (h) + atomic_inc(&h->ctrack->ct_general.use); + READ_UNLOCK(&ip_conntrack_lock); + + if (!h) + return dropped; + + if (del_timer(&h->ctrack->timeout)) { + death_by_timeout((unsigned long)h->ctrack); + dropped = 1; + } + ip_conntrack_put(h->ctrack); + return dropped; +} + +static inline int helper_cmp(const struct ip_conntrack_helper *i, + const struct ip_conntrack_tuple *rtuple) +{ + return ip_ct_tuple_mask_cmp(rtuple, &i->tuple, &i->mask); +} + +struct ip_conntrack_helper *ip_ct_find_helper(const struct ip_conntrack_tuple *tuple) +{ + return LIST_FIND(&helpers, helper_cmp, + struct ip_conntrack_helper *, + tuple); +} + +/* Allocate a new conntrack: we return -ENOMEM if classification + failed due to stress. Otherwise it really is unclassifiable. */ +static struct ip_conntrack_tuple_hash * +init_conntrack(const struct ip_conntrack_tuple *tuple, + struct ip_conntrack_protocol *protocol, + struct sk_buff *skb) +{ + struct ip_conntrack *conntrack; + struct ip_conntrack_tuple repl_tuple; + size_t hash; + struct ip_conntrack_expect *expected; + int i; + static unsigned int drop_next; + + if (!ip_conntrack_hash_rnd_initted) { + get_random_bytes(&ip_conntrack_hash_rnd, 4); + ip_conntrack_hash_rnd_initted = 1; + } + + hash = hash_conntrack(tuple); + + if (ip_conntrack_max && + atomic_read(&ip_conntrack_count) >= ip_conntrack_max) { + /* Try dropping from random chain, or else from the + chain about to put into (in case they're trying to + bomb one hash chain). */ + unsigned int next = (drop_next++)%ip_conntrack_htable_size; + + if (!early_drop(&ip_conntrack_hash[next]) + && !early_drop(&ip_conntrack_hash[hash])) { + if (net_ratelimit()) + printk(KERN_WARNING + "ip_conntrack: table full, dropping" + " packet.\n"); + return ERR_PTR(-ENOMEM); + } + } + + if (!invert_tuple(&repl_tuple, tuple, protocol)) { + DEBUGP("Can't invert tuple.\n"); + return NULL; + } + + conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC); + if (!conntrack) { + DEBUGP("Can't allocate conntrack.\n"); + return ERR_PTR(-ENOMEM); + } + + memset(conntrack, 0, sizeof(*conntrack)); + atomic_set(&conntrack->ct_general.use, 1); + conntrack->ct_general.destroy = destroy_conntrack; + conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *tuple; + conntrack->tuplehash[IP_CT_DIR_ORIGINAL].ctrack = conntrack; + conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = repl_tuple; + conntrack->tuplehash[IP_CT_DIR_REPLY].ctrack = conntrack; + for (i=0; i < IP_CT_NUMBER; i++) + conntrack->infos[i].master = &conntrack->ct_general; + + if (!protocol->new(conntrack, skb)) { + kmem_cache_free(ip_conntrack_cachep, conntrack); + return NULL; + } + /* Don't set timer yet: wait for confirmation */ + init_timer(&conntrack->timeout); + conntrack->timeout.data = (unsigned long)conntrack; + conntrack->timeout.function = death_by_timeout; + + INIT_LIST_HEAD(&conntrack->sibling_list); + + WRITE_LOCK(&ip_conntrack_lock); + /* Need finding and deleting of expected ONLY if we win race */ + READ_LOCK(&ip_conntrack_expect_tuple_lock); + expected = LIST_FIND(&ip_conntrack_expect_list, expect_cmp, + struct ip_conntrack_expect *, tuple); + READ_UNLOCK(&ip_conntrack_expect_tuple_lock); + + /* If master is not in hash table yet (ie. packet hasn't left + this machine yet), how can other end know about expected? + Hence these are not the droids you are looking for (if + master ct never got confirmed, we'd hold a reference to it + and weird things would happen to future packets). */ + if (expected && !is_confirmed(expected->expectant)) + expected = NULL; + + /* Look up the conntrack helper for master connections only */ + if (!expected) + conntrack->helper = ip_ct_find_helper(&repl_tuple); + + /* If the expectation is dying, then this is a loser. */ + if (expected + && expected->expectant->helper->timeout + && ! del_timer(&expected->timeout)) + expected = NULL; + + if (expected) { + DEBUGP("conntrack: expectation arrives ct=%p exp=%p\n", + conntrack, expected); + /* Welcome, Mr. Bond. We've been expecting you... */ + IP_NF_ASSERT(master_ct(conntrack)); + __set_bit(IPS_EXPECTED_BIT, &conntrack->status); + conntrack->master = expected; + expected->sibling = conntrack; + LIST_DELETE(&ip_conntrack_expect_list, expected); + expected->expectant->expecting--; + nf_conntrack_get(&master_ct(conntrack)->infos[0]); + } + atomic_inc(&ip_conntrack_count); + WRITE_UNLOCK(&ip_conntrack_lock); + + if (expected && expected->expectfn) + expected->expectfn(conntrack); + return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL]; +} + +/* On success, returns conntrack ptr, sets skb->nfct and ctinfo */ +static inline struct ip_conntrack * +resolve_normal_ct(struct sk_buff *skb, + struct ip_conntrack_protocol *proto, + int *set_reply, + unsigned int hooknum, + enum ip_conntrack_info *ctinfo) +{ + struct ip_conntrack_tuple tuple; + struct ip_conntrack_tuple_hash *h; + + IP_NF_ASSERT((skb->nh.iph->frag_off & htons(IP_OFFSET)) == 0); + + if (!get_tuple(skb->nh.iph, skb, skb->nh.iph->ihl*4, &tuple, proto)) + return NULL; + + /* look for tuple match */ + h = ip_conntrack_find_get(&tuple, NULL); + if (!h) { + h = init_conntrack(&tuple, proto, skb); + if (!h) + return NULL; + if (IS_ERR(h)) + return (void *)h; + } + + /* It exists; we have (non-exclusive) reference. */ + if (DIRECTION(h) == IP_CT_DIR_REPLY) { + *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY; + /* Please set reply bit if this packet OK */ + *set_reply = 1; + } else { + /* Once we've had two way comms, always ESTABLISHED. */ + if (test_bit(IPS_SEEN_REPLY_BIT, &h->ctrack->status)) { + DEBUGP("ip_conntrack_in: normal packet for %p\n", + h->ctrack); + *ctinfo = IP_CT_ESTABLISHED; + } else if (test_bit(IPS_EXPECTED_BIT, &h->ctrack->status)) { + DEBUGP("ip_conntrack_in: related packet for %p\n", + h->ctrack); + *ctinfo = IP_CT_RELATED; + } else { + DEBUGP("ip_conntrack_in: new packet for %p\n", + h->ctrack); + *ctinfo = IP_CT_NEW; + } + *set_reply = 0; + } + skb->nfct = &h->ctrack->infos[*ctinfo]; + return h->ctrack; +} + +/* Netfilter hook itself. */ +unsigned int ip_conntrack_in(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + struct ip_conntrack *ct; + enum ip_conntrack_info ctinfo; + struct ip_conntrack_protocol *proto; + int set_reply; + int ret; + + /* FIXME: Do this right please. --RR */ + (*pskb)->nfcache |= NFC_UNKNOWN; + +/* Doesn't cover locally-generated broadcast, so not worth it. */ +#if 0 + /* Ignore broadcast: no `connection'. */ + if ((*pskb)->pkt_type == PACKET_BROADCAST) { + printk("Broadcast packet!\n"); + return NF_ACCEPT; + } else if (((*pskb)->nh.iph->daddr & htonl(0x000000FF)) + == htonl(0x000000FF)) { + printk("Should bcast: %u.%u.%u.%u->%u.%u.%u.%u (sk=%p, ptype=%u)\n", + NIPQUAD((*pskb)->nh.iph->saddr), + NIPQUAD((*pskb)->nh.iph->daddr), + (*pskb)->sk, (*pskb)->pkt_type); + } +#endif + + /* Previously seen (loopback)? Ignore. Do this before + fragment check. */ + if ((*pskb)->nfct) + return NF_ACCEPT; + + /* Gather fragments. */ + if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) { + *pskb = ip_ct_gather_frags(*pskb); + if (!*pskb) + return NF_STOLEN; + } + + proto = ip_ct_find_proto((*pskb)->nh.iph->protocol); + + /* It may be an icmp error... */ + if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP + && icmp_error_track(*pskb, &ctinfo, hooknum)) + return NF_ACCEPT; + + if (!(ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo))) + /* Not valid part of a connection */ + return NF_ACCEPT; + + if (IS_ERR(ct)) + /* Too stressed to deal. */ + return NF_DROP; + + IP_NF_ASSERT((*pskb)->nfct); + + ret = proto->packet(ct, *pskb, ctinfo); + if (ret == -1) { + /* Invalid */ + nf_conntrack_put((*pskb)->nfct); + (*pskb)->nfct = NULL; + return NF_ACCEPT; + } + + if (ret != NF_DROP && ct->helper) { + ret = ct->helper->help(*pskb, ct, ctinfo); + if (ret == -1) { + /* Invalid */ + nf_conntrack_put((*pskb)->nfct); + (*pskb)->nfct = NULL; + return NF_ACCEPT; + } + } + if (set_reply) + set_bit(IPS_SEEN_REPLY_BIT, &ct->status); + + return ret; +} + +int invert_tuplepr(struct ip_conntrack_tuple *inverse, + const struct ip_conntrack_tuple *orig) +{ + return invert_tuple(inverse, orig, ip_ct_find_proto(orig->dst.protonum)); +} + +static inline int resent_expect(const struct ip_conntrack_expect *i, + const struct ip_conntrack_tuple *tuple, + const struct ip_conntrack_tuple *mask) +{ + DEBUGP("resent_expect\n"); + DEBUGP(" tuple: "); DUMP_TUPLE(&i->tuple); + DEBUGP("ct_tuple: "); DUMP_TUPLE(&i->ct_tuple); + DEBUGP("test tuple: "); DUMP_TUPLE(tuple); + return (((i->ct_tuple.dst.protonum == 0 && ip_ct_tuple_equal(&i->tuple, tuple)) + || (i->ct_tuple.dst.protonum && ip_ct_tuple_equal(&i->ct_tuple, tuple))) + && ip_ct_tuple_equal(&i->mask, mask)); +} + +/* Would two expected things clash? */ +static inline int expect_clash(const struct ip_conntrack_expect *i, + const struct ip_conntrack_tuple *tuple, + const struct ip_conntrack_tuple *mask) +{ + /* Part covered by intersection of masks must be unequal, + otherwise they clash */ + struct ip_conntrack_tuple intersect_mask + = { { i->mask.src.ip & mask->src.ip, + { i->mask.src.u.all & mask->src.u.all } }, + { i->mask.dst.ip & mask->dst.ip, + { i->mask.dst.u.all & mask->dst.u.all }, + i->mask.dst.protonum & mask->dst.protonum } }; + + return ip_ct_tuple_mask_cmp(&i->tuple, tuple, &intersect_mask); +} + +inline void ip_conntrack_unexpect_related(struct ip_conntrack_expect *expect) +{ + WRITE_LOCK(&ip_conntrack_lock); + unexpect_related(expect); + WRITE_UNLOCK(&ip_conntrack_lock); +} + +static void expectation_timed_out(unsigned long ul_expect) +{ + struct ip_conntrack_expect *expect = (void *) ul_expect; + + DEBUGP("expectation %p timed out\n", expect); + WRITE_LOCK(&ip_conntrack_lock); + __unexpect_related(expect); + WRITE_UNLOCK(&ip_conntrack_lock); +} + +/* Add a related connection. */ +int ip_conntrack_expect_related(struct ip_conntrack *related_to, + struct ip_conntrack_expect *expect) +{ + struct ip_conntrack_expect *old, *new; + int ret = 0; + + WRITE_LOCK(&ip_conntrack_lock); + /* Because of the write lock, no reader can walk the lists, + * so there is no need to use the tuple lock too */ + + DEBUGP("ip_conntrack_expect_related %p\n", related_to); + DEBUGP("tuple: "); DUMP_TUPLE(&expect->tuple); + DEBUGP("mask: "); DUMP_TUPLE(&expect->mask); + + old = LIST_FIND(&ip_conntrack_expect_list, resent_expect, + struct ip_conntrack_expect *, &expect->tuple, + &expect->mask); + if (old) { + /* Helper private data may contain offsets but no pointers + pointing into the payload - otherwise we should have to copy + the data filled out by the helper over the old one */ + DEBUGP("expect_related: resent packet\n"); + if (related_to->helper->timeout) { + if (!del_timer(&old->timeout)) { + /* expectation is dying. Fall through */ + old = NULL; + } else { + old->timeout.expires = jiffies + + related_to->helper->timeout * HZ; + add_timer(&old->timeout); + } + } + + if (old) { + WRITE_UNLOCK(&ip_conntrack_lock); + return -EEXIST; + } + } else if (related_to->helper->max_expected && + related_to->expecting >= related_to->helper->max_expected) { + struct list_head *cur_item; + /* old == NULL */ + if (!(related_to->helper->flags & + IP_CT_HELPER_F_REUSE_EXPECT)) { + WRITE_UNLOCK(&ip_conntrack_lock); + if (net_ratelimit()) + printk(KERN_WARNING + "ip_conntrack: max number of expected " + "connections %i of %s reached for " + "%u.%u.%u.%u->%u.%u.%u.%u\n", + related_to->helper->max_expected, + related_to->helper->name, + NIPQUAD(related_to->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip), + NIPQUAD(related_to->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip)); + return -EPERM; + } + DEBUGP("ip_conntrack: max number of expected " + "connections %i of %s reached for " + "%u.%u.%u.%u->%u.%u.%u.%u, reusing\n", + related_to->helper->max_expected, + related_to->helper->name, + NIPQUAD(related_to->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip), + NIPQUAD(related_to->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip)); + + /* choose the the oldest expectation to evict */ + list_for_each(cur_item, &related_to->sibling_list) { + struct ip_conntrack_expect *cur; + + cur = list_entry(cur_item, + struct ip_conntrack_expect, + expected_list); + if (cur->sibling == NULL) { + old = cur; + break; + } + } + + /* (!old) cannot happen, since related_to->expecting is the + * number of unconfirmed expects */ + IP_NF_ASSERT(old); + + /* newnat14 does not reuse the real allocated memory + * structures but rather unexpects the old and + * allocates a new. unexpect_related will decrement + * related_to->expecting. + */ + unexpect_related(old); + ret = -EPERM; + } else if (LIST_FIND(&ip_conntrack_expect_list, expect_clash, + struct ip_conntrack_expect *, &expect->tuple, + &expect->mask)) { + WRITE_UNLOCK(&ip_conntrack_lock); + DEBUGP("expect_related: busy!\n"); + return -EBUSY; + } + + new = (struct ip_conntrack_expect *) + kmalloc(sizeof(struct ip_conntrack_expect), GFP_ATOMIC); + if (!new) { + WRITE_UNLOCK(&ip_conntrack_lock); + DEBUGP("expect_relaed: OOM allocating expect\n"); + return -ENOMEM; + } + + DEBUGP("new expectation %p of conntrack %p\n", new, related_to); + memcpy(new, expect, sizeof(*expect)); + new->expectant = related_to; + new->sibling = NULL; + atomic_set(&new->use, 1); + + /* add to expected list for this connection */ + list_add(&new->expected_list, &related_to->sibling_list); + /* add to global list of expectations */ + list_prepend(&ip_conntrack_expect_list, &new->list); + /* add and start timer if required */ + if (related_to->helper->timeout) { + init_timer(&new->timeout); + new->timeout.data = (unsigned long)new; + new->timeout.function = expectation_timed_out; + new->timeout.expires = jiffies + + related_to->helper->timeout * HZ; + add_timer(&new->timeout); + } + related_to->expecting++; + + WRITE_UNLOCK(&ip_conntrack_lock); + + return ret; +} + +/* Change tuple in an existing expectation */ +int ip_conntrack_change_expect(struct ip_conntrack_expect *expect, + struct ip_conntrack_tuple *newtuple) +{ + int ret; + + MUST_BE_READ_LOCKED(&ip_conntrack_lock); + WRITE_LOCK(&ip_conntrack_expect_tuple_lock); + + DEBUGP("change_expect:\n"); + DEBUGP("exp tuple: "); DUMP_TUPLE(&expect->tuple); + DEBUGP("exp mask: "); DUMP_TUPLE(&expect->mask); + DEBUGP("newtuple: "); DUMP_TUPLE(newtuple); + if (expect->ct_tuple.dst.protonum == 0) { + /* Never seen before */ + DEBUGP("change expect: never seen before\n"); + if (!ip_ct_tuple_equal(&expect->tuple, newtuple) + && LIST_FIND(&ip_conntrack_expect_list, expect_clash, + struct ip_conntrack_expect *, newtuple, &expect->mask)) { + /* Force NAT to find an unused tuple */ + ret = -1; + } else { + memcpy(&expect->ct_tuple, &expect->tuple, sizeof(expect->tuple)); + memcpy(&expect->tuple, newtuple, sizeof(expect->tuple)); + ret = 0; + } + } else { + /* Resent packet */ + DEBUGP("change expect: resent packet\n"); + if (ip_ct_tuple_equal(&expect->tuple, newtuple)) { + ret = 0; + } else { + /* Force NAT to choose again the same port */ + ret = -1; + } + } + WRITE_UNLOCK(&ip_conntrack_expect_tuple_lock); + + return ret; +} + +/* Alter reply tuple (maybe alter helper). If it's already taken, + return 0 and don't do alteration. */ +int ip_conntrack_alter_reply(struct ip_conntrack *conntrack, + const struct ip_conntrack_tuple *newreply) +{ + WRITE_LOCK(&ip_conntrack_lock); + if (__ip_conntrack_find(newreply, conntrack)) { + WRITE_UNLOCK(&ip_conntrack_lock); + return 0; + } + /* Should be unconfirmed, so not in hash table yet */ + IP_NF_ASSERT(!is_confirmed(conntrack)); + + DEBUGP("Altering reply tuple of %p to ", conntrack); + DUMP_TUPLE(newreply); + + conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = *newreply; + if (!conntrack->master) + conntrack->helper = LIST_FIND(&helpers, helper_cmp, + struct ip_conntrack_helper *, + newreply); + WRITE_UNLOCK(&ip_conntrack_lock); + + return 1; +} + +int ip_conntrack_helper_register(struct ip_conntrack_helper *me) +{ + WRITE_LOCK(&ip_conntrack_lock); + list_prepend(&helpers, me); + WRITE_UNLOCK(&ip_conntrack_lock); + + return 0; +} + +static inline int unhelp(struct ip_conntrack_tuple_hash *i, + const struct ip_conntrack_helper *me) +{ + if (i->ctrack->helper == me) { + /* Get rid of any expected. */ + remove_expectations(i->ctrack, 0); + /* And *then* set helper to NULL */ + i->ctrack->helper = NULL; + } + return 0; +} + +void ip_conntrack_helper_unregister(struct ip_conntrack_helper *me) +{ + unsigned int i; + + /* Need write lock here, to delete helper. */ + WRITE_LOCK(&ip_conntrack_lock); + LIST_DELETE(&helpers, me); + + /* Get rid of expecteds, set helpers to NULL. */ + for (i = 0; i < ip_conntrack_htable_size; i++) + LIST_FIND_W(&ip_conntrack_hash[i], unhelp, + struct ip_conntrack_tuple_hash *, me); + WRITE_UNLOCK(&ip_conntrack_lock); + + /* Someone could be still looking at the helper in a bh. */ + synchronize_net(); +} + +/* Refresh conntrack for this many jiffies. */ +void ip_ct_refresh(struct ip_conntrack *ct, unsigned long extra_jiffies) +{ + IP_NF_ASSERT(ct->timeout.data == (unsigned long)ct); + + WRITE_LOCK(&ip_conntrack_lock); + /* If not in hash table, timer will not be active yet */ + if (!is_confirmed(ct)) + ct->timeout.expires = extra_jiffies; + else { + /* Need del_timer for race avoidance (may already be dying). */ + if (del_timer(&ct->timeout)) { + ct->timeout.expires = jiffies + extra_jiffies; + add_timer(&ct->timeout); + } + } + WRITE_UNLOCK(&ip_conntrack_lock); +} + +/* Returns new sk_buff, or NULL */ +struct sk_buff * +ip_ct_gather_frags(struct sk_buff *skb) +{ + struct sock *sk = skb->sk; +#ifdef CONFIG_NETFILTER_DEBUG + unsigned int olddebug = skb->nf_debug; +#endif + if (sk) { + sock_hold(sk); + skb_orphan(skb); + } + + local_bh_disable(); + skb = ip_defrag(skb); + local_bh_enable(); + + if (!skb) { + if (sk) + sock_put(sk); + return skb; + } + + if (sk) { + skb_set_owner_w(skb, sk); + sock_put(sk); + } + + ip_send_check(skb->nh.iph); + skb->nfcache |= NFC_ALTERED; +#ifdef CONFIG_NETFILTER_DEBUG + /* Packet path as if nothing had happened. */ + skb->nf_debug = olddebug; +#endif + return skb; +} + +/* Used by ipt_REJECT. */ +static void ip_conntrack_attach(struct sk_buff *nskb, struct nf_ct_info *nfct) +{ + struct ip_conntrack *ct; + enum ip_conntrack_info ctinfo; + + ct = __ip_conntrack_get(nfct, &ctinfo); + + /* This ICMP is in reverse direction to the packet which + caused it */ + if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) + ctinfo = IP_CT_RELATED + IP_CT_IS_REPLY; + else + ctinfo = IP_CT_RELATED; + + /* Attach new skbuff, and increment count */ + nskb->nfct = &ct->infos[ctinfo]; + atomic_inc(&ct->ct_general.use); +} + +static inline int +do_kill(const struct ip_conntrack_tuple_hash *i, + int (*kill)(const struct ip_conntrack *i, void *data), + void *data) +{ + return kill(i->ctrack, data); +} + +/* Bring out ya dead! */ +static struct ip_conntrack_tuple_hash * +get_next_corpse(int (*kill)(const struct ip_conntrack *i, void *data), + void *data) +{ + struct ip_conntrack_tuple_hash *h = NULL; + unsigned int i; + + READ_LOCK(&ip_conntrack_lock); + for (i = 0; !h && i < ip_conntrack_htable_size; i++) { + h = LIST_FIND(&ip_conntrack_hash[i], do_kill, + struct ip_conntrack_tuple_hash *, kill, data); + } + if (h) + atomic_inc(&h->ctrack->ct_general.use); + READ_UNLOCK(&ip_conntrack_lock); + + return h; +} + +void +ip_ct_selective_cleanup(int (*kill)(const struct ip_conntrack *i, void *data), + void *data) +{ + struct ip_conntrack_tuple_hash *h; + + /* This is order n^2, by the way. */ + while ((h = get_next_corpse(kill, data)) != NULL) { + /* Time to push up daises... */ + if (del_timer(&h->ctrack->timeout)) + death_by_timeout((unsigned long)h->ctrack); + /* ... else the timer will get him soon. */ + + ip_conntrack_put(h->ctrack); + } +} + +/* Fast function for those who don't want to parse /proc (and I don't + blame them). */ +/* Reversing the socket's dst/src point of view gives us the reply + mapping. */ +static int +getorigdst(struct sock *sk, int optval, void *user, int *len) +{ + struct inet_opt *inet = inet_sk(sk); + struct ip_conntrack_tuple_hash *h; + struct ip_conntrack_tuple tuple; + + IP_CT_TUPLE_U_BLANK(&tuple); + tuple.src.ip = inet->rcv_saddr; + tuple.src.u.tcp.port = inet->sport; + tuple.dst.ip = inet->daddr; + tuple.dst.u.tcp.port = inet->dport; + tuple.dst.protonum = IPPROTO_TCP; + + /* We only do TCP at the moment: is there a better way? */ + if (strcmp(sk->sk_prot->name, "TCP")) { + DEBUGP("SO_ORIGINAL_DST: Not a TCP socket\n"); + return -ENOPROTOOPT; + } + + if ((unsigned int) *len < sizeof(struct sockaddr_in)) { + DEBUGP("SO_ORIGINAL_DST: len %u not %u\n", + *len, sizeof(struct sockaddr_in)); + return -EINVAL; + } + + h = ip_conntrack_find_get(&tuple, NULL); + if (h) { + struct sockaddr_in sin; + + sin.sin_family = AF_INET; + sin.sin_port = h->ctrack->tuplehash[IP_CT_DIR_ORIGINAL] + .tuple.dst.u.tcp.port; + sin.sin_addr.s_addr = h->ctrack->tuplehash[IP_CT_DIR_ORIGINAL] + .tuple.dst.ip; + + DEBUGP("SO_ORIGINAL_DST: %u.%u.%u.%u %u\n", + NIPQUAD(sin.sin_addr.s_addr), ntohs(sin.sin_port)); + ip_conntrack_put(h->ctrack); + if (copy_to_user(user, &sin, sizeof(sin)) != 0) + return -EFAULT; + else + return 0; + } + DEBUGP("SO_ORIGINAL_DST: Can't find %u.%u.%u.%u/%u-%u.%u.%u.%u/%u.\n", + NIPQUAD(tuple.src.ip), ntohs(tuple.src.u.tcp.port), + NIPQUAD(tuple.dst.ip), ntohs(tuple.dst.u.tcp.port)); + return -ENOENT; +} + +static struct nf_sockopt_ops so_getorigdst = { + .pf = PF_INET, + .get_optmin = SO_ORIGINAL_DST, + .get_optmax = SO_ORIGINAL_DST+1, + .get = &getorigdst, +}; + +static int kill_all(const struct ip_conntrack *i, void *data) +{ + return 1; +} + +/* Mishearing the voices in his head, our hero wonders how he's + supposed to kill the mall. */ +void ip_conntrack_cleanup(void) +{ + ip_ct_attach = NULL; + /* This makes sure all current packets have passed through + netfilter framework. Roll on, two-stage module + delete... */ + synchronize_net(); + + i_see_dead_people: + ip_ct_selective_cleanup(kill_all, NULL); + if (atomic_read(&ip_conntrack_count) != 0) { + schedule(); + goto i_see_dead_people; + } + + kmem_cache_destroy(ip_conntrack_cachep); + vfree(ip_conntrack_hash); + nf_unregister_sockopt(&so_getorigdst); +} + +static int hashsize; +MODULE_PARM(hashsize, "i"); + +int __init ip_conntrack_init(void) +{ + unsigned int i; + int ret; + + /* Idea from tcp.c: use 1/16384 of memory. On i386: 32MB + * machine has 256 buckets. >= 1GB machines have 8192 buckets. */ + if (hashsize) { + ip_conntrack_htable_size = hashsize; + } else { + ip_conntrack_htable_size + = (((num_physpages << PAGE_SHIFT) / 16384) + / sizeof(struct list_head)); + if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE)) + ip_conntrack_htable_size = 8192; + if (ip_conntrack_htable_size < 16) + ip_conntrack_htable_size = 16; + } + ip_conntrack_max = 8 * ip_conntrack_htable_size; + + printk("ip_conntrack version %s (%u buckets, %d max)" + " - %Zd bytes per conntrack\n", IP_CONNTRACK_VERSION, + ip_conntrack_htable_size, ip_conntrack_max, + sizeof(struct ip_conntrack)); + + ret = nf_register_sockopt(&so_getorigdst); + if (ret != 0) { + printk(KERN_ERR "Unable to register netfilter socket option\n"); + return ret; + } + + ip_conntrack_hash = vmalloc(sizeof(struct list_head) + * ip_conntrack_htable_size); + if (!ip_conntrack_hash) { + printk(KERN_ERR "Unable to create ip_conntrack_hash\n"); + goto err_unreg_sockopt; + } + + ip_conntrack_cachep = kmem_cache_create("ip_conntrack", + sizeof(struct ip_conntrack), 0, + SLAB_HWCACHE_ALIGN, NULL, NULL); + if (!ip_conntrack_cachep) { + printk(KERN_ERR "Unable to create ip_conntrack slab cache\n"); + goto err_free_hash; + } + /* Don't NEED lock here, but good form anyway. */ + WRITE_LOCK(&ip_conntrack_lock); + /* Sew in builtin protocols. */ + list_append(&protocol_list, &ip_conntrack_protocol_tcp); + list_append(&protocol_list, &ip_conntrack_protocol_udp); + list_append(&protocol_list, &ip_conntrack_protocol_icmp); + WRITE_UNLOCK(&ip_conntrack_lock); + + for (i = 0; i < ip_conntrack_htable_size; i++) + INIT_LIST_HEAD(&ip_conntrack_hash[i]); + + /* For use by ipt_REJECT */ + ip_ct_attach = ip_conntrack_attach; + return ret; + +err_free_hash: + vfree(ip_conntrack_hash); +err_unreg_sockopt: + nf_unregister_sockopt(&so_getorigdst); + + return -ENOMEM; +} diff -Nur linux-2.6.0-test11.org/net/ipv4/netfilter/ip_conntrack_core.c.rej linux-2.6.0-test11/net/ipv4/netfilter/ip_conntrack_core.c.rej --- linux-2.6.0-test11.org/net/ipv4/netfilter/ip_conntrack_core.c.rej 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.0-test11/net/ipv4/netfilter/ip_conntrack_core.c.rej 2003-12-17 14:02:02.000000000 +0100 @@ -0,0 +1,17 @@ +*************** +*** 142,147 **** + tuple->dst.ip = iph->daddr; + tuple->dst.protonum = iph->protocol; + + ret = protocol->pkt_to_tuple((u_int32_t *)iph + iph->ihl, + len - 4*iph->ihl, + tuple); +--- 142,149 ---- + tuple->dst.ip = iph->daddr; + tuple->dst.protonum = iph->protocol; + ++ tuple->src.u.all = tuple->dst.u.all = 0; ++ + ret = protocol->pkt_to_tuple((u_int32_t *)iph + iph->ihl, + len - 4*iph->ihl, + tuple); diff -Nur linux-2.6.0-test11.org/net/ipv4/netfilter/ip_nat_core.c linux-2.6.0-test11/net/ipv4/netfilter/ip_nat_core.c --- linux-2.6.0-test11.org/net/ipv4/netfilter/ip_nat_core.c 2003-11-26 21:43:07.000000000 +0100 +++ linux-2.6.0-test11/net/ipv4/netfilter/ip_nat_core.c 2003-12-17 14:02:03.000000000 +0100 @@ -432,7 +432,7 @@ *tuple = *orig_tuple; while ((rptr = find_best_ips_proto_fast(tuple, mr, conntrack, hooknum)) != NULL) { - DEBUGP("Found best for "); DUMP_TUPLE(tuple); + DEBUGP("Found best for "); DUMP_TUPLE_RAW(tuple); /* 3) The per-protocol part of the manip is made to map into the range to make a unique tuple. */ @@ -573,9 +573,9 @@ HOOK2MANIP(hooknum)==IP_NAT_MANIP_SRC ? "SRC" : "DST", conntrack); DEBUGP("Original: "); - DUMP_TUPLE(&orig_tp); + DUMP_TUPLE_RAW(&orig_tp); DEBUGP("New: "); - DUMP_TUPLE(&new_tuple); + DUMP_TUPLE_RAW(&new_tuple); #endif /* We now have two tuples (SRCIP/SRCPT/DSTIP/DSTPT): diff -Nur linux-2.6.0-test11.org/net/ipv4/netfilter/ip_nat_core.c.orig linux-2.6.0-test11/net/ipv4/netfilter/ip_nat_core.c.orig --- linux-2.6.0-test11.org/net/ipv4/netfilter/ip_nat_core.c.orig 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.0-test11/net/ipv4/netfilter/ip_nat_core.c.orig 2003-11-26 21:43:07.000000000 +0100 @@ -0,0 +1,1030 @@ +/* NAT for netfilter; shared with compatibility layer. */ + +/* (c) 1999 Paul `Rusty' Russell. Licenced under the GNU General + Public Licence. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* For tcp_prot in getorigdst */ +#include +#include + +#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_nat_lock) +#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_nat_lock) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +DECLARE_RWLOCK(ip_nat_lock); +DECLARE_RWLOCK_EXTERN(ip_conntrack_lock); + +/* Calculated at init based on memory size */ +static unsigned int ip_nat_htable_size; + +static struct list_head *bysource; +static struct list_head *byipsproto; +LIST_HEAD(protos); +LIST_HEAD(helpers); + +extern struct ip_nat_protocol unknown_nat_protocol; + +/* We keep extra hashes for each conntrack, for fast searching. */ +static inline size_t +hash_by_ipsproto(u_int32_t src, u_int32_t dst, u_int16_t proto) +{ + /* Modified src and dst, to ensure we don't create two + identical streams. */ + return (src + dst + proto) % ip_nat_htable_size; +} + +static inline size_t +hash_by_src(const struct ip_conntrack_manip *manip, u_int16_t proto) +{ + /* Original src, to ensure we map it consistently if poss. */ + return (manip->ip + manip->u.all + proto) % ip_nat_htable_size; +} + +/* Noone using conntrack by the time this called. */ +static void ip_nat_cleanup_conntrack(struct ip_conntrack *conn) +{ + struct ip_nat_info *info = &conn->nat.info; + unsigned int hs, hp; + + if (!info->initialized) + return; + + IP_NF_ASSERT(info->bysource.conntrack); + IP_NF_ASSERT(info->byipsproto.conntrack); + + hs = hash_by_src(&conn->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src, + conn->tuplehash[IP_CT_DIR_ORIGINAL] + .tuple.dst.protonum); + + hp = hash_by_ipsproto(conn->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip, + conn->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip, + conn->tuplehash[IP_CT_DIR_REPLY] + .tuple.dst.protonum); + + WRITE_LOCK(&ip_nat_lock); + LIST_DELETE(&bysource[hs], &info->bysource); + LIST_DELETE(&byipsproto[hp], &info->byipsproto); + WRITE_UNLOCK(&ip_nat_lock); +} + +/* We do checksum mangling, so if they were wrong before they're still + * wrong. Also works for incomplete packets (eg. ICMP dest + * unreachables.) */ +u_int16_t +ip_nat_cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck) +{ + u_int32_t diffs[] = { oldvalinv, newval }; + return csum_fold(csum_partial((char *)diffs, sizeof(diffs), + oldcheck^0xFFFF)); +} + +static inline int cmp_proto(const struct ip_nat_protocol *i, int proto) +{ + return i->protonum == proto; +} + +struct ip_nat_protocol * +find_nat_proto(u_int16_t protonum) +{ + struct ip_nat_protocol *i; + + MUST_BE_READ_LOCKED(&ip_nat_lock); + i = LIST_FIND(&protos, cmp_proto, struct ip_nat_protocol *, protonum); + if (!i) + i = &unknown_nat_protocol; + return i; +} + +/* Is this tuple already taken? (not by us) */ +int +ip_nat_used_tuple(const struct ip_conntrack_tuple *tuple, + const struct ip_conntrack *ignored_conntrack) +{ + /* Conntrack tracking doesn't keep track of outgoing tuples; only + incoming ones. NAT means they don't have a fixed mapping, + so we invert the tuple and look for the incoming reply. + + We could keep a separate hash if this proves too slow. */ + struct ip_conntrack_tuple reply; + + invert_tuplepr(&reply, tuple); + return ip_conntrack_tuple_taken(&reply, ignored_conntrack); +} + +/* Does tuple + the source manip come within the range mr */ +static int +in_range(const struct ip_conntrack_tuple *tuple, + const struct ip_conntrack_manip *manip, + const struct ip_nat_multi_range *mr) +{ + struct ip_nat_protocol *proto = find_nat_proto(tuple->dst.protonum); + unsigned int i; + struct ip_conntrack_tuple newtuple = { *manip, tuple->dst }; + + for (i = 0; i < mr->rangesize; i++) { + /* If we are allowed to map IPs, then we must be in the + range specified, otherwise we must be unchanged. */ + if (mr->range[i].flags & IP_NAT_RANGE_MAP_IPS) { + if (ntohl(newtuple.src.ip) < ntohl(mr->range[i].min_ip) + || (ntohl(newtuple.src.ip) + > ntohl(mr->range[i].max_ip))) + continue; + } else { + if (newtuple.src.ip != tuple->src.ip) + continue; + } + + if (!(mr->range[i].flags & IP_NAT_RANGE_PROTO_SPECIFIED) + || proto->in_range(&newtuple, IP_NAT_MANIP_SRC, + &mr->range[i].min, &mr->range[i].max)) + return 1; + } + return 0; +} + +static inline int +src_cmp(const struct ip_nat_hash *i, + const struct ip_conntrack_tuple *tuple, + const struct ip_nat_multi_range *mr) +{ + return (i->conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum + == tuple->dst.protonum + && i->conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip + == tuple->src.ip + && i->conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.all + == tuple->src.u.all + && in_range(tuple, + &i->conntrack->tuplehash[IP_CT_DIR_ORIGINAL] + .tuple.src, + mr)); +} + +/* Only called for SRC manip */ +static struct ip_conntrack_manip * +find_appropriate_src(const struct ip_conntrack_tuple *tuple, + const struct ip_nat_multi_range *mr) +{ + unsigned int h = hash_by_src(&tuple->src, tuple->dst.protonum); + struct ip_nat_hash *i; + + MUST_BE_READ_LOCKED(&ip_nat_lock); + i = LIST_FIND(&bysource[h], src_cmp, struct ip_nat_hash *, tuple, mr); + if (i) + return &i->conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src; + else + return NULL; +} + +#ifdef CONFIG_IP_NF_NAT_LOCAL +/* If it's really a local destination manip, it may need to do a + source manip too. */ +static int +do_extra_mangle(u_int32_t var_ip, u_int32_t *other_ipp) +{ + struct flowi fl = { .nl_u = { .ip4_u = { .daddr = var_ip } } }; + struct rtable *rt; + + /* FIXME: IPTOS_TOS(iph->tos) --RR */ + if (ip_route_output_key(&rt, &fl) != 0) { + DEBUGP("do_extra_mangle: Can't get route to %u.%u.%u.%u\n", + NIPQUAD(var_ip)); + return 0; + } + + *other_ipp = rt->rt_src; + ip_rt_put(rt); + return 1; +} +#endif + +/* Simple way to iterate through all. */ +static inline int fake_cmp(const struct ip_nat_hash *i, + u_int32_t src, u_int32_t dst, u_int16_t protonum, + unsigned int *score, + const struct ip_conntrack *conntrack) +{ + /* Compare backwards: we're dealing with OUTGOING tuples, and + inside the conntrack is the REPLY tuple. Don't count this + conntrack. */ + if (i->conntrack != conntrack + && i->conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip == dst + && i->conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip == src + && (i->conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.dst.protonum + == protonum)) + (*score)++; + return 0; +} + +static inline unsigned int +count_maps(u_int32_t src, u_int32_t dst, u_int16_t protonum, + const struct ip_conntrack *conntrack) +{ + unsigned int score = 0; + unsigned int h; + + MUST_BE_READ_LOCKED(&ip_nat_lock); + h = hash_by_ipsproto(src, dst, protonum); + LIST_FIND(&byipsproto[h], fake_cmp, struct ip_nat_hash *, + src, dst, protonum, &score, conntrack); + + return score; +} + +/* For [FUTURE] fragmentation handling, we want the least-used + src-ip/dst-ip/proto triple. Fairness doesn't come into it. Thus + if the range specifies 1.2.3.4 ports 10000-10005 and 1.2.3.5 ports + 1-65535, we don't do pro-rata allocation based on ports; we choose + the ip with the lowest src-ip/dst-ip/proto usage. + + If an allocation then fails (eg. all 6 ports used in the 1.2.3.4 + range), we eliminate that and try again. This is not the most + efficient approach, but if you're worried about that, don't hand us + ranges you don't really have. */ +static struct ip_nat_range * +find_best_ips_proto(struct ip_conntrack_tuple *tuple, + const struct ip_nat_multi_range *mr, + const struct ip_conntrack *conntrack, + unsigned int hooknum) +{ + unsigned int i; + struct { + const struct ip_nat_range *range; + unsigned int score; + struct ip_conntrack_tuple tuple; + } best = { NULL, 0xFFFFFFFF }; + u_int32_t *var_ipp, *other_ipp, saved_ip, orig_dstip; + static unsigned int randomness; + + if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC) { + var_ipp = &tuple->src.ip; + saved_ip = tuple->dst.ip; + other_ipp = &tuple->dst.ip; + } else { + var_ipp = &tuple->dst.ip; + saved_ip = tuple->src.ip; + other_ipp = &tuple->src.ip; + } + /* Don't do do_extra_mangle unless necessary (overrides + explicit socket bindings, for example) */ + orig_dstip = tuple->dst.ip; + + IP_NF_ASSERT(mr->rangesize >= 1); + for (i = 0; i < mr->rangesize; i++) { + /* Host order */ + u_int32_t minip, maxip, j; + + /* Don't do ranges which are already eliminated. */ + if (mr->range[i].flags & IP_NAT_RANGE_FULL) { + continue; + } + + if (mr->range[i].flags & IP_NAT_RANGE_MAP_IPS) { + minip = ntohl(mr->range[i].min_ip); + maxip = ntohl(mr->range[i].max_ip); + } else + minip = maxip = ntohl(*var_ipp); + + randomness++; + for (j = 0; j < maxip - minip + 1; j++) { + unsigned int score; + + *var_ipp = htonl(minip + (randomness + j) + % (maxip - minip + 1)); + + /* Reset the other ip in case it was mangled by + * do_extra_mangle last time. */ + *other_ipp = saved_ip; + +#ifdef CONFIG_IP_NF_NAT_LOCAL + if (hooknum == NF_IP_LOCAL_OUT + && *var_ipp != orig_dstip + && !do_extra_mangle(*var_ipp, other_ipp)) { + DEBUGP("Range %u %u.%u.%u.%u rt failed!\n", + i, NIPQUAD(*var_ipp)); + /* Can't route? This whole range part is + * probably screwed, but keep trying + * anyway. */ + continue; + } +#endif + + /* Count how many others map onto this. */ + score = count_maps(tuple->src.ip, tuple->dst.ip, + tuple->dst.protonum, conntrack); + if (score < best.score) { + /* Optimization: doesn't get any better than + this. */ + if (score == 0) + return (struct ip_nat_range *) + &mr->range[i]; + + best.score = score; + best.tuple = *tuple; + best.range = &mr->range[i]; + } + } + } + *tuple = best.tuple; + + /* Discard const. */ + return (struct ip_nat_range *)best.range; +} + +/* Fast version doesn't iterate through hash chains, but only handles + common case of single IP address (null NAT, masquerade) */ +static struct ip_nat_range * +find_best_ips_proto_fast(struct ip_conntrack_tuple *tuple, + const struct ip_nat_multi_range *mr, + const struct ip_conntrack *conntrack, + unsigned int hooknum) +{ + if (mr->rangesize != 1 + || (mr->range[0].flags & IP_NAT_RANGE_FULL) + || ((mr->range[0].flags & IP_NAT_RANGE_MAP_IPS) + && mr->range[0].min_ip != mr->range[0].max_ip)) + return find_best_ips_proto(tuple, mr, conntrack, hooknum); + + if (mr->range[0].flags & IP_NAT_RANGE_MAP_IPS) { + if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC) + tuple->src.ip = mr->range[0].min_ip; + else { + /* Only do extra mangle when required (breaks + socket binding) */ +#ifdef CONFIG_IP_NF_NAT_LOCAL + if (tuple->dst.ip != mr->range[0].min_ip + && hooknum == NF_IP_LOCAL_OUT + && !do_extra_mangle(mr->range[0].min_ip, + &tuple->src.ip)) + return NULL; +#endif + tuple->dst.ip = mr->range[0].min_ip; + } + } + + /* Discard const. */ + return (struct ip_nat_range *)&mr->range[0]; +} + +static int +get_unique_tuple(struct ip_conntrack_tuple *tuple, + const struct ip_conntrack_tuple *orig_tuple, + const struct ip_nat_multi_range *mrr, + struct ip_conntrack *conntrack, + unsigned int hooknum) +{ + struct ip_nat_protocol *proto + = find_nat_proto(orig_tuple->dst.protonum); + struct ip_nat_range *rptr; + unsigned int i; + int ret; + + /* We temporarily use flags for marking full parts, but we + always clean up afterwards */ + struct ip_nat_multi_range *mr = (void *)mrr; + + /* 1) If this srcip/proto/src-proto-part is currently mapped, + and that same mapping gives a unique tuple within the given + range, use that. + + This is only required for source (ie. NAT/masq) mappings. + So far, we don't do local source mappings, so multiple + manips not an issue. */ + if (hooknum == NF_IP_POST_ROUTING) { + struct ip_conntrack_manip *manip; + + manip = find_appropriate_src(orig_tuple, mr); + if (manip) { + /* Apply same source manipulation. */ + *tuple = ((struct ip_conntrack_tuple) + { *manip, orig_tuple->dst }); + DEBUGP("get_unique_tuple: Found current src map\n"); + if (!ip_nat_used_tuple(tuple, conntrack)) + return 1; + } + } + + /* 2) Select the least-used IP/proto combination in the given + range. + */ + *tuple = *orig_tuple; + while ((rptr = find_best_ips_proto_fast(tuple, mr, conntrack, hooknum)) + != NULL) { + DEBUGP("Found best for "); DUMP_TUPLE(tuple); + /* 3) The per-protocol part of the manip is made to + map into the range to make a unique tuple. */ + + /* Only bother mapping if it's not already in range + and unique */ + if ((!(rptr->flags & IP_NAT_RANGE_PROTO_SPECIFIED) + || proto->in_range(tuple, HOOK2MANIP(hooknum), + &rptr->min, &rptr->max)) + && !ip_nat_used_tuple(tuple, conntrack)) { + ret = 1; + goto clear_fulls; + } else { + if (proto->unique_tuple(tuple, rptr, + HOOK2MANIP(hooknum), + conntrack)) { + /* Must be unique. */ + IP_NF_ASSERT(!ip_nat_used_tuple(tuple, + conntrack)); + ret = 1; + goto clear_fulls; + } else if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) { + /* Try implicit source NAT; protocol + may be able to play with ports to + make it unique. */ + struct ip_nat_range r + = { IP_NAT_RANGE_MAP_IPS, + tuple->src.ip, tuple->src.ip, + { 0 }, { 0 } }; + DEBUGP("Trying implicit mapping\n"); + if (proto->unique_tuple(tuple, &r, + IP_NAT_MANIP_SRC, + conntrack)) { + /* Must be unique. */ + IP_NF_ASSERT(!ip_nat_used_tuple + (tuple, conntrack)); + ret = 1; + goto clear_fulls; + } + } + DEBUGP("Protocol can't get unique tuple %u.\n", + hooknum); + } + + /* Eliminate that from range, and try again. */ + rptr->flags |= IP_NAT_RANGE_FULL; + *tuple = *orig_tuple; + } + + ret = 0; + + clear_fulls: + /* Clear full flags. */ + IP_NF_ASSERT(mr->rangesize >= 1); + for (i = 0; i < mr->rangesize; i++) + mr->range[i].flags &= ~IP_NAT_RANGE_FULL; + + return ret; +} + +static inline int +helper_cmp(const struct ip_nat_helper *helper, + const struct ip_conntrack_tuple *tuple) +{ + return ip_ct_tuple_mask_cmp(tuple, &helper->tuple, &helper->mask); +} + +/* Where to manip the reply packets (will be reverse manip). */ +static unsigned int opposite_hook[NF_IP_NUMHOOKS] += { [NF_IP_PRE_ROUTING] = NF_IP_POST_ROUTING, + [NF_IP_POST_ROUTING] = NF_IP_PRE_ROUTING, +#ifdef CONFIG_IP_NF_NAT_LOCAL + [NF_IP_LOCAL_OUT] = NF_IP_LOCAL_IN, + [NF_IP_LOCAL_IN] = NF_IP_LOCAL_OUT, +#endif +}; + +unsigned int +ip_nat_setup_info(struct ip_conntrack *conntrack, + const struct ip_nat_multi_range *mr, + unsigned int hooknum) +{ + struct ip_conntrack_tuple new_tuple, inv_tuple, reply; + struct ip_conntrack_tuple orig_tp; + struct ip_nat_info *info = &conntrack->nat.info; + int in_hashes = info->initialized; + + MUST_BE_WRITE_LOCKED(&ip_nat_lock); + IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING + || hooknum == NF_IP_POST_ROUTING + || hooknum == NF_IP_LOCAL_OUT); + IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS); + IP_NF_ASSERT(!(info->initialized & (1 << HOOK2MANIP(hooknum)))); + + /* What we've got will look like inverse of reply. Normally + this is what is in the conntrack, except for prior + manipulations (future optimization: if num_manips == 0, + orig_tp = + conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple) */ + invert_tuplepr(&orig_tp, + &conntrack->tuplehash[IP_CT_DIR_REPLY].tuple); + +#if 0 + { + unsigned int i; + + DEBUGP("Hook %u (%s), ", hooknum, + HOOK2MANIP(hooknum)==IP_NAT_MANIP_SRC ? "SRC" : "DST"); + DUMP_TUPLE(&orig_tp); + DEBUGP("Range %p: ", mr); + for (i = 0; i < mr->rangesize; i++) { + DEBUGP("%u:%s%s%s %u.%u.%u.%u - %u.%u.%u.%u %u - %u\n", + i, + (mr->range[i].flags & IP_NAT_RANGE_MAP_IPS) + ? " MAP_IPS" : "", + (mr->range[i].flags + & IP_NAT_RANGE_PROTO_SPECIFIED) + ? " PROTO_SPECIFIED" : "", + (mr->range[i].flags & IP_NAT_RANGE_FULL) + ? " FULL" : "", + NIPQUAD(mr->range[i].min_ip), + NIPQUAD(mr->range[i].max_ip), + mr->range[i].min.all, + mr->range[i].max.all); + } + } +#endif + + do { + if (!get_unique_tuple(&new_tuple, &orig_tp, mr, conntrack, + hooknum)) { + DEBUGP("ip_nat_setup_info: Can't get unique for %p.\n", + conntrack); + return NF_DROP; + } + +#if 0 + DEBUGP("Hook %u (%s) %p\n", hooknum, + HOOK2MANIP(hooknum)==IP_NAT_MANIP_SRC ? "SRC" : "DST", + conntrack); + DEBUGP("Original: "); + DUMP_TUPLE(&orig_tp); + DEBUGP("New: "); + DUMP_TUPLE(&new_tuple); +#endif + + /* We now have two tuples (SRCIP/SRCPT/DSTIP/DSTPT): + the original (A/B/C/D') and the mangled one (E/F/G/H'). + + We're only allowed to work with the SRC per-proto + part, so we create inverses of both to start, then + derive the other fields we need. */ + + /* Reply connection: simply invert the new tuple + (G/H/E/F') */ + invert_tuplepr(&reply, &new_tuple); + + /* Alter conntrack table so it recognizes replies. + If fail this race (reply tuple now used), repeat. */ + } while (!ip_conntrack_alter_reply(conntrack, &reply)); + + /* FIXME: We can simply used existing conntrack reply tuple + here --RR */ + /* Create inverse of original: C/D/A/B' */ + invert_tuplepr(&inv_tuple, &orig_tp); + + /* Has source changed?. */ + if (!ip_ct_tuple_src_equal(&new_tuple, &orig_tp)) { + /* In this direction, a source manip. */ + info->manips[info->num_manips++] = + ((struct ip_nat_info_manip) + { IP_CT_DIR_ORIGINAL, hooknum, + IP_NAT_MANIP_SRC, new_tuple.src }); + + IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS); + + /* In the reverse direction, a destination manip. */ + info->manips[info->num_manips++] = + ((struct ip_nat_info_manip) + { IP_CT_DIR_REPLY, opposite_hook[hooknum], + IP_NAT_MANIP_DST, orig_tp.src }); + IP_NF_ASSERT(info->num_manips <= IP_NAT_MAX_MANIPS); + } + + /* Has destination changed? */ + if (!ip_ct_tuple_dst_equal(&new_tuple, &orig_tp)) { + /* In this direction, a destination manip */ + info->manips[info->num_manips++] = + ((struct ip_nat_info_manip) + { IP_CT_DIR_ORIGINAL, hooknum, + IP_NAT_MANIP_DST, reply.src }); + + IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS); + + /* In the reverse direction, a source manip. */ + info->manips[info->num_manips++] = + ((struct ip_nat_info_manip) + { IP_CT_DIR_REPLY, opposite_hook[hooknum], + IP_NAT_MANIP_SRC, inv_tuple.src }); + IP_NF_ASSERT(info->num_manips <= IP_NAT_MAX_MANIPS); + } + + /* If there's a helper, assign it; based on new tuple. */ + if (!conntrack->master) + info->helper = LIST_FIND(&helpers, helper_cmp, struct ip_nat_helper *, + &reply); + + /* It's done. */ + info->initialized |= (1 << HOOK2MANIP(hooknum)); + + if (in_hashes) { + IP_NF_ASSERT(info->bysource.conntrack); + replace_in_hashes(conntrack, info); + } else { + place_in_hashes(conntrack, info); + } + + return NF_ACCEPT; +} + +void replace_in_hashes(struct ip_conntrack *conntrack, + struct ip_nat_info *info) +{ + /* Source has changed, so replace in hashes. */ + unsigned int srchash + = hash_by_src(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL] + .tuple.src, + conntrack->tuplehash[IP_CT_DIR_ORIGINAL] + .tuple.dst.protonum); + /* We place packet as seen OUTGOUNG in byips_proto hash + (ie. reverse dst and src of reply packet. */ + unsigned int ipsprotohash + = hash_by_ipsproto(conntrack->tuplehash[IP_CT_DIR_REPLY] + .tuple.dst.ip, + conntrack->tuplehash[IP_CT_DIR_REPLY] + .tuple.src.ip, + conntrack->tuplehash[IP_CT_DIR_REPLY] + .tuple.dst.protonum); + + IP_NF_ASSERT(info->bysource.conntrack == conntrack); + MUST_BE_WRITE_LOCKED(&ip_nat_lock); + + list_del(&info->bysource.list); + list_del(&info->byipsproto.list); + + list_prepend(&bysource[srchash], &info->bysource); + list_prepend(&byipsproto[ipsprotohash], &info->byipsproto); +} + +void place_in_hashes(struct ip_conntrack *conntrack, + struct ip_nat_info *info) +{ + unsigned int srchash + = hash_by_src(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL] + .tuple.src, + conntrack->tuplehash[IP_CT_DIR_ORIGINAL] + .tuple.dst.protonum); + /* We place packet as seen OUTGOUNG in byips_proto hash + (ie. reverse dst and src of reply packet. */ + unsigned int ipsprotohash + = hash_by_ipsproto(conntrack->tuplehash[IP_CT_DIR_REPLY] + .tuple.dst.ip, + conntrack->tuplehash[IP_CT_DIR_REPLY] + .tuple.src.ip, + conntrack->tuplehash[IP_CT_DIR_REPLY] + .tuple.dst.protonum); + + IP_NF_ASSERT(!info->bysource.conntrack); + + MUST_BE_WRITE_LOCKED(&ip_nat_lock); + info->byipsproto.conntrack = conntrack; + info->bysource.conntrack = conntrack; + + list_prepend(&bysource[srchash], &info->bysource); + list_prepend(&byipsproto[ipsprotohash], &info->byipsproto); +} + +/* Returns true if succeeded. */ +static int +manip_pkt(u_int16_t proto, + struct sk_buff **pskb, + unsigned int iphdroff, + const struct ip_conntrack_manip *manip, + enum ip_nat_manip_type maniptype) +{ + struct iphdr *iph; + + (*pskb)->nfcache |= NFC_ALTERED; + if (!skb_ip_make_writable(pskb, iphdroff+sizeof(iph))) + return 0; + + iph = (void *)(*pskb)->data + iphdroff; + + /* Manipulate protcol part. */ + if (!find_nat_proto(proto)->manip_pkt(pskb, + iphdroff + iph->ihl*4, + manip, maniptype)) + return 0; + + iph = (void *)(*pskb)->data + iphdroff; + + if (maniptype == IP_NAT_MANIP_SRC) { + iph->check = ip_nat_cheat_check(~iph->saddr, manip->ip, + iph->check); + iph->saddr = manip->ip; + } else { + iph->check = ip_nat_cheat_check(~iph->daddr, manip->ip, + iph->check); + iph->daddr = manip->ip; + } + return 1; +} + +static inline int exp_for_packet(struct ip_conntrack_expect *exp, + struct sk_buff *skb) +{ + struct ip_conntrack_protocol *proto; + int ret = 1; + + MUST_BE_READ_LOCKED(&ip_conntrack_lock); + proto = __ip_ct_find_proto(skb->nh.iph->protocol); + if (proto->exp_matches_pkt) + ret = proto->exp_matches_pkt(exp, skb); + + return ret; +} + +/* Do packet manipulations according to binding. */ +unsigned int +do_bindings(struct ip_conntrack *ct, + enum ip_conntrack_info ctinfo, + struct ip_nat_info *info, + unsigned int hooknum, + struct sk_buff **pskb) +{ + unsigned int i; + struct ip_nat_helper *helper; + enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); + int proto = (*pskb)->nh.iph->protocol; + + /* Need nat lock to protect against modification, but neither + conntrack (referenced) and helper (deleted with + synchronize_bh()) can vanish. */ + READ_LOCK(&ip_nat_lock); + for (i = 0; i < info->num_manips; i++) { + if (info->manips[i].direction == dir + && info->manips[i].hooknum == hooknum) { + DEBUGP("Mangling %p: %s to %u.%u.%u.%u %u\n", + *pskb, + info->manips[i].maniptype == IP_NAT_MANIP_SRC + ? "SRC" : "DST", + NIPQUAD(info->manips[i].manip.ip), + htons(info->manips[i].manip.u.all)); + if (!manip_pkt(proto, pskb, 0, + &info->manips[i].manip, + info->manips[i].maniptype)) { + READ_UNLOCK(&ip_nat_lock); + return NF_DROP; + } + } + } + helper = info->helper; + READ_UNLOCK(&ip_nat_lock); + + if (helper) { + struct ip_conntrack_expect *exp = NULL; + struct list_head *cur_item; + int ret = NF_ACCEPT; + int helper_called = 0; + + DEBUGP("do_bindings: helper existing for (%p)\n", ct); + + /* Always defragged for helpers */ + IP_NF_ASSERT(!((*pskb)->nh.iph->frag_off + & htons(IP_MF|IP_OFFSET))); + + /* Have to grab read lock before sibling_list traversal */ + READ_LOCK(&ip_conntrack_lock); + list_for_each(cur_item, &ct->sibling_list) { + exp = list_entry(cur_item, struct ip_conntrack_expect, + expected_list); + + /* if this expectation is already established, skip */ + if (exp->sibling) + continue; + + if (exp_for_packet(exp, *pskb)) { + /* FIXME: May be true multiple times in the + * case of UDP!! */ + DEBUGP("calling nat helper (exp=%p) for packet\n", exp); + ret = helper->help(ct, exp, info, ctinfo, + hooknum, pskb); + if (ret != NF_ACCEPT) { + READ_UNLOCK(&ip_conntrack_lock); + return ret; + } + helper_called = 1; + } + } + /* Helper might want to manip the packet even when there is no + * matching expectation for this packet */ + if (!helper_called && helper->flags & IP_NAT_HELPER_F_ALWAYS) { + DEBUGP("calling nat helper for packet without expectation\n"); + ret = helper->help(ct, NULL, info, ctinfo, + hooknum, pskb); + if (ret != NF_ACCEPT) { + READ_UNLOCK(&ip_conntrack_lock); + return ret; + } + } + READ_UNLOCK(&ip_conntrack_lock); + + /* Adjust sequence number only once per packet + * (helper is called at all hooks) */ + if (proto == IPPROTO_TCP + && (hooknum == NF_IP_POST_ROUTING + || hooknum == NF_IP_LOCAL_IN)) { + DEBUGP("ip_nat_core: adjusting sequence number\n"); + /* future: put this in a l4-proto specific function, + * and call this function here. */ + if (!ip_nat_seq_adjust(pskb, ct, ctinfo)) + ret = NF_DROP; + } + + return ret; + + } else + return NF_ACCEPT; + + /* not reached */ +} + +int +icmp_reply_translation(struct sk_buff **pskb, + struct ip_conntrack *conntrack, + unsigned int hooknum, + int dir) +{ + struct { + struct icmphdr icmp; + struct iphdr ip; + } *inside; + unsigned int i; + struct ip_nat_info *info = &conntrack->nat.info; + int hdrlen; + + if (!skb_ip_make_writable(pskb,(*pskb)->nh.iph->ihl*4+sizeof(*inside))) + return 0; + inside = (void *)(*pskb)->data + (*pskb)->nh.iph->ihl*4; + + /* We're actually going to mangle it beyond trivial checksum + adjustment, so make sure the current checksum is correct. */ + if ((*pskb)->ip_summed != CHECKSUM_UNNECESSARY) { + hdrlen = (*pskb)->nh.iph->ihl * 4; + if ((u16)csum_fold(skb_checksum(*pskb, hdrlen, + (*pskb)->len - hdrlen, 0))) + return 0; + } + + /* Must be RELATED */ + IP_NF_ASSERT((*pskb)->nfct + - (struct ip_conntrack *)(*pskb)->nfct->master + == IP_CT_RELATED + || (*pskb)->nfct + - (struct ip_conntrack *)(*pskb)->nfct->master + == IP_CT_RELATED+IP_CT_IS_REPLY); + + /* Redirects on non-null nats must be dropped, else they'll + start talking to each other without our translation, and be + confused... --RR */ + if (inside->icmp.type == ICMP_REDIRECT) { + /* Don't care about races here. */ + if (info->initialized + != ((1 << IP_NAT_MANIP_SRC) | (1 << IP_NAT_MANIP_DST)) + || info->num_manips != 0) + return 0; + } + + DEBUGP("icmp_reply_translation: translating error %p hook %u dir %s\n", + *pskb, hooknum, dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY"); + /* Note: May not be from a NAT'd host, but probably safest to + do translation always as if it came from the host itself + (even though a "host unreachable" coming from the host + itself is a bit weird). + + More explanation: some people use NAT for anonymizing. + Also, CERT recommends dropping all packets from private IP + addresses (although ICMP errors from internal links with + such addresses are not too uncommon, as Alan Cox points + out) */ + + READ_LOCK(&ip_nat_lock); + for (i = 0; i < info->num_manips; i++) { + DEBUGP("icmp_reply: manip %u dir %s hook %u\n", + i, info->manips[i].direction == IP_CT_DIR_ORIGINAL ? + "ORIG" : "REPLY", info->manips[i].hooknum); + + if (info->manips[i].direction != dir) + continue; + + /* Mapping the inner packet is just like a normal + packet, except it was never src/dst reversed, so + where we would normally apply a dst manip, we apply + a src, and vice versa. */ + if (info->manips[i].hooknum == hooknum) { + DEBUGP("icmp_reply: inner %s -> %u.%u.%u.%u %u\n", + info->manips[i].maniptype == IP_NAT_MANIP_SRC + ? "DST" : "SRC", + NIPQUAD(info->manips[i].manip.ip), + ntohs(info->manips[i].manip.u.udp.port)); + if (!manip_pkt(inside->ip.protocol, pskb, + (*pskb)->nh.iph->ihl*4 + + sizeof(inside->icmp), + &info->manips[i].manip, + !info->manips[i].maniptype)) + goto unlock_fail; + + /* Outer packet needs to have IP header NATed like + it's a reply. */ + + /* Use mapping to map outer packet: 0 give no + per-proto mapping */ + DEBUGP("icmp_reply: outer %s -> %u.%u.%u.%u\n", + info->manips[i].maniptype == IP_NAT_MANIP_SRC + ? "SRC" : "DST", + NIPQUAD(info->manips[i].manip.ip)); + if (!manip_pkt(0, pskb, 0, + &info->manips[i].manip, + info->manips[i].maniptype)) + goto unlock_fail; + } + } + READ_UNLOCK(&ip_nat_lock); + + hdrlen = (*pskb)->nh.iph->ihl * 4; + + inside = (void *)(*pskb)->data + (*pskb)->nh.iph->ihl*4; + + inside->icmp.checksum = 0; + inside->icmp.checksum = csum_fold(skb_checksum(*pskb, hdrlen, + (*pskb)->len - hdrlen, + 0)); + return 1; + + unlock_fail: + READ_UNLOCK(&ip_nat_lock); + return 0; +} + +int __init ip_nat_init(void) +{ + size_t i; + + /* Leave them the same for the moment. */ + ip_nat_htable_size = ip_conntrack_htable_size; + + /* One vmalloc for both hash tables */ + bysource = vmalloc(sizeof(struct list_head) * ip_nat_htable_size*2); + if (!bysource) { + return -ENOMEM; + } + byipsproto = bysource + ip_nat_htable_size; + + /* Sew in builtin protocols. */ + WRITE_LOCK(&ip_nat_lock); + list_append(&protos, &ip_nat_protocol_tcp); + list_append(&protos, &ip_nat_protocol_udp); + list_append(&protos, &ip_nat_protocol_icmp); + WRITE_UNLOCK(&ip_nat_lock); + + for (i = 0; i < ip_nat_htable_size; i++) { + INIT_LIST_HEAD(&bysource[i]); + INIT_LIST_HEAD(&byipsproto[i]); + } + + /* FIXME: Man, this is a hack. */ + IP_NF_ASSERT(ip_conntrack_destroyed == NULL); + ip_conntrack_destroyed = &ip_nat_cleanup_conntrack; + + return 0; +} + +/* Clear NAT section of all conntracks, in case we're loaded again. */ +static int clean_nat(const struct ip_conntrack *i, void *data) +{ + memset((void *)&i->nat, 0, sizeof(i->nat)); + return 0; +} + +/* Not __exit: called from ip_nat_standalone.c:init_or_cleanup() --RR */ +void ip_nat_cleanup(void) +{ + ip_ct_selective_cleanup(&clean_nat, NULL); + ip_conntrack_destroyed = NULL; + vfree(bysource); +}