Index: linux-2.6.7/include/linux/in6.h =================================================================== --- linux-2.6.7.orig/include/linux/in6.h 2004-06-16 07:19:37.000000000 +0200 +++ linux-2.6.7/include/linux/in6.h 2004-08-05 16:14:54.848147402 +0200 @@ -196,4 +196,7 @@ * MCAST_MSFILTER 48 */ +/* Netfilter */ +#define IPV6_NF_ORIGINAL_DST 80 + #endif Index: linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack.h 2004-08-05 16:14:54.849147305 +0200 @@ -0,0 +1,265 @@ +/* + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: include/linux/netfilter_ipv4/ip_conntrack.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _IP6_CONNTRACK_H +#define _IP6_CONNTRACK_H +/* Connection state tracking for netfilter. This is separated from, + but required by, the NAT layer; it can also be used by an iptables + extension. */ + +#include +#include +#include +#include +#include + +enum ip6_conntrack_info +{ + /* Part of an established connection (either direction). */ + IP6_CT_ESTABLISHED, + + /* Like NEW, but related to an existing connection, or ICMP error + (in either direction). */ + IP6_CT_RELATED, + + /* Started a new connection to track (only + IP6_CT_DIR_ORIGINAL); may be a retransmission. */ + IP6_CT_NEW, + + /* >= this indicates reply direction */ + IP6_CT_IS_REPLY, + + /* Number of distinct IP6_CT types (no NEW in reply dirn). */ + IP6_CT_NUMBER = IP6_CT_IS_REPLY * 2 - 1 +}; + +/* Bitset representing status of connection. */ +enum ip6_conntrack_status { + /* It's an expected connection: bit 0 set. This bit never changed */ + IP6S_EXPECTED_BIT = 0, + IP6S_EXPECTED = (1 << IP6S_EXPECTED_BIT), + + /* We've seen packets both ways: bit 1 set. Can be set, not unset. */ + IP6S_SEEN_REPLY_BIT = 1, + IP6S_SEEN_REPLY = (1 << IP6S_SEEN_REPLY_BIT), + + /* Conntrack should never be early-expired. */ + IP6S_ASSURED_BIT = 2, + IP6S_ASSURED = (1 << IP6S_ASSURED_BIT), + + /* Connection is confirmed: originating packet has left box */ + IP6S_CONFIRMED_BIT = 3, + IP6S_CONFIRMED = (1 << IP6S_CONFIRMED_BIT), +}; + +#include +#include + +/* per conntrack: protocol private data */ +union ip6_conntrack_proto { + /* insert conntrack proto private data here */ + struct ip6_ct_tcp tcp; + struct ip6_ct_icmpv6 icmpv6; +}; + +union ip6_conntrack_expect_proto { + /* insert expect proto private data here */ +}; + +/* Add protocol helper include file here */ +#include + +/* per expectation: application helper private data */ +union ip6_conntrack_expect_help { + /* insert conntrack helper private data (expect) here */ + struct ip6_ct_ftp_expect exp_ftp_info; +}; + +/* per conntrack: application helper private data */ +union ip6_conntrack_help { + /* insert conntrack helper private data (master) here */ + struct ip6_ct_ftp_master ct_ftp_info; +}; + +#ifdef __KERNEL__ + +#include +#include + +#ifdef CONFIG_NF_DEBUG +#define IP6_NF_ASSERT(x) \ +do { \ + if (!(x)) \ + /* Wooah! I'm tripping my conntrack in a frenzy of \ + netplay... */ \ + printk("NF_IP6_ASSERT: %s:%i(%s)\n", \ + __FILE__, __LINE__, __FUNCTION__); \ +} while(0) +#else +#define IP6_NF_ASSERT(x) +#endif + +struct ip6_conntrack_expect +{ + /* Internal linked list (global expectation list) */ + struct list_head list; + + /* reference count */ + atomic_t use; + + /* expectation list for this master */ + struct list_head expected_list; + + /* The conntrack of the master connection */ + struct ip6_conntrack *expectant; + + /* The conntrack of the sibling connection, set after + * expectation arrived */ + struct ip6_conntrack *sibling; + + /* IPv6 packet is never NATed */ + /* Tuple saved for conntrack */ +/* + struct ip6_conntrack_tuple ct_tuple; +*/ + + /* Timer function; deletes the expectation. */ + struct timer_list timeout; + + /* Data filled out by the conntrack helpers follow: */ + + /* We expect this tuple, with the following mask */ + struct ip6_conntrack_tuple tuple, mask; + + /* Function to call after setup and insertion */ + int (*expectfn)(struct ip6_conntrack *new); + + /* At which sequence number did this expectation occur */ + u_int32_t seq; + + union ip6_conntrack_expect_proto proto; + + union ip6_conntrack_expect_help help; +}; + +#include +struct ip6_conntrack +{ + /* Usage count in here is 1 for hash table/destruct timer, 1 per skb, + plus 1 for any connection(s) we are `master' for */ + struct nf_conntrack ct_general; + + /* These are my tuples; original and reply */ + struct ip6_conntrack_tuple_hash tuplehash[IP6_CT_DIR_MAX]; + + /* Have we seen traffic both ways yet? (bitset) */ + unsigned long status; + + /* Timer function; drops refcnt when it goes off. */ + struct timer_list timeout; + + /* If we're expecting another related connection, this will be + in expected linked list */ + struct list_head sibling_list; + + /* Current number of expected connections */ + unsigned int expecting; + + /* If we were expected by an expectation, this will be it */ + struct ip6_conntrack_expect *master; + + /* Helper, if any. */ + struct ip6_conntrack_helper *helper; + + /* Our various nf_ct_info structs specify *what* relation this + packet has to the conntrack */ + struct nf_ct_info infos[IP6_CT_NUMBER]; + + /* Storage reserved for other modules: */ + + union ip6_conntrack_proto proto; + + union ip6_conntrack_help help; +}; + +/* get master conntrack via master expectation */ +#define master_ct6(conntr) (conntr->master ? conntr->master->expectant : NULL) + +/* Alter reply tuple (maybe alter helper). If it's already taken, + return 0 and don't do alteration. */ +extern int +ip6_conntrack_alter_reply(struct ip6_conntrack *conntrack, + const struct ip6_conntrack_tuple *newreply); + +/* Is this tuple taken? (ignoring any belonging to the given + conntrack). */ +extern int +ip6_conntrack_tuple_taken(const struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack *ignored_conntrack); + +/* Return conntrack_info and tuple hash for given skb. */ +extern struct ip6_conntrack * +ip6_conntrack_get(struct sk_buff *skb, enum ip6_conntrack_info *ctinfo); + +/* decrement reference count on a conntrack */ +extern inline void ip6_conntrack_put(struct ip6_conntrack *ct); + +/* find unconfirmed expectation based on tuple */ +struct ip6_conntrack_expect * +ip6_conntrack_expect_find_get(const struct ip6_conntrack_tuple *tuple); + +/* decrement reference count on an expectation */ +void ip6_conntrack_expect_put(struct ip6_conntrack_expect *exp); + +/* call to create an explicit dependency on ip6_conntrack. */ +extern void need_ip6_conntrack(void); + +extern int ip6_invert_tuplepr(struct ip6_conntrack_tuple *inverse, + const struct ip6_conntrack_tuple *orig); + +/* Refresh conntrack for this many jiffies */ +extern void ip6_ct_refresh(struct ip6_conntrack *ct, + unsigned long extra_jiffies); + +/* Call me when a conntrack is destroyed. */ +extern void (*ip6_conntrack_destroyed)(struct ip6_conntrack *conntrack); + +/* Returns new sk_buff, or NULL */ +struct sk_buff * +ip6_ct_gather_frags(struct sk_buff *skb); + +/* Delete all conntracks which match. */ +extern void +ip6_ct_selective_cleanup(int (*kill)(const struct ip6_conntrack *i, void *data), + void *data); + +/* It's confirmed if it is, or has been in the hash table. */ +static inline int is_confirmed(struct ip6_conntrack *ct) +{ + return test_bit(IP6S_CONFIRMED_BIT, &ct->status); +} + +extern unsigned int ip6_conntrack_htable_size; + +/* eg. PROVIDES_CONNTRACK6(ftp); */ +#define PROVIDES_CONNTRACK6(name) \ + int needs_ip6_conntrack_##name; \ + EXPORT_SYMBOL(needs_ip6_conntrack_##name) + +/*. eg. NEEDS_CONNTRACK6(ftp); */ +#define NEEDS_CONNTRACK6(name) \ + extern int needs_ip6_conntrack_##name; \ + static int *need_ip6_conntrack_##name __attribute_used__ = &needs_ip6_conntrack_##name + +#endif /* __KERNEL__ */ +#endif /* _IP6_CONNTRACK_H */ Index: linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_core.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_core.h 2004-08-05 16:14:54.851147111 +0200 @@ -0,0 +1,69 @@ +/* + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: include/linux/netfilter_ipv4/ip_conntrack_core.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _IP6_CONNTRACK_CORE_H +#define _IP6_CONNTRACK_CORE_H +#include +#include + +/* This header is used to share core functionality between the + standalone connection tracking module, and the compatibility layer's use + of connection tracking. */ +extern unsigned int ip6_conntrack_in(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)); + +extern int ip6_conntrack_init(void); +extern void ip6_conntrack_cleanup(void); + +struct ip6_conntrack_protocol; +extern struct ip6_conntrack_protocol *ip6_ct_find_proto(u_int8_t protocol); +/* Like above, but you already have conntrack read lock. */ +extern struct ip6_conntrack_protocol *__ip6_ct_find_proto(u_int8_t protocol); +extern struct list_head ip6_protocol_list; + +/* Returns conntrack if it dealt with ICMP, and filled in skb->nfct */ +extern struct ip6_conntrack *icmp6_error_track(struct sk_buff *skb, + unsigned int icmp6off, + enum ip6_conntrack_info *ctinfo, + unsigned int hooknum); +extern int ip6_get_tuple(const struct ipv6hdr *ipv6h, + const struct sk_buff *skb, + unsigned int protoff, + u_int8_t protonum, + struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack_protocol *protocol); + +/* Find a connection corresponding to a tuple. */ +struct ip6_conntrack_tuple_hash * +ip6_conntrack_find_get(const struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack *ignored_conntrack); + +extern int __ip6_conntrack_confirm(struct nf_ct_info *nfct); + +/* Confirm a connection: returns NF_DROP if packet must be dropped. */ +static inline int ip6_conntrack_confirm(struct sk_buff *skb) +{ + if (skb->nfct + && !is_confirmed((struct ip6_conntrack *)skb->nfct->master)) + return __ip6_conntrack_confirm(skb->nfct); + return NF_ACCEPT; +} + +extern struct list_head *ip6_conntrack_hash; +extern struct list_head ip6_conntrack_expect_list; +DECLARE_RWLOCK_EXTERN(ip6_conntrack_lock); +#endif /* _IP6_CONNTRACK_CORE_H */ + Index: linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h 2004-08-05 16:14:54.852147014 +0200 @@ -0,0 +1,57 @@ +/* + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: include/linux/netfilter_ipv4/ip_conntrack_ftp.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _IP6_CONNTRACK_FTP_H +#define _IP6_CONNTRACK_FTP_H +/* FTP tracking. */ + +#ifdef __KERNEL__ + +#include + +/* Protects ftp part of conntracks */ +DECLARE_LOCK_EXTERN(ip6_ftp_lock); + +#define FTP_PORT 21 + +#endif /* __KERNEL__ */ + +enum ip6_ct_ftp_type +{ + /* EPRT command from client */ + IP6_CT_FTP_EPRT, + /* EPSV response from server */ + IP6_CT_FTP_EPSV, +}; + +/* This structure is per expected connection */ +struct ip6_ct_ftp_expect +{ + /* We record seq number and length of ftp ip/port text here: all in + * host order. */ + + /* sequence number of IP address in packet is in ip_conntrack_expect */ + u_int32_t len; /* length of IPv6 address */ + enum ip6_ct_ftp_type ftptype; /* EPRT or EPSV ? */ + u_int16_t port; /* Port that was to be used */ +}; + +/* This structure exists only once per master */ +struct ip6_ct_ftp_master { + /* Next valid seq position for cmd matching after newline */ + u_int32_t seq_aft_nl[IP6_CT_DIR_MAX]; + /* 0 means seq_match_aft_nl not set */ + int seq_aft_nl_set[IP6_CT_DIR_MAX]; +}; + +#endif /* _IP6_CONNTRACK_FTP_H */ Index: linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_helper.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_helper.h 2004-08-05 16:14:54.853146917 +0200 @@ -0,0 +1,57 @@ +/* + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: include/linux/netfilter_ipv4/ip_conntrack_helper.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +/* IP6 connection tracking helpers. */ +#ifndef _IP6_CONNTRACK_HELPER_H +#define _IP6_CONNTRACK_HELPER_H +#include + +struct module; + +/* Reuse expectation when max_expected reached */ +#define IP6_CT_HELPER_F_REUSE_EXPECT 0x01 + +struct ip6_conntrack_helper +{ + struct list_head list; /* Internal use. */ + + const char *name; /* name of the module */ + unsigned char flags; /* Flags (see above) */ + struct module *me; /* pointer to self */ + unsigned int max_expected; /* Maximum number of concurrent + * expected connections */ + unsigned int timeout; /* timeout for expecteds */ + + /* Mask of things we will help (compared against server response) */ + struct ip6_conntrack_tuple tuple; + struct ip6_conntrack_tuple mask; + + /* Function to call when data passes; return verdict, or -1 to + invalidate. */ + int (*help)(const struct sk_buff *skb, + unsigned int protoff, + struct ip6_conntrack *ct, + enum ip6_conntrack_info conntrackinfo); +}; + +extern int ip6_conntrack_helper_register(struct ip6_conntrack_helper *); +extern void ip6_conntrack_helper_unregister(struct ip6_conntrack_helper *); + +extern struct ip6_conntrack_helper *ip6_ct_find_helper(const struct ip6_conntrack_tuple *tuple); + +/* Add an expected connection: can have more than one per connection */ +extern int ip6_conntrack_expect_related(struct ip6_conntrack *related_to, + struct ip6_conntrack_expect *exp); +extern void ip6_conntrack_unexpect_related(struct ip6_conntrack_expect *exp); + +#endif /*_IP6_CONNTRACK_HELPER_H*/ Index: linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h 2004-08-05 16:14:54.854146820 +0200 @@ -0,0 +1,24 @@ +/* + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: include/linux/netfilter_ipv4/ip_conntrack_icmp.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _IP6_CONNTRACK_ICMPV6_H +#define _IP6_CONNTRACK_ICMPV6_H +/* ICMPv6 tracking. */ +#include + +struct ip6_ct_icmpv6 +{ + /* Optimization: when number in == number out, forget immediately. */ + atomic_t count; +}; +#endif /* _IP6_CONNTRACK_ICMPv6_H */ Index: linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h 2004-08-05 16:14:54.855146723 +0200 @@ -0,0 +1,83 @@ +/* + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: include/linux/netfilter_ipv4/ip_conntrack_protocol.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +/* Header for use in defining a given protocol for connection tracking. */ +#ifndef _IP6_CONNTRACK_PROTOCOL_H +#define _IP6_CONNTRACK_PROTOCOL_H +#include +#include + +struct ip6_conntrack_protocol +{ + /* Next pointer. */ + struct list_head list; + + /* Protocol number. */ + u_int8_t proto; + + /* Protocol name */ + const char *name; + + /* Try to fill in the third arg: dataoff is offset past IPv6 + hdr and IPv6 ext hdrs. Return true if possible. */ + int (*pkt_to_tuple)(const struct sk_buff *skb, + unsigned int dataoff, + struct ip6_conntrack_tuple *tuple); + + /* Invert the per-proto part of the tuple: ie. turn xmit into reply. + * Some packets can't be inverted: return 0 in that case. + */ + int (*invert_tuple)(struct ip6_conntrack_tuple *inverse, + const struct ip6_conntrack_tuple *orig); + + /* Print out the per-protocol part of the tuple. */ + unsigned int (*print_tuple)(char *buffer, + const struct ip6_conntrack_tuple *); + + /* Print out the private part of the conntrack. */ + unsigned int (*print_conntrack)(char *buffer, + const struct ip6_conntrack *); + + /* Returns verdict for packet, or -1 for invalid. */ + int (*packet)(struct ip6_conntrack *conntrack, + const struct sk_buff *skb, + unsigned int dataoff, + enum ip6_conntrack_info ctinfo); + + /* Called when a new connection for this protocol found; + * returns TRUE if it's OK. If so, packet() called next. */ + int (*new)(struct ip6_conntrack *conntrack, const struct sk_buff *skb, + unsigned int dataoff); + + /* Called when a conntrack entry is destroyed */ + void (*destroy)(struct ip6_conntrack *conntrack); + + /* Has to decide if a expectation matches one packet or not */ + int (*exp_matches_pkt)(struct ip6_conntrack_expect *exp, + const struct sk_buff *skb, + unsigned int dataoff); + + /* Module (if any) which this is connected to. */ + struct module *me; +}; + +/* Protocol registration. */ +extern int ip6_conntrack_protocol_register(struct ip6_conntrack_protocol *proto); +extern void ip6_conntrack_protocol_unregister(struct ip6_conntrack_protocol *proto); + +/* Existing built-in protocols */ +extern struct ip6_conntrack_protocol ip6_conntrack_protocol_tcp; +extern struct ip6_conntrack_protocol ip6_conntrack_protocol_udp; +extern struct ip6_conntrack_protocol ip6_conntrack_protocol_icmpv6; +extern int ip6_conntrack_protocol_tcp_init(void); +#endif /*_IP6_CONNTRACK_PROTOCOL_H*/ Index: linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h 2004-08-05 16:14:54.856146626 +0200 @@ -0,0 +1,28 @@ +/* + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _IP6_CONNTRACK_REASM_H +#define _IP6_CONNTRACK_REASM_H + +#include +extern struct sk_buff * +ip6_ct_gather_frags(struct sk_buff *skb); + +extern int +ip6_ct_output_frags(struct sk_buff *skb, struct nf_info *info); + +extern int ip6_ct_kfree_frags(struct sk_buff *skb); + +extern int ip6_ct_frags_init(void); +extern void ip6_ct_frags_cleanup(void); + +#endif /* _IP6_CONNTRACK_REASM_H */ + Index: linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h 2004-08-05 16:14:54.857146530 +0200 @@ -0,0 +1,41 @@ +/* + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: include/linux/netfilter_ipv4/ip_conntrack_tcp.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _IP6_CONNTRACK_TCP_H +#define _IP6_CONNTRACK_TCP_H +/* TCP tracking. */ + +enum tcp_conntrack { + TCP_CONNTRACK_NONE, + TCP_CONNTRACK_ESTABLISHED, + TCP_CONNTRACK_SYN_SENT, + TCP_CONNTRACK_SYN_RECV, + TCP_CONNTRACK_FIN_WAIT, + TCP_CONNTRACK_TIME_WAIT, + TCP_CONNTRACK_CLOSE, + TCP_CONNTRACK_CLOSE_WAIT, + TCP_CONNTRACK_LAST_ACK, + TCP_CONNTRACK_LISTEN, + TCP_CONNTRACK_MAX +}; + +struct ip6_ct_tcp +{ + enum tcp_conntrack state; + + /* Poor man's window tracking: sequence number of valid ACK + handshake completion packet */ + u_int32_t handshake_ack; +}; + +#endif /* _IP6_CONNTRACK_TCP_H */ Index: linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h 2004-08-05 16:14:54.858146433 +0200 @@ -0,0 +1,131 @@ +/* + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: include/linux/netfilter_ipv4/ip_conntrack_tuple.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _IP6_CONNTRACK_TUPLE_H +#define _IP6_CONNTRACK_TUPLE_H + +#ifdef __KERNEL__ +#include +#include +#endif + +/* 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 ip6_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; + } icmpv6; +}; + +/* The manipulable part of the tuple. */ +struct ip6_conntrack_manip +{ + struct in6_addr ip; + union ip6_conntrack_manip_proto u; +}; + +/* This contains the information to distinguish a connection. */ +struct ip6_conntrack_tuple +{ + struct ip6_conntrack_manip src; + + /* These are the parts of the tuple which are fixed. */ + struct { + struct in6_addr 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; + } icmpv6; + } u; + + /* The protocol. */ + u_int16_t protonum; + } dst; +}; + +enum ip6_conntrack_dir +{ + IP6_CT_DIR_ORIGINAL, + IP6_CT_DIR_REPLY, + IP6_CT_DIR_MAX +}; + +#ifdef __KERNEL__ + +#define DUMP_TUPLE(tp) \ +{ \ + DEBUGP("tuple %p: %u %x:%x:%x:%x:%x:%x:%x:%x, %hu -> %x:%x:%x:%x:%x:%x:%x:%x, %hu\n", \ + (tp), (tp)->dst.protonum, \ + NIP6((tp)->src.ip), ntohs((tp)->src.u.all), \ + NIP6((tp)->dst.ip), ntohs((tp)->dst.u.all)); \ +} + +#define CTINFO2DIR(ctinfo) ((ctinfo) >= IP6_CT_IS_REPLY ? IP6_CT_DIR_REPLY : IP6_CT_DIR_ORIGINAL) + +/* If we're the first tuple, it's the original dir. */ +#define DIRECTION(h) ((enum ip6_conntrack_dir)(&(h)->ctrack->tuplehash[1] == (h))) + +/* Connections have two entries in the hash table: one for each way */ +struct ip6_conntrack_tuple_hash +{ + struct list_head list; + + struct ip6_conntrack_tuple tuple; + + /* this == &ctrack->tuplehash[DIRECTION(this)]. */ + struct ip6_conntrack *ctrack; +}; + +#endif /* __KERNEL__ */ + +extern int ip6_ct_tuple_src_equal(const struct ip6_conntrack_tuple *t1, + const struct ip6_conntrack_tuple *t2); + +extern int ip6_ct_tuple_dst_equal(const struct ip6_conntrack_tuple *t1, + const struct ip6_conntrack_tuple *t2); + +extern int ip6_ct_tuple_equal(const struct ip6_conntrack_tuple *t1, + const struct ip6_conntrack_tuple *t2); + +extern int ip6_ct_tuple_mask_cmp(const struct ip6_conntrack_tuple *t, + const struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack_tuple *mask); + +#endif /* _IP6_CONNTRACK_TUPLE_H */ Index: linux-2.6.7/include/linux/netfilter_ipv6/ip6t_state.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/include/linux/netfilter_ipv6/ip6t_state.h 2004-08-05 16:14:54.859146336 +0200 @@ -0,0 +1,24 @@ +/* + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: include/linux/netfilter_ipv4/ipt_state.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _IP6T_STATE_H +#define _IP6T_STATE_H + +#define IP6T_STATE_BIT(ctinfo) (1 << ((ctinfo)%IP6_CT_IS_REPLY+1)) +#define IP6T_STATE_INVALID (1 << 0) + +struct ip6t_state_info +{ + unsigned int statemask; +}; +#endif /*_IP6T_STATE_H*/ Index: linux-2.6.7/net/ipv6/netfilter/Kconfig =================================================================== --- linux-2.6.7.orig/net/ipv6/netfilter/Kconfig 2004-08-05 16:13:24.127944948 +0200 +++ linux-2.6.7/net/ipv6/netfilter/Kconfig 2004-08-05 16:14:54.861146142 +0200 @@ -5,6 +5,16 @@ menu "IPv6: Netfilter Configuration" depends on INET && IPV6 && NETFILTER +config IP6_NF_FTP + tristate "FTP protocol support" + depends on IP6_NF_CONNTRACK + help + Tracking FTP connections is problematic: special helpers are + required for tracking them. + + If you want to compile it as a module, say M here and read + . If unsure, say `Y'. + #tristate 'Connection tracking (required for masq/NAT)' CONFIG_IP6_NF_CONNTRACK #if [ "$CONFIG_IP6_NF_CONNTRACK" != "n" ]; then # dep_tristate ' FTP protocol support' CONFIG_IP6_NF_FTP $CONFIG_IP6_NF_CONNTRACK @@ -158,6 +168,32 @@ To compile it as a module, choose M here. If unsure, say N. +config IP6_NF_CONNTRACK + tristate "Connection tracking (EXPERIMENTAL)" + depends on EXPERIMENTAL + ---help--- + Connection tracking keeps a record of what packets have passed + through your machine, in order to figure out how they are related + into connections. + + It can also be used to enhance packet filtering + (see `Connection state match support' + below). + + If you want to compile it as a module, say M here and read + . If unsure, say `N'. + +config IP6_NF_MATCH_STATE + tristate "Connection state match support" + depends on IP6_NF_CONNTRACK && IP6_NF_IPTABLES + help + Connection state matching allows you to match packets based on their + relationship to a tracked connection (ie. previous packets). This + is a powerful tool for packet classification. + + If you want to compile it as a module, say M here and read + . If unsure, say `N'. + # dep_tristate ' Multiple port match support' CONFIG_IP6_NF_MATCH_MULTIPORT $CONFIG_IP6_NF_IPTABLES # dep_tristate ' TOS match support' CONFIG_IP6_NF_MATCH_TOS $CONFIG_IP6_NF_IPTABLES # if [ "$CONFIG_IP6_NF_CONNTRACK" != "n" ]; then Index: linux-2.6.7/net/ipv6/netfilter/Makefile =================================================================== --- linux-2.6.7.orig/net/ipv6/netfilter/Makefile 2004-08-05 16:13:24.128944851 +0200 +++ linux-2.6.7/net/ipv6/netfilter/Makefile 2004-08-05 16:14:54.862146045 +0200 @@ -2,6 +2,18 @@ # Makefile for the netfilter modules on top of IPv6. # +# objects for the conntrack +ip6_nf_conntrack-objs := ip6_conntrack_core.o ip6_conntrack_proto_generic.o ip6_conntrack_proto_tcp.o ip6_conntrack_proto_udp.o ip6_conntrack_proto_icmpv6.o ip6_conntrack_reasm.o + +# objects for the standalone - connection tracking +ip6_conntrack-objs := ip6_conntrack_standalone.o $(ip6_nf_conntrack-objs) + +# connection tracking +obj-$(CONFIG_IP6_NF_CONNTRACK) += ip6_conntrack.o + +# connection tracking helpers +obj-$(CONFIG_IP6_NF_FTP) += ip6_conntrack_ftp.o + # Link order matters here. obj-$(CONFIG_IP6_NF_IPTABLES) += ip6_tables.o obj-$(CONFIG_IP6_NF_MATCH_LIMIT) += ip6t_limit.o @@ -23,4 +35,5 @@ obj-$(CONFIG_IP6_NF_TARGET_LOG) += ip6t_LOG.o obj-$(CONFIG_IP6_NF_RAW) += ip6table_raw.o obj-$(CONFIG_IP6_NF_MATCH_HL) += ip6t_hl.o +obj-$(CONFIG_IP6_NF_MATCH_STATE) += ip6t_state.o obj-$(CONFIG_IP6_NF_TARGET_REJECT) += ip6t_REJECT.o Index: linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_core.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_core.c 2004-08-05 16:14:54.869145366 +0200 @@ -0,0 +1,1617 @@ +/* + * IPv6 Connection Tracking + * Linux INET6 implementation + * + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: net/ipv4/netfilter/ip_conntrack_core.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +/* (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 +#include +#include +#include +#include + +/* This rwlock protects the main hash table, protocol/helper/expected + registrations, conntrack timers*/ +#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip6_conntrack_lock) +#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip6_conntrack_lock) + +#include +#include +#include +#include +#include + +#define IP6_CONNTRACK_VERSION "0.1" + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +DECLARE_RWLOCK(ip6_conntrack_lock); +DECLARE_RWLOCK(ip6_conntrack_expect_tuple_lock); + +void (*ip6_conntrack_destroyed)(struct ip6_conntrack *conntrack) = NULL; +LIST_HEAD(ip6_conntrack_expect_list); +LIST_HEAD(ip6_protocol_list); +static LIST_HEAD(helpers); +unsigned int ip6_conntrack_htable_size = 0; +static int ip6_conntrack_max = 0; +static atomic_t ip6_conntrack_count = ATOMIC_INIT(0); +struct list_head *ip6_conntrack_hash; +static kmem_cache_t *ip6_conntrack_cachep; + +extern struct ip6_conntrack_protocol ip6_conntrack_generic_protocol; + +/* + * Based on ipv6_skip_exthdr() in net/ipv6/exthdr.c + * + * This function parses (probably truncated) exthdr set "hdr" + * of length "len". "nexthdrp" initially points to some place, + * where type of the first header can be found. + * + * It skips all well-known exthdrs, and returns pointer to the start + * of unparsable area i.e. the first header with unknown type. + * if success, *nexthdr is updated by type/protocol of this header. + * + * NOTES: - it may return pointer pointing beyond end of packet, + * if the last recognized header is truncated in the middle. + * - if packet is truncated, so that all parsed headers are skipped, + * it returns -1. + * - First fragment header is skipped, not-first ones + * are considered as unparsable. + * - ESP is unparsable for now and considered like + * normal payload protocol. + * - Note also special handling of AUTH header. Thanks to IPsec wizards. + */ + +static int ip6_ct_skip_exthdr(struct sk_buff *skb, int start, u8 *nexthdrp, + int len) +{ + u8 nexthdr = *nexthdrp; + + while (ipv6_ext_hdr(nexthdr)) { + struct ipv6_opt_hdr hdr; + int hdrlen; + + if (len < (int)sizeof(struct ipv6_opt_hdr)) + return -1; + if (nexthdr == NEXTHDR_NONE) + break; + if (skb_copy_bits(skb, start, &hdr, sizeof(hdr))) + BUG(); + if (nexthdr == NEXTHDR_FRAGMENT) { + struct frag_hdr fhdr; + + if (len < (int)sizeof(struct frag_hdr)) + return -1; + if (skb_copy_bits(skb, start, &fhdr, sizeof(fhdr))) + BUG(); + if (ntohs(fhdr.frag_off) & ~0x7) + return -1; + hdrlen = 8; + } else if (nexthdr == NEXTHDR_AUTH) + hdrlen = (hdr.hdrlen+2)<<2; + else + hdrlen = ipv6_optlen(&hdr); + + nexthdr = hdr.nexthdr; + len -= hdrlen; + start += hdrlen; + } + + *nexthdrp = nexthdr; + return start; +} + +int ip6_ct_tuple_src_equal(const struct ip6_conntrack_tuple *t1, + const struct ip6_conntrack_tuple *t2) +{ + if (ipv6_addr_cmp(&t1->src.ip, &t2->src.ip)) + return 0; + + if (t1->src.u.all != t2->src.u.all) + return 0; + + if (t1->dst.protonum != t2->dst.protonum) + return 0; + + return 1; + +} + +int ip6_ct_tuple_dst_equal(const struct ip6_conntrack_tuple *t1, + const struct ip6_conntrack_tuple *t2) +{ + if (ipv6_addr_cmp(&t1->dst.ip, &t2->dst.ip)) + return 0; + + if (t1->dst.u.all != t2->dst.u.all) + return 0; + + if (t1->dst.protonum != t2->dst.protonum) + return 0; + + return 1; +} + +int ip6_ct_tuple_equal(const struct ip6_conntrack_tuple *t1, + const struct ip6_conntrack_tuple *t2) +{ + return ip6_ct_tuple_src_equal(t1, t2) && ip6_ct_tuple_dst_equal(t1, t2); +} + +int ip6_ct_tuple_mask_cmp(const struct ip6_conntrack_tuple *t, + const struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack_tuple *mask) +{ + int count = 0; + + for (count = 0; count < 8; count++){ + if ((ntohs(t->src.ip.s6_addr16[count]) ^ + ntohs(tuple->src.ip.s6_addr16[count])) & + ntohs(mask->src.ip.s6_addr16[count])) + return 0; + + if ((ntohs(t->dst.ip.s6_addr16[count]) ^ + ntohs(tuple->dst.ip.s6_addr16[count])) & + ntohs(mask->dst.ip.s6_addr16[count])) + return 0; + } + + if ((t->src.u.all ^ tuple->src.u.all) & mask->src.u.all) + return 0; + + if ((t->dst.u.all ^ tuple->dst.u.all) & mask->dst.u.all) + return 0; + + if ((t->dst.protonum ^ tuple->dst.protonum) & mask->dst.protonum) + return 0; + + return 1; +} + +static inline int proto_cmpfn(const struct ip6_conntrack_protocol *curr, + u_int8_t protocol) +{ + return protocol == curr->proto; +} + +struct ip6_conntrack_protocol *__ip6_ct_find_proto(u_int8_t protocol) +{ + struct ip6_conntrack_protocol *p; + + MUST_BE_READ_LOCKED(&ip6_conntrack_lock); + p = LIST_FIND(&ip6_protocol_list, proto_cmpfn, + struct ip6_conntrack_protocol *, protocol); + if (!p) + p = &ip6_conntrack_generic_protocol; + + return p; +} + +struct ip6_conntrack_protocol *ip6_ct_find_proto(u_int8_t protocol) +{ + struct ip6_conntrack_protocol *p; + + READ_LOCK(&ip6_conntrack_lock); + p = __ip6_ct_find_proto(protocol); + READ_UNLOCK(&ip6_conntrack_lock); + return p; +} + +inline void +ip6_conntrack_put(struct ip6_conntrack *ct) +{ + IP6_NF_ASSERT(ct); + IP6_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 ip6_conntrack_hash_rnd_initted; +static unsigned int ip6_conntrack_hash_rnd; +static u_int32_t +hash_conntrack(const struct ip6_conntrack_tuple *tuple) +{ + u32 a, b, c; + + a = tuple->src.ip.s6_addr32[0]; + b = tuple->src.ip.s6_addr32[1]; + c = tuple->src.ip.s6_addr32[2]; + + a += JHASH_GOLDEN_RATIO; + b += JHASH_GOLDEN_RATIO; + c += ip6_conntrack_hash_rnd; + __jhash_mix(a, b, c); + + a += tuple->src.ip.s6_addr32[3]; + b += tuple->dst.ip.s6_addr32[0]; + c += tuple->dst.ip.s6_addr32[1]; + __jhash_mix(a, b, c); + + a += tuple->dst.ip.s6_addr32[2]; + b += tuple->dst.ip.s6_addr32[3]; + c += tuple->src.u.all | (tuple->dst.u.all << 16); + __jhash_mix(a, b, c); + + a += tuple->dst.protonum; + __jhash_mix(a, b, c); + + return c % ip6_conntrack_htable_size; +} + +int +ip6_get_tuple(const struct ipv6hdr *ipv6h, + const struct sk_buff *skb, + unsigned int dataoff, + u_int8_t protonum, + struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack_protocol *protocol) +{ + /* Should I check that this packet is'nt fragmented + like IPv4 conntrack? - kozakai */ + + ipv6_addr_copy(&tuple->src.ip, &ipv6h->saddr); + ipv6_addr_copy(&tuple->dst.ip, &ipv6h->daddr); + + tuple->dst.protonum = protonum; + + return protocol->pkt_to_tuple(skb, dataoff, tuple); +} + +static int +invert_tuple(struct ip6_conntrack_tuple *inverse, + const struct ip6_conntrack_tuple *orig, + const struct ip6_conntrack_protocol *protocol) +{ + ipv6_addr_copy(&inverse->src.ip, &orig->dst.ip); + ipv6_addr_copy(&inverse->dst.ip, &orig->src.ip); + inverse->dst.protonum = orig->dst.protonum; + + return protocol->invert_tuple(inverse, orig); +} + + +/* ip6_conntrack_expect helper functions */ + +/* Compare tuple parts depending on mask. */ +static inline int expect_cmp(const struct ip6_conntrack_expect *i, + const struct ip6_conntrack_tuple *tuple) +{ + MUST_BE_READ_LOCKED(&ip6_conntrack_expect_tuple_lock); + return ip6_ct_tuple_mask_cmp(tuple, &i->tuple, &i->mask); +} + +static void +destroy_expect(struct ip6_conntrack_expect *exp) +{ + DEBUGP("destroy_expect(%p) use=%d\n", exp, atomic_read(&exp->use)); + IP6_NF_ASSERT(atomic_read(&exp->use)); + IP6_NF_ASSERT(!timer_pending(&exp->timeout)); + + kfree(exp); +} + + +inline void ip6_conntrack_expect_put(struct ip6_conntrack_expect *exp) +{ + IP6_NF_ASSERT(exp); + + if (atomic_dec_and_test(&exp->use)) { + /* usage count dropped to zero */ + destroy_expect(exp); + } +} + +static inline struct ip6_conntrack_expect * +__ip6_ct_expect_find(const struct ip6_conntrack_tuple *tuple) +{ + MUST_BE_READ_LOCKED(&ip6_conntrack_lock); + MUST_BE_READ_LOCKED(&ip6_conntrack_expect_tuple_lock); + return LIST_FIND(&ip6_conntrack_expect_list, expect_cmp, + struct ip6_conntrack_expect *, tuple); +} + +/* Find a expectation corresponding to a tuple. */ +struct ip6_conntrack_expect * +ip6_conntrack_expect_find_get(const struct ip6_conntrack_tuple *tuple) +{ + struct ip6_conntrack_expect *exp; + + READ_LOCK(&ip6_conntrack_lock); + READ_LOCK(&ip6_conntrack_expect_tuple_lock); + exp = __ip6_ct_expect_find(tuple); + if (exp) + atomic_inc(&exp->use); + READ_UNLOCK(&ip6_conntrack_expect_tuple_lock); + READ_UNLOCK(&ip6_conntrack_lock); + + return exp; +} + +/* remove one specific expectation from all lists and drop refcount, + * does _NOT_ delete the timer. */ +static void __unexpect_related(struct ip6_conntrack_expect *expect) +{ + DEBUGP("unexpect_related(%p)\n", expect); + MUST_BE_WRITE_LOCKED(&ip6_conntrack_lock); + + /* we're not allowed to unexpect a confirmed expectation! */ + IP6_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--; + + ip6_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 ip6_conntrack_expect *expect) +{ + IP6_NF_ASSERT(expect->expectant); + IP6_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 ip6_conntrack *ct, int drop_refcount) +{ + struct list_head *exp_entry, *next; + struct ip6_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 ip6_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 */ + ip6_conntrack_put(exp->expectant); + exp->expectant = NULL; + } + continue; + } + + IP6_NF_ASSERT(list_inlist(&ip6_conntrack_expect_list, exp)); + IP6_NF_ASSERT(exp->expectant == ct); + + /* delete expectation from global and private lists */ + unexpect_related(exp); + } +} + +static void +clean_from_lists(struct ip6_conntrack *ct) +{ + unsigned int ho, hr; + + DEBUGP("clean_from_lists(%p)\n", ct); + MUST_BE_WRITE_LOCKED(&ip6_conntrack_lock); + + ho = hash_conntrack(&ct->tuplehash[IP6_CT_DIR_ORIGINAL].tuple); + hr = hash_conntrack(&ct->tuplehash[IP6_CT_DIR_REPLY].tuple); + + LIST_DELETE(&ip6_conntrack_hash[ho], + &ct->tuplehash[IP6_CT_DIR_ORIGINAL]); + LIST_DELETE(&ip6_conntrack_hash[hr], + &ct->tuplehash[IP6_CT_DIR_REPLY]); + + /* Destroy all un-established, pending expectations */ + remove_expectations(ct, 1); +} + +static void +destroy_conntrack(struct nf_conntrack *nfct) +{ + struct ip6_conntrack *ct = (struct ip6_conntrack *)nfct, *master = NULL; + struct ip6_conntrack_protocol *proto; + + DEBUGP("destroy_conntrack(%p)\n", ct); + IP6_NF_ASSERT(atomic_read(&nfct->use) == 0); + IP6_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 ip6_conntrack_lock!!! -HW */ + proto = ip6_ct_find_proto(ct->tuplehash[IP6_CT_DIR_REPLY].tuple.dst.protonum); + if (proto && proto->destroy) + proto->destroy(ct); + + if (ip6_conntrack_destroyed) + ip6_conntrack_destroyed(ct); + + WRITE_LOCK(&ip6_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(&ip6_conntrack_lock); + + if (master) + ip6_conntrack_put(master); + + DEBUGP("destroy_conntrack: returning ct=%p to slab\n", ct); + kmem_cache_free(ip6_conntrack_cachep, ct); + atomic_dec(&ip6_conntrack_count); +} + +static void death_by_timeout(unsigned long ul_conntrack) +{ + struct ip6_conntrack *ct = (void *)ul_conntrack; + + WRITE_LOCK(&ip6_conntrack_lock); + clean_from_lists(ct); + WRITE_UNLOCK(&ip6_conntrack_lock); + ip6_conntrack_put(ct); +} + +static inline int +conntrack_tuple_cmp(const struct ip6_conntrack_tuple_hash *i, + const struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack *ignored_conntrack) +{ + MUST_BE_READ_LOCKED(&ip6_conntrack_lock); + return i->ctrack != ignored_conntrack + && ip6_ct_tuple_equal(tuple, &i->tuple); +} + +static struct ip6_conntrack_tuple_hash * +__ip6_conntrack_find(const struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack *ignored_conntrack) +{ + struct ip6_conntrack_tuple_hash *h; + unsigned int hash = hash_conntrack(tuple); + + MUST_BE_READ_LOCKED(&ip6_conntrack_lock); + h = LIST_FIND(&ip6_conntrack_hash[hash], + conntrack_tuple_cmp, + struct ip6_conntrack_tuple_hash *, + tuple, ignored_conntrack); + return h; +} + +/* Find a connection corresponding to a tuple. */ +struct ip6_conntrack_tuple_hash * +ip6_conntrack_find_get(const struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack *ignored_conntrack) +{ + struct ip6_conntrack_tuple_hash *h; + + READ_LOCK(&ip6_conntrack_lock); + h = __ip6_conntrack_find(tuple, ignored_conntrack); + if (h) + atomic_inc(&h->ctrack->ct_general.use); + READ_UNLOCK(&ip6_conntrack_lock); + + return h; +} + +static inline struct ip6_conntrack * +__ip6_conntrack_get(struct nf_ct_info *nfct, enum ip6_conntrack_info *ctinfo) +{ + struct ip6_conntrack *ct + = (struct ip6_conntrack *)nfct->master; + + /* ctinfo is the index of the nfct inside the conntrack */ + *ctinfo = nfct - ct->infos; + IP6_NF_ASSERT(*ctinfo >= 0 && *ctinfo < IP6_CT_NUMBER); + return ct; +} + +/* Return conntrack and conntrack_info given skb->nfct->master */ +struct ip6_conntrack * +ip6_conntrack_get(struct sk_buff *skb, enum ip6_conntrack_info *ctinfo) +{ + if (skb->nfct) + return __ip6_conntrack_get(skb->nfct, ctinfo); + return NULL; +} + +/* Confirm a connection given skb->nfct; places it in hash table */ +int +__ip6_conntrack_confirm(struct nf_ct_info *nfct) +{ + unsigned int hash, repl_hash; + struct ip6_conntrack *ct; + enum ip6_conntrack_info ctinfo; + + ct = __ip6_conntrack_get(nfct, &ctinfo); + + /* ip6t_REJECT uses ip6_conntrack_attach to attach related + ICMP/TCP RST packets in other direction. Actual packet + which created connection will be IP6_CT_NEW or for an + expected connection, IP6_CT_RELATED. */ + if (CTINFO2DIR(ctinfo) != IP6_CT_DIR_ORIGINAL) + return NF_ACCEPT; + + hash = hash_conntrack(&ct->tuplehash[IP6_CT_DIR_ORIGINAL].tuple); + repl_hash = hash_conntrack(&ct->tuplehash[IP6_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. */ + /* IP6_NF_ASSERT(atomic_read(&ct->ct_general.use) == 1); */ + + /* No external references means noone else could have + confirmed us. */ + IP6_NF_ASSERT(!is_confirmed(ct)); + DEBUGP("Confirming conntrack %p\n", ct); + + WRITE_LOCK(&ip6_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(&ip6_conntrack_hash[hash], + conntrack_tuple_cmp, + struct ip6_conntrack_tuple_hash *, + &ct->tuplehash[IP6_CT_DIR_ORIGINAL].tuple, NULL) + && !LIST_FIND(&ip6_conntrack_hash[repl_hash], + conntrack_tuple_cmp, + struct ip6_conntrack_tuple_hash *, + &ct->tuplehash[IP6_CT_DIR_REPLY].tuple, NULL)) { + list_prepend(&ip6_conntrack_hash[hash], + &ct->tuplehash[IP6_CT_DIR_ORIGINAL]); + list_prepend(&ip6_conntrack_hash[repl_hash], + &ct->tuplehash[IP6_CT_DIR_REPLY]); + /* Timer relative to confirmation time, not original + setting time, otherwise we'd get timer wrap in + wierd delay cases. */ + ct->timeout.expires += jiffies; + add_timer(&ct->timeout); + atomic_inc(&ct->ct_general.use); + set_bit(IP6S_CONFIRMED_BIT, &ct->status); + WRITE_UNLOCK(&ip6_conntrack_lock); + return NF_ACCEPT; + } + + WRITE_UNLOCK(&ip6_conntrack_lock); + return NF_DROP; +} + +/* Is this needed ? this code is for NAT. - kozakai */ +/* Returns true if a connection correspondings to the tuple (required + for NAT). */ +int +ip6_conntrack_tuple_taken(const struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack *ignored_conntrack) +{ + struct ip6_conntrack_tuple_hash *h; + + READ_LOCK(&ip6_conntrack_lock); + h = __ip6_conntrack_find(tuple, ignored_conntrack); + READ_UNLOCK(&ip6_conntrack_lock); + + return h != NULL; +} + +/* Returns conntrack if it dealt with ICMP, and filled in skb fields */ +struct ip6_conntrack * +icmp6_error_track(struct sk_buff *skb, + unsigned int icmp6off, + enum ip6_conntrack_info *ctinfo, + unsigned int hooknum) +{ + struct ip6_conntrack_tuple intuple, origtuple; + struct ip6_conntrack_tuple_hash *h; + struct ipv6hdr *ip6h; + struct icmp6hdr hdr; + struct ipv6hdr inip6h; + unsigned int inip6off; + struct ip6_conntrack_protocol *inproto; + u_int8_t inprotonum; + unsigned int inprotoff; + + IP6_NF_ASSERT(skb->nfct == NULL); + + ip6h = skb->nh.ipv6h; + if (skb_copy_bits(skb, icmp6off, &hdr, sizeof(hdr)) != 0) { + DEBUGP("icmp_error_track: Can't copy ICMPv6 hdr.\n"); + return NULL; + } + + if (hdr.icmp6_type >= 128) + return NULL; + + /* + * Should I ignore invalid ICMPv6 error here ? + * ex) ICMPv6 error in ICMPv6 error, Fragmented packet, and so on. + * - kozakai + */ + + /* Why not check checksum in IPv4 conntrack ? - kozakai */ + /* Ignore it if the checksum's bogus. */ + + if (csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, skb->len - icmp6off, + IPPROTO_ICMPV6, + skb_checksum(skb, icmp6off, + skb->len - icmp6off, 0))) { + DEBUGP("ICMPv6 checksum failed\n"); + return NULL; + } + + inip6off = icmp6off + sizeof(hdr); + if (skb_copy_bits(skb, inip6off, &inip6h, sizeof(inip6h)) != 0) { + DEBUGP("Can't copy inner IPv6 hdr.\n"); + return NULL; + } + + inprotonum = inip6h.nexthdr; + inprotoff = ip6_ct_skip_exthdr(skb, inip6off + sizeof(inip6h), + &inprotonum, + skb->len - inip6off - sizeof(inip6h)); + + if (inprotoff < 0 || inprotoff > skb->len + || inprotonum == NEXTHDR_FRAGMENT) { + DEBUGP("icmp6_error: Can't find protocol header in ICMPv6 payload.\n"); + return NULL; + } + + inproto = ip6_ct_find_proto(inprotonum); + + /* Are they talking about one of our connections? */ + if (!ip6_get_tuple(&inip6h, skb, inprotoff, inprotonum, + &origtuple, inproto)) { + DEBUGP("icmp6_error: ! get_tuple p=%u\n", inprotonum); + return NULL; + } + + /* Ordinarily, we'd expect the inverted tupleproto, but it's + been preserved inside the ICMP. */ + if (!invert_tuple(&intuple, &origtuple, inproto)) { + DEBUGP("icmp6_error_track: Can't invert tuple\n"); + return NULL; + } + + *ctinfo = IP6_CT_RELATED; + + h = ip6_conntrack_find_get(&intuple, NULL); + if (!h) { + DEBUGP("icmp6_error_track: no match\n"); + return NULL; + } else { + if (DIRECTION(h) == IP6_CT_DIR_REPLY) + *ctinfo += IP6_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 ip6_conntrack_tuple_hash *i) +{ + return !(test_bit(IP6S_ASSURED_BIT, &i->ctrack->status)); +} + +static int early_drop(struct list_head *chain) +{ + /* Traverse backwards: gives us oldest, which is roughly LRU */ + struct ip6_conntrack_tuple_hash *h; + int dropped = 0; + + READ_LOCK(&ip6_conntrack_lock); + h = LIST_FIND_B(chain, unreplied, struct ip6_conntrack_tuple_hash *); + if (h) + atomic_inc(&h->ctrack->ct_general.use); + READ_UNLOCK(&ip6_conntrack_lock); + + if (!h) + return dropped; + + if (del_timer(&h->ctrack->timeout)) { + death_by_timeout((unsigned long)h->ctrack); + dropped = 1; + } + ip6_conntrack_put(h->ctrack); + return dropped; +} + +static inline int helper_cmp(const struct ip6_conntrack_helper *i, + const struct ip6_conntrack_tuple *rtuple) +{ + return ip6_ct_tuple_mask_cmp(rtuple, &i->tuple, &i->mask); +} + +struct ip6_conntrack_helper * +ip6_ct_find_helper(const struct ip6_conntrack_tuple *tuple){ + + MUST_BE_READ_LOCKED(&ip6_conntrack_lock); + return LIST_FIND(&helpers, helper_cmp, + struct ip6_conntrack_helper *, + tuple); +} + +/* Allocate a new conntrack: we return -ENOMEM if classification + failed due to stress. Otherwise it really is unclassifiable. */ +static struct ip6_conntrack_tuple_hash * +init_conntrack(const struct ip6_conntrack_tuple *tuple, + struct ip6_conntrack_protocol *protocol, + struct sk_buff *skb, + unsigned int protoff) +{ + struct ip6_conntrack *conntrack; + struct ip6_conntrack_tuple repl_tuple; + size_t hash; + struct ip6_conntrack_expect *expected; + int i; + static unsigned int drop_next = 0; + + if (!ip6_conntrack_hash_rnd_initted) { + get_random_bytes(&ip6_conntrack_hash_rnd, 4); + ip6_conntrack_hash_rnd_initted = 1; + } + + hash = hash_conntrack(tuple); + + if (ip6_conntrack_max && + atomic_read(&ip6_conntrack_count) >= ip6_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++)%ip6_conntrack_htable_size; + + if (!early_drop(&ip6_conntrack_hash[next]) + && !early_drop(&ip6_conntrack_hash[hash])) { + if (net_ratelimit()) + printk(KERN_WARNING + "ip6_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(ip6_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[IP6_CT_DIR_ORIGINAL].tuple = *tuple; + conntrack->tuplehash[IP6_CT_DIR_ORIGINAL].ctrack = conntrack; + conntrack->tuplehash[IP6_CT_DIR_REPLY].tuple = repl_tuple; + conntrack->tuplehash[IP6_CT_DIR_REPLY].ctrack = conntrack; + for (i=0; i < IP6_CT_NUMBER; i++) + conntrack->infos[i].master = &conntrack->ct_general; + + if (!protocol->new(conntrack, skb, protoff)) { + kmem_cache_free(ip6_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(&ip6_conntrack_lock); + /* Need finding and deleting of expected ONLY if we win race */ + READ_LOCK(&ip6_conntrack_expect_tuple_lock); + expected = LIST_FIND(&ip6_conntrack_expect_list, expect_cmp, + struct ip6_conntrack_expect *, tuple); + READ_UNLOCK(&ip6_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 = ip6_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... */ + IP6_NF_ASSERT(master_ct6(conntrack)); + __set_bit(IP6S_EXPECTED_BIT, &conntrack->status); + conntrack->master = expected; + expected->sibling = conntrack; + LIST_DELETE(&ip6_conntrack_expect_list, expected); + expected->expectant->expecting--; + nf_conntrack_get(&master_ct6(conntrack)->infos[0]); + } + atomic_inc(&ip6_conntrack_count); + WRITE_UNLOCK(&ip6_conntrack_lock); + + if (expected && expected->expectfn) + expected->expectfn(conntrack); + return &conntrack->tuplehash[IP6_CT_DIR_ORIGINAL]; +} + +/* On success, returns conntrack ptr, sets skb->nfct and ctinfo */ +static inline struct ip6_conntrack * +resolve_normal_ct(struct sk_buff *skb, + unsigned int protoff, + u_int16_t protonum, + struct ip6_conntrack_protocol *proto, + int *set_reply, + unsigned int hooknum, + enum ip6_conntrack_info *ctinfo) +{ + struct ip6_conntrack_tuple tuple; + struct ip6_conntrack_tuple_hash *h; + + if (!ip6_get_tuple(skb->nh.ipv6h, skb, protoff, protonum, &tuple, proto)) + return NULL; + + /* look for tuple match */ + h = ip6_conntrack_find_get(&tuple, NULL); + if (!h) { + h = init_conntrack(&tuple, proto, skb, protoff); + if (!h) + return NULL; + if (IS_ERR(h)) + return (void *)h; + } + + /* It exists; we have (non-exclusive) reference. */ + if (DIRECTION(h) == IP6_CT_DIR_REPLY) { + *ctinfo = IP6_CT_ESTABLISHED + IP6_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(IP6S_SEEN_REPLY_BIT, &h->ctrack->status)) { + DEBUGP("ip6_conntrack_in: normal packet for %p\n", + h->ctrack); + *ctinfo = IP6_CT_ESTABLISHED; + } else if (test_bit(IP6S_EXPECTED_BIT, &h->ctrack->status)) { + DEBUGP("ip6_conntrack_in: related packet for %p\n", + h->ctrack); + *ctinfo = IP6_CT_RELATED; + } else { + DEBUGP("ip6_conntrack_in: new packet for %p\n", + h->ctrack); + *ctinfo = IP6_CT_NEW; + } + *set_reply = 0; + } + skb->nfct = &h->ctrack->infos[*ctinfo]; + return h->ctrack; +} + +/* Netfilter hook itself. */ +unsigned int ip6_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 ip6_conntrack *ct; + enum ip6_conntrack_info ctinfo; + struct ip6_conntrack_protocol *proto; + int set_reply; + int ret; + u_int8_t protonum; + int len; + int daddr_type; + int protoff, extoff; + + /* FIXME: Do this right please. --RR */ + (*pskb)->nfcache |= NFC_UNKNOWN; + + /* Ignore multicast - kozakai */ + daddr_type = ipv6_addr_type(&(*pskb)->nh.ipv6h->daddr); + if (daddr_type & IPV6_ADDR_MULTICAST) + return NF_ACCEPT; + + /* Previously seen (loopback)? Ignore. Do this before + fragment check. */ + if ((*pskb)->nfct) + return NF_ACCEPT; + + extoff = (u8*)((*pskb)->nh.ipv6h+1) - (*pskb)->data; + len = (*pskb)->len - extoff; + + /* Verify that a protocol is present and get the protocol handler + we need */ + protonum = (*pskb)->nh.ipv6h->nexthdr; + protoff = ip6_ct_skip_exthdr(*pskb, extoff, &protonum, len); + + /* + * Notice! (protoff == (*pskb)->len) mean that this packet doesn't + * have no data except of IPv6 & ext headers. but tracked anyway. + * - kozakai + */ + if (protoff < 0 || protoff > (*pskb)->len + || protonum == NEXTHDR_FRAGMENT) { + DEBUGP("ip6_conntrack_core: can't find proto in pkt\n"); + return NF_ACCEPT; + } + + /* It may be an icmp error... */ + if (protonum == IPPROTO_ICMPV6 + && icmp6_error_track(*pskb, protoff, &ctinfo, hooknum)) + return NF_ACCEPT; + + proto = ip6_ct_find_proto(protonum); + + if (!(ct = resolve_normal_ct(*pskb, protoff, protonum, 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; + + IP6_NF_ASSERT((*pskb)->nfct); + + ret = proto->packet(ct, *pskb, protoff, 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, protoff, ct, ctinfo); + if (ret == -1) { + /* Invalid */ + nf_conntrack_put((*pskb)->nfct); + (*pskb)->nfct = NULL; + return NF_ACCEPT; + } + } + if (set_reply) + set_bit(IP6S_SEEN_REPLY_BIT, &ct->status); + + return ret; +} + +int ip6_invert_tuplepr(struct ip6_conntrack_tuple *inverse, + const struct ip6_conntrack_tuple *orig) +{ + return invert_tuple(inverse, orig, ip6_ct_find_proto(orig->dst.protonum)); +} + +static inline int resent_expect(const struct ip6_conntrack_expect *i, + const struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack_tuple *mask) +{ + DEBUGP("resent_expect\n"); + DEBUGP(" tuple: "); DUMP_TUPLE(&i->tuple); + DEBUGP("test tuple: "); DUMP_TUPLE(tuple); + return (ip6_ct_tuple_equal(&i->tuple, tuple) + && ip6_ct_tuple_equal(&i->mask, mask)); +} + +static struct in6_addr * +or_addr6_bits(struct in6_addr *result, const struct in6_addr *one, + const struct in6_addr *two) +{ + + int count = 0; + + for (count = 0; count < 8; count++) + result->s6_addr16[count] = ntohs(one->s6_addr16[count]) + & ntohs(two->s6_addr16[count]); + + return result; +} + +/* Would two expected things clash? */ +static inline int expect_clash(const struct ip6_conntrack_expect *i, + const struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack_tuple *mask) +{ + /* Part covered by intersection of masks must be unequal, + otherwise they clash */ + struct ip6_conntrack_tuple intersect_mask; + + intersect_mask.src.u.all = i->mask.src.u.all & mask->src.u.all; + intersect_mask.dst.u.all = i->mask.dst.u.all & mask->dst.u.all; + intersect_mask.dst.protonum = i->mask.dst.protonum + & mask->dst.protonum; + + or_addr6_bits(&intersect_mask.src.ip, &i->mask.src.ip, + &mask->src.ip); + or_addr6_bits(&intersect_mask.dst.ip, &i->mask.dst.ip, + &mask->dst.ip); + + return ip6_ct_tuple_mask_cmp(&i->tuple, tuple, &intersect_mask); +} + +inline void ip6_conntrack_unexpect_related(struct ip6_conntrack_expect *expect) +{ + WRITE_LOCK(&ip6_conntrack_lock); + unexpect_related(expect); + WRITE_UNLOCK(&ip6_conntrack_lock); +} + +static void expectation_timed_out(unsigned long ul_expect) +{ + struct ip6_conntrack_expect *expect = (void *) ul_expect; + + DEBUGP("expectation %p timed out\n", expect); + WRITE_LOCK(&ip6_conntrack_lock); + __unexpect_related(expect); + WRITE_UNLOCK(&ip6_conntrack_lock); +} + +/* Add a related connection. */ +int ip6_conntrack_expect_related(struct ip6_conntrack *related_to, + struct ip6_conntrack_expect *expect) +{ + struct ip6_conntrack_expect *old, *new; + int ret = 0; + + WRITE_LOCK(&ip6_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("ip6_conntrack_expect_related %p\n", related_to); + DEBUGP("tuple: "); DUMP_TUPLE(&expect->tuple); + DEBUGP("mask: "); DUMP_TUPLE(&expect->mask); + + old = LIST_FIND(&ip6_conntrack_expect_list, resent_expect, + struct ip6_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(&ip6_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 & + IP6_CT_HELPER_F_REUSE_EXPECT)) { + WRITE_UNLOCK(&ip6_conntrack_lock); + if (net_ratelimit()) + printk(KERN_WARNING + "ip6_conntrack: max number of expected " + "connections %i of %s for " + "%x:%x:%x:%x:%x:%x:%x:%x->%x:%x:%x:%x:%x:%x:%x:%x\n", + related_to->helper->max_expected, + related_to->helper->name, + NIP6(related_to->tuplehash[IP6_CT_DIR_ORIGINAL].tuple.src.ip), + NIP6(related_to->tuplehash[IP6_CT_DIR_ORIGINAL].tuple.dst.ip)); + return -EPERM; + } + DEBUGP("ip6_conntrack: max number of expected " + "connections %i of %s reached for " + "%x:%x:%x:%x:%x:%x:%x:%x->%x:%x:%x:%x:%x:%x:%x:%x, reusing\n", + related_to->helper->max_expected, + related_to->helper->name, + NIP6(related_to->tuplehash[IP6_CT_DIR_ORIGINAL].tuple.src.ip), + NIP6(related_to->tuplehash[IP6_CT_DIR_ORIGINAL].tuple.dst.ip)); + + /* choose the the oldest expectation to evict */ + list_for_each(cur_item, &related_to->sibling_list) { + struct ip6_conntrack_expect *cur; + + cur = list_entry(cur_item, + struct ip6_conntrack_expect, + expected_list); + if (cur->sibling == NULL) { + old = cur; + break; + } + } + + /* (!old) cannot happen, since related_to->expecting is the + * number of unconfirmed expects */ + IP6_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(&ip6_conntrack_expect_list, expect_clash, + struct ip6_conntrack_expect *, &expect->tuple, + &expect->mask)) { + WRITE_UNLOCK(&ip6_conntrack_lock); + DEBUGP("expect_related: busy!\n"); + return -EBUSY; + } + + new = (struct ip6_conntrack_expect *) + kmalloc(sizeof(struct ip6_conntrack_expect), GFP_ATOMIC); + if (!new) { + WRITE_UNLOCK(&ip6_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(&ip6_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(&ip6_conntrack_lock); + + return ret; +} + + +/* Is this code needed ? this is for NAT. - kozakai */ +/* Alter reply tuple (maybe alter helper). If it's already taken, + return 0 and don't do alteration. */ +int ip6_conntrack_alter_reply(struct ip6_conntrack *conntrack, + const struct ip6_conntrack_tuple *newreply) +{ + WRITE_LOCK(&ip6_conntrack_lock); + if (__ip6_conntrack_find(newreply, conntrack)) { + WRITE_UNLOCK(&ip6_conntrack_lock); + return 0; + } + /* Should be unconfirmed, so not in hash table yet */ + IP6_NF_ASSERT(!is_confirmed(conntrack)); + + DEBUGP("Altering reply tuple of %p to ", conntrack); + DUMP_TUPLE(newreply); + + conntrack->tuplehash[IP6_CT_DIR_REPLY].tuple = *newreply; + if (!conntrack->master) + conntrack->helper = ip6_ct_find_helper(newreply); + WRITE_UNLOCK(&ip6_conntrack_lock); + + return 1; +} + +int ip6_conntrack_helper_register(struct ip6_conntrack_helper *me) +{ + WRITE_LOCK(&ip6_conntrack_lock); + list_prepend(&helpers, me); + WRITE_UNLOCK(&ip6_conntrack_lock); + + return 0; +} + +static inline int unhelp(struct ip6_conntrack_tuple_hash *i, + const struct ip6_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 ip6_conntrack_helper_unregister(struct ip6_conntrack_helper *me) +{ + unsigned int i; + + /* Need write lock here, to delete helper. */ + WRITE_LOCK(&ip6_conntrack_lock); + LIST_DELETE(&helpers, me); + + /* Get rid of expecteds, set helpers to NULL. */ + for (i = 0; i < ip6_conntrack_htable_size; i++) + LIST_FIND_W(&ip6_conntrack_hash[i], unhelp, + struct ip6_conntrack_tuple_hash *, me); + WRITE_UNLOCK(&ip6_conntrack_lock); + + /* Someone could be still looking at the helper in a bh. */ + synchronize_net(); +} + +/* Refresh conntrack for this many jiffies. */ +void ip6_ct_refresh(struct ip6_conntrack *ct, unsigned long extra_jiffies) +{ + IP6_NF_ASSERT(ct->timeout.data == (unsigned long)ct); + + WRITE_LOCK(&ip6_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(&ip6_conntrack_lock); +} + +/* Used by ip6t_REJECT. */ +static void ip6_conntrack_attach(struct sk_buff *nskb, struct nf_ct_info *nfct) +{ + struct ip6_conntrack *ct; + enum ip6_conntrack_info ctinfo; + + ct = __ip6_conntrack_get(nfct, &ctinfo); + + /* This ICMP is in reverse direction to the packet which + caused it */ + if (CTINFO2DIR(ctinfo) == IP6_CT_DIR_ORIGINAL) + ctinfo = IP6_CT_RELATED + IP6_CT_IS_REPLY; + else + ctinfo = IP6_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 ip6_conntrack_tuple_hash *i, + int (*kill)(const struct ip6_conntrack *i, void *data), + void *data) +{ + return kill(i->ctrack, data); +} + +/* Bring out ya dead! */ +static struct ip6_conntrack_tuple_hash * +get_next_corpse(int (*kill)(const struct ip6_conntrack *i, void *data), + void *data) +{ + struct ip6_conntrack_tuple_hash *h = NULL; + unsigned int i; + + READ_LOCK(&ip6_conntrack_lock); + for (i = 0; !h && i < ip6_conntrack_htable_size; i++) { + h = LIST_FIND(&ip6_conntrack_hash[i], do_kill, + struct ip6_conntrack_tuple_hash *, kill, data); + } + if (h) + atomic_inc(&h->ctrack->ct_general.use); + READ_UNLOCK(&ip6_conntrack_lock); + + return h; +} + +void +ip6_ct_selective_cleanup(int (*kill)(const struct ip6_conntrack *i, void *data), + void *data) +{ + struct ip6_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. */ + + ip6_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 ipv6_pinfo *np = inet6_sk(sk); + struct ip6_conntrack_tuple_hash *h; + struct ip6_conntrack_tuple tuple; + + memset(&tuple, 0, sizeof(tuple)); + ipv6_addr_copy(&tuple.src.ip, &np->rcv_saddr); + ipv6_addr_copy(&tuple.dst.ip, &np->daddr); + tuple.src.u.tcp.port = inet->sport; + 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("IPV6_NF_ORIGINAL_DST: Not a TCP socket\n"); + return -ENOPROTOOPT; + } + + if ((unsigned int) *len < sizeof(struct sockaddr_in)) { + DEBUGP("IPV6_NF_ORIGINAL_DST: len %u not %u\n", + *len, sizeof(struct sockaddr_in)); + return -EINVAL; + } + + h = ip6_conntrack_find_get(&tuple, NULL); + if (h) { + struct sockaddr_in6 sin; + + sin.sin6_family = AF_INET6; + sin.sin6_port = h->ctrack->tuplehash[IP6_CT_DIR_ORIGINAL] + .tuple.dst.u.tcp.port; + ipv6_addr_copy(&sin.sin6_addr, + &h->ctrack->tuplehash[IP6_CT_DIR_ORIGINAL] + .tuple.dst.ip); + + DEBUGP("IPV6_NF_ORIGINAL_DST: %x:%x:%x:%x:%x:%x:%x:%x %u\n", + NIP6(sin.sin6_addr), ntohs(sin.sin6_port)); + ip6_conntrack_put(h->ctrack); + if (copy_to_user(user, &sin, sizeof(sin)) != 0) + return -EFAULT; + else + return 0; + } + DEBUGP("IPV6_NF_ORIGINAL_DST: Can't find %x:%x:%x:%x:%x:%x:%x:%x/%u-%x:%x:%x:%x:%x:%x:%x:%x/%u.\n", + NIP6(tuple.src.ip), ntohs(tuple.src.u.tcp.port), + NIP6(tuple.dst.ip), ntohs(tuple.dst.u.tcp.port)); + return -ENOENT; +} + +static struct nf_sockopt_ops so_getorigdst = { + .pf = PF_INET6, + .get_optmin = IPV6_NF_ORIGINAL_DST, + .get_optmax = IPV6_NF_ORIGINAL_DST+1, + .get = &getorigdst, +}; + +#define NET_IP6_CONNTRACK_MAX 2089 +#define NET_IP6_CONNTRACK_MAX_NAME "ip6_conntrack_max" + +#ifdef CONFIG_SYSCTL +static struct ctl_table_header *ip6_conntrack_sysctl_header; + +static ctl_table ip6_conntrack_table[] = { + { + .ctl_name = NET_IP6_CONNTRACK_MAX, + .procname = NET_IP6_CONNTRACK_MAX_NAME, + .data = &ip6_conntrack_max, + .maxlen = sizeof(ip6_conntrack_max), + .mode = 0644, + .proc_handler = proc_dointvec + }, + { .ctl_name = 0 } +}; + +static ctl_table ip6_conntrack_dir_table[] = { + { + .ctl_name = NET_IPV6, + .procname = "ipv6", NULL, + .mode = 0555, + .child = ip6_conntrack_table + }, + { .ctl_name = 0 } +}; + +static ctl_table ip6_conntrack_root_table[] = { + { + .ctl_name = CTL_NET, + .procname = "net", + .mode = 0555, + .child = ip6_conntrack_dir_table + }, + { .ctl_name = 0 } +}; +#endif /*CONFIG_SYSCTL*/ + +static int kill_all(const struct ip6_conntrack *i, void *data) +{ + return 1; +} + +/* Mishearing the voices in his head, our hero wonders how he's + supposed to kill the mall. */ +void ip6_conntrack_cleanup(void) +{ +#ifdef CONFIG_SYSCTL + unregister_sysctl_table(ip6_conntrack_sysctl_header); +#endif + ip6_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: + ip6_ct_selective_cleanup(kill_all, NULL); + if (atomic_read(&ip6_conntrack_count) != 0) { + schedule(); + goto i_see_dead_people; + } + + kmem_cache_destroy(ip6_conntrack_cachep); + vfree(ip6_conntrack_hash); + nf_unregister_sockopt(&so_getorigdst); +} + +static int hashsize = 0; +MODULE_PARM(hashsize, "i"); + +int __init ip6_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) { + ip6_conntrack_htable_size = hashsize; + } else { + ip6_conntrack_htable_size + = (((num_physpages << PAGE_SHIFT) / 16384) + / sizeof(struct list_head)); + if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE)) + ip6_conntrack_htable_size = 8192; + if (ip6_conntrack_htable_size < 16) + ip6_conntrack_htable_size = 16; + } + ip6_conntrack_max = 8 * ip6_conntrack_htable_size; + + printk("ip6_conntrack version %s (%u buckets, %d max)" + " - %Zd bytes per conntrack\n", IP6_CONNTRACK_VERSION, + ip6_conntrack_htable_size, ip6_conntrack_max, + sizeof(struct ip6_conntrack)); + + ret = nf_register_sockopt(&so_getorigdst); + if (ret != 0) { + printk(KERN_ERR "Unable to register netfilter socket option\n"); + return ret; + } + + ip6_conntrack_hash = vmalloc(sizeof(struct list_head) + * ip6_conntrack_htable_size); + if (!ip6_conntrack_hash) { + printk(KERN_ERR "Unable to create ip6_conntrack_hash\n"); + goto err_unreg_sockopt; + } + + ip6_conntrack_cachep = kmem_cache_create("ip6_conntrack", + sizeof(struct ip6_conntrack), 0, + SLAB_HWCACHE_ALIGN, NULL, NULL); + if (!ip6_conntrack_cachep) { + printk(KERN_ERR "Unable to create ip6_conntrack slab cache\n"); + goto err_free_hash; + } + /* Don't NEED lock here, but good form anyway. */ + WRITE_LOCK(&ip6_conntrack_lock); + /* Sew in builtin protocols. */ + list_append(&ip6_protocol_list, &ip6_conntrack_protocol_tcp); + list_append(&ip6_protocol_list, &ip6_conntrack_protocol_udp); + list_append(&ip6_protocol_list, &ip6_conntrack_protocol_icmpv6); + WRITE_UNLOCK(&ip6_conntrack_lock); + + for (i = 0; i < ip6_conntrack_htable_size; i++) + INIT_LIST_HEAD(&ip6_conntrack_hash[i]); + +/* This is fucking braindead. There is NO WAY of doing this without + the CONFIG_SYSCTL unless you don't want to detect errors. + Grrr... --RR */ +#ifdef CONFIG_SYSCTL + ip6_conntrack_sysctl_header + = register_sysctl_table(ip6_conntrack_root_table, 0); + if (ip6_conntrack_sysctl_header == NULL) { + goto err_free_ct_cachep; + } +#endif /*CONFIG_SYSCTL*/ + + /* For use by ip6t_REJECT */ + ip6_ct_attach = ip6_conntrack_attach; + return ret; + +#ifdef CONFIG_SYSCTL +err_free_ct_cachep: + kmem_cache_destroy(ip6_conntrack_cachep); +#endif /*CONFIG_SYSCTL*/ +err_free_hash: + vfree(ip6_conntrack_hash); +err_unreg_sockopt: + nf_unregister_sockopt(&so_getorigdst); + + return -ENOMEM; +} Index: linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_ftp.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_ftp.c 2004-08-05 16:14:54.871145172 +0200 @@ -0,0 +1,554 @@ +/* + * FTP extension for IPv6 connection tracking. + * Linux INET6 implementation + * + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: net/ipv4/netfilter/ip_conntrack_ftp.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +/* FTP extension for IP6 connection tracking. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* This is slow, but it's simple. --RR */ +static char ftp_buffer[65536]; + +DECLARE_LOCK(ip6_ftp_lock); +struct module *ip6_conntrack_ftp = THIS_MODULE; + +#define MAX_PORTS 8 +static int ports[MAX_PORTS]; +static int ports_c = 0; +#ifdef MODULE_PARM +MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i"); +#endif + +static int loose = 0; +MODULE_PARM(loose, "i"); + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +struct cmd_info { + struct in6_addr ip; + u_int16_t port; +}; + +static int try_eprt(const char *, size_t, struct cmd_info *, char); +static int try_espv_response(const char *, size_t, struct cmd_info *, char); + +static struct ftp_search { + enum ip6_conntrack_dir dir; + const char *pattern; + size_t plen; + char skip; + char term; + enum ip6_ct_ftp_type ftptype; + int (*getnum)(const char *, size_t, struct cmd_info *, char); +} search[] = { + { + IP6_CT_DIR_ORIGINAL, + "EPRT", sizeof("EPRT") - 1, ' ', '\r', + IP6_CT_FTP_EPRT, + try_eprt, + }, + { + IP6_CT_DIR_REPLY, + "229 ", sizeof("229 ") - 1, '(', ')', + IP6_CT_FTP_EPSV, + try_espv_response, + }, +}; + +/* This code is based on inet_pton() in glibc-2.2.4 */ + +#define NS_IN6ADDRSZ 16 +#define NS_INADDRSZ 4 +#define NS_INT16SZ 2 + +/* + * return the length of string of address parse untill error, + * dlen or reaching terminal char - kozakai + */ +static int +get_ipv6_addr(const char *src, u_int8_t *dst, size_t dlen, u_int8_t term) +{ + static const char xdigits[] = "0123456789abcdef"; + u_int8_t tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp; + const char *curtok; + int ch, saw_xdigit; + u_int32_t val; + size_t clen = 0; + + tp = memset(tmp, '\0', NS_IN6ADDRSZ); + endp = tp + NS_IN6ADDRSZ; + colonp = NULL; + + /* Leading :: requires some special handling. */ + if (*src == ':'){ + if (*++src != ':') + return (0); + clen++; + } + + curtok = src; + saw_xdigit = 0; + val = 0; + while ((clen < dlen) && (*src != term)) { + const char *pch; + + ch = tolower (*src++); + clen++; + + pch = strchr(xdigits, ch); + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return (0); + + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) + return (0); + colonp = tp; + continue; + } else if (*src == term) { + return (0); + } + if (tp + NS_INT16SZ > endp) + return (0); + *tp++ = (u_int8_t) (val >> 8) & 0xff; + *tp++ = (u_int8_t) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + return (0); + } + if (saw_xdigit) { + if (tp + NS_INT16SZ > endp) + return (0); + + *tp++ = (u_int8_t) (val >> 8) & 0xff; + *tp++ = (u_int8_t) val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + int i; + + if (tp == endp) + return (0); + + for (i = 1; i <= n; i++) { + endp[- i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp || (*src != term)) + return (0); + + memcpy(dst, tmp, NS_IN6ADDRSZ); + return clen; +} + +/* return length of port if succeed. */ +static int get_port(const char *data, u_int16_t *port, size_t dlen, char term) +{ + int i; + u_int16_t tmp_port = 0; + + for(i = 0; i < dlen; i++) { + /* Finished? */ + if(data[i] == term){ + *port = htons(tmp_port); + return i; + } + + if(data[i] < '0' || data[i] > '9') + return 0; + + tmp_port = tmp_port*10 + (data[i] - '0'); + } + return 0; +} + +/* Returns 0, or length of numbers: |1|132.235.1.2|6275| */ +static int try_eprt(const char *data, size_t dlen, struct cmd_info *cmd, + char term) +{ + char delim; + int len; + int addr_len; + + /* First character is delimiter, then "1" for IPv4, then + delimiter again. */ + + if (dlen <= 3) + return 0; + + delim = data[0]; + + if (isdigit(delim) || delim < 33 || delim > 126 + || data[1] != '2' || data[2] != delim){ + return 0; + } + DEBUGP("Got %c2%c\n", delim, delim); + + len = 3; + + /* Now we have IP address. */ + addr_len = get_ipv6_addr(&data[len], cmd->ip.s6_addr, + dlen - len, delim); + + if (addr_len == 0) + return 0; + + len += addr_len + 1; + + DEBUGP("Got IPv6 address!\n"); + + addr_len = get_port(&data[len], &cmd->port, dlen, delim); + + if(addr_len == 0) + return 0; + + len += addr_len + 1; + + return len; +} + +/* Returns 0, or length of numbers: |||6446| */ +static int try_espv_response(const char *data, size_t dlen, + struct cmd_info *cmd, char term) +{ + char delim; + size_t len; + + /* Three delimiters. */ + if (dlen <= 3) + return 0; + + delim = data[0]; + + if (isdigit(delim) || delim < 33 || delim > 126 + || data[1] != delim || data[2] != delim) + return 0; + + len = get_port(&data[3], &cmd->port, dlen, delim); + + if(len == 0) + return 0; + + return 3 + len + 1; +} + +/* Return 1 for match, 0 for accept, -1 for partial. */ +static int find_pattern(const char *data, size_t dlen, + const char *pattern, size_t plen, + char skip, char term, + unsigned int *numoff, + unsigned int *numlen, + struct cmd_info *cmd, + int (*getnum)(const char *, size_t, struct cmd_info *, + char)) +{ + size_t i; + + DEBUGP("find_pattern `%s': dlen = %u\n", pattern, dlen); + if (dlen == 0) + return 0; + + if (dlen <= plen) { + /* Short packet: try for partial? */ + if (strnicmp(data, pattern, dlen) == 0) + return -1; + else return 0; + } + + if (strnicmp(data, pattern, plen) != 0) { +#if 0 + size_t i; + + DEBUGP("ftp: string mismatch\n"); + for (i = 0; i < plen; i++) { + DEBUGP("ftp:char %u `%c'(%u) vs `%c'(%u)\n", + i, data[i], data[i], + pattern[i], pattern[i]); + } +#endif + return 0; + } + + DEBUGP("Pattern matches!\n"); + /* Now we've found the constant string, try to skip + to the 'skip' character */ + for (i = plen; data[i] != skip; i++) + if (i == dlen - 1) return -1; + + /* Skip over the last character */ + i++; + + DEBUGP("Skipped up to `%c'!\n", skip); + + *numoff = i; + *numlen = getnum(data + i, dlen - i, cmd, term); + if (!*numlen) + return -1; + + DEBUGP("Match succeeded!\n"); + return 1; +} + +static int help(const struct sk_buff *skb, + unsigned int protoff, + struct ip6_conntrack *ct, + enum ip6_conntrack_info ctinfo) +{ + unsigned int dataoff, datalen; + struct tcphdr tcph; + u_int32_t old_seq_aft_nl; + int old_seq_aft_nl_set, ret; + int dir = CTINFO2DIR(ctinfo); + unsigned int matchlen, matchoff; + struct ip6_ct_ftp_master *ct_ftp_info = &ct->help.ct_ftp_info; + struct ip6_conntrack_expect expect, *exp = &expect; + struct ip6_ct_ftp_expect *exp_ftp_info = &exp->help.exp_ftp_info; + + unsigned int i; + int found = 0; + + struct ipv6hdr *ipv6h = skb->nh.ipv6h; + struct ip6_conntrack_tuple *t = &exp->tuple, *mask = &exp->mask; + struct cmd_info cmd; + unsigned int csum; + + /* Until there's been traffic both ways, don't look in packets. */ + if (ctinfo != IP6_CT_ESTABLISHED + && ctinfo != IP6_CT_ESTABLISHED+IP6_CT_IS_REPLY) { + DEBUGP("ftp: Conntrackinfo = %u\n", ctinfo); + return NF_ACCEPT; + } + + if (skb_copy_bits(skb, protoff, &tcph, sizeof(tcph)) != 0) + return NF_ACCEPT; + + dataoff = protoff + tcph.doff * 4; + /* No data? */ + if (dataoff >= skb->len) { + DEBUGP("ftp: dataoff(%u) >= skblen(%u)\n", dataoff, skb->len); + return NF_ACCEPT; + } + datalen = skb->len - dataoff; + + LOCK_BH(&ip6_ftp_lock); + + csum = skb_copy_and_csum_bits(skb, dataoff, ftp_buffer, + skb->len - dataoff, 0); + csum = skb_checksum(skb, protoff, tcph.doff * 4, csum); + + /* Checksum invalid? Ignore. */ + /* FIXME: Source route IP option packets --RR */ + if (csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, skb->len - protoff, + IPPROTO_TCP, csum)) { + DEBUGP("ftp_help: bad csum: %p %u\n" + "%x:%x:%x:%x:%x:%x:%x:%x -> %x:%x:%x:%x:%x:%x:%x:%x\n", + &tcph, skb->len - protoff, NIP6(ipv6h->saddr), + NIP6(ipv6h->daddr)); + ret = NF_ACCEPT; + goto out; + } + + old_seq_aft_nl_set = ct_ftp_info->seq_aft_nl_set[dir]; + old_seq_aft_nl = ct_ftp_info->seq_aft_nl[dir]; + + DEBUGP("conntrack_ftp: datalen %u\n", datalen); + if (ftp_buffer[datalen - 1] == '\n') { + DEBUGP("conntrack_ftp: datalen %u ends in \\n\n", datalen); + if (!old_seq_aft_nl_set + || after(ntohl(tcph.seq) + datalen, old_seq_aft_nl)) { + DEBUGP("conntrack_ftp: updating nl to %u\n", + ntohl(tcph.seq) + datalen); + ct_ftp_info->seq_aft_nl[dir] = + ntohl(tcph.seq) + datalen; + ct_ftp_info->seq_aft_nl_set[dir] = 1; + } + } + + if(!old_seq_aft_nl_set || + (ntohl(tcph.seq) != old_seq_aft_nl)) { + DEBUGP("ip6_conntrack_ftp_help: wrong seq pos %s(%u)\n", + old_seq_aft_nl_set ? "":"(UNSET) ", old_seq_aft_nl); + ret = NF_ACCEPT; + goto out; + } + + /* Initialize IP array to expected address (it's not mentioned + in EPSV responses) */ + ipv6_addr_copy(&cmd.ip, &ct->tuplehash[dir].tuple.src.ip); + + for (i = 0; i < ARRAY_SIZE(search); i++) { + if (search[i].dir != dir) continue; + + found = find_pattern(ftp_buffer, datalen, + search[i].pattern, + search[i].plen, + search[i].skip, + search[i].term, + &matchoff, &matchlen, + &cmd, + search[i].getnum); + if (found) break; + } + if (found == -1) { + /* We don't usually drop packets. After all, this is + connection tracking, not packet filtering. + However, it is neccessary for accurate tracking in + this case. */ + if (net_ratelimit()) + printk("conntrack_ftp: partial %s %u+%u\n", + search[i].pattern, + ntohl(tcph.seq), datalen); + ret = NF_DROP; + goto out; + } else if (found == 0) { /* No match */ + ret = NF_ACCEPT; + goto out; + } + + DEBUGP("conntrack_ftp: match `%.*s' (%u bytes at %u)\n", + (int)matchlen, ftp_buffer + matchoff, + matchlen, ntohl(tcph.seq) + matchoff); + + memset(&expect, 0, sizeof(expect)); + + /* Update the ftp info */ + if (!ipv6_addr_cmp(&cmd.ip, &ct->tuplehash[dir].tuple.src.ip)) { + exp->seq = ntohl(tcph.seq) + matchoff; + exp_ftp_info->len = matchlen; + exp_ftp_info->ftptype = search[i].ftptype; + exp_ftp_info->port = cmd.port; + } else { + /* + This situation is occurred with NAT. + */ + if (!loose) { + ret = NF_ACCEPT; + goto out; + } + } + + ipv6_addr_copy(&t->src.ip, &ct->tuplehash[!dir].tuple.src.ip); + ipv6_addr_copy(&t->dst.ip, &cmd.ip); + t->src.u.tcp.port = 0; + t->dst.u.tcp.port = cmd.port; + t->dst.protonum = IPPROTO_TCP; + + ipv6_addr_set(&mask->src.ip, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF); + mask->src.u.tcp.port = 0; + mask->dst.u.tcp.port = 0xFFFF; + mask->dst.protonum = 0xFFFF; + + exp->expectfn = NULL; + + /* Ignore failure; should only happen with NAT */ + ip6_conntrack_expect_related(ct, &expect); + ret = NF_ACCEPT; + out: + UNLOCK_BH(&ip6_ftp_lock); + return ret; +} + +static struct ip6_conntrack_helper ftp[MAX_PORTS]; +static char ftp_names[MAX_PORTS][10]; + +/* Not __exit: called from init() */ +static void fini(void) +{ + int i; + for (i = 0; i < ports_c; i++) { + DEBUGP("ip6_ct_ftp: unregistering helper for port %d\n", + ports[i]); + ip6_conntrack_helper_unregister(&ftp[i]); + } +} + +static int __init init(void) +{ + int i, ret; + char *tmpname; + + if (ports[0] == 0) + ports[0] = FTP_PORT; + + for (i = 0; (i < MAX_PORTS) && ports[i]; i++) { + memset(&ftp[i], 0, sizeof(struct ip6_conntrack_helper)); + ftp[i].tuple.src.u.tcp.port = htons(ports[i]); + ftp[i].tuple.dst.protonum = IPPROTO_TCP; + ftp[i].mask.src.u.tcp.port = 0xFFFF; + ftp[i].mask.dst.protonum = 0xFFFF; + ftp[i].max_expected = 1; + ftp[i].timeout = 0; + ftp[i].flags = IP6_CT_HELPER_F_REUSE_EXPECT; + ftp[i].me = ip6_conntrack_ftp; + ftp[i].help = help; + + tmpname = &ftp_names[i][0]; + if (ports[i] == FTP_PORT) + sprintf(tmpname, "ftp"); + else + sprintf(tmpname, "ftp-%d", ports[i]); + ftp[i].name = tmpname; + + DEBUGP("ip6_ct_ftp: registering helper for port %d\n", + ports[i]); + ret = ip6_conntrack_helper_register(&ftp[i]); + + if (ret) { + fini(); + return ret; + } + ports_c++; + } + return 0; +} + + +PROVIDES_CONNTRACK6(ftp); +EXPORT_SYMBOL(ip6_ftp_lock); +MODULE_LICENSE("GPL"); +module_init(init); +module_exit(fini); Index: linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_proto_generic.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_proto_generic.c 2004-08-05 16:14:54.872145075 +0200 @@ -0,0 +1,82 @@ +/* + * IPv6 generic protocol extension for IPv6 connection tracking + * Linux INET6 implementation + * + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: net/ipv4/netfilter/ip_conntrack_proto_generic.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#define GENERIC_TIMEOUT (600*HZ) + +static int generic_pkt_to_tuple(const struct sk_buff *skb, + unsigned int dataoff, + struct ip6_conntrack_tuple *tuple) +{ + tuple->src.u.all = 0; + tuple->dst.u.all = 0; + + return 1; +} + +static int generic_invert_tuple(struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack_tuple *orig) +{ + tuple->src.u.all = 0; + tuple->dst.u.all = 0; + + return 1; +} + +/* Print out the per-protocol part of the tuple. */ +static unsigned int generic_print_tuple(char *buffer, + const struct ip6_conntrack_tuple *tuple) +{ + return 0; +} + +/* Print out the private part of the conntrack. */ +static unsigned int generic_print_conntrack(char *buffer, + const struct ip6_conntrack *state) +{ + return 0; +} + +/* Returns verdict for packet, or -1 for invalid. */ +static int established(struct ip6_conntrack *conntrack, + const struct sk_buff *skb, + unsigned int dataoff, + enum ip6_conntrack_info conntrackinfo) +{ + ip6_ct_refresh(conntrack, GENERIC_TIMEOUT); + return NF_ACCEPT; +} + +/* Called when a new connection for this protocol found. */ +static int +new(struct ip6_conntrack *conntrack, + const struct sk_buff *skb, + unsigned int dataoff) +{ + return 1; +} + +struct ip6_conntrack_protocol ip6_conntrack_generic_protocol += { { NULL, NULL }, 0, "unknown", + generic_pkt_to_tuple, generic_invert_tuple, generic_print_tuple, + generic_print_conntrack, established, new, NULL, NULL, NULL }; + Index: linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c 2004-08-05 16:14:54.873144978 +0200 @@ -0,0 +1,135 @@ +/* + * ICMPv6 extension for IPv6 connection tracking + * Linux INET6 implementation + * + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: net/ipv4/netfilter/ip_conntrack_proto_icmp.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define ICMPV6_TIMEOUT (30*HZ) + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +static int icmpv6_pkt_to_tuple(const struct sk_buff *skb, + unsigned int dataoff, + struct ip6_conntrack_tuple *tuple) +{ + struct icmp6hdr hdr; + + if (skb_copy_bits(skb, dataoff, &hdr, sizeof(hdr)) != 0) + return 0; + tuple->dst.u.icmpv6.type = hdr.icmp6_type; + tuple->src.u.icmpv6.id = hdr.icmp6_identifier; + tuple->dst.u.icmpv6.code = hdr.icmp6_code; + + return 1; +} + +static int icmpv6_invert_tuple(struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack_tuple *orig) +{ + /* Add 1; spaces filled with 0. */ + static u_int8_t invmap[] = { + [ICMPV6_ECHO_REQUEST] = ICMPV6_ECHO_REPLY + 1, + [ICMPV6_ECHO_REPLY] = ICMPV6_ECHO_REQUEST + 1, + [ICMPV6_NI_QUERY] = ICMPV6_NI_QUERY + 1, + [ICMPV6_NI_REPLY] = ICMPV6_NI_REPLY +1 + }; + + if (orig->dst.u.icmpv6.type >= sizeof(invmap) + || !invmap[orig->dst.u.icmpv6.type]) + return 0; + + tuple->src.u.icmpv6.id = orig->src.u.icmpv6.id; + tuple->dst.u.icmpv6.type = invmap[orig->dst.u.icmpv6.type] - 1; + tuple->dst.u.icmpv6.code = orig->dst.u.icmpv6.code; + return 1; +} + +/* Print out the per-protocol part of the tuple. */ +static unsigned int icmpv6_print_tuple(char *buffer, + const struct ip6_conntrack_tuple *tuple) +{ + return sprintf(buffer, "type=%u code=%u id=%u ", + tuple->dst.u.icmpv6.type, + tuple->dst.u.icmpv6.code, + ntohs(tuple->src.u.icmpv6.id)); +} + +/* Print out the private part of the conntrack. */ +static unsigned int icmpv6_print_conntrack(char *buffer, + const struct ip6_conntrack *conntrack) +{ + return sprintf(buffer, "count=%u ", + atomic_read(&conntrack->proto.icmpv6.count)); +} + +/* Returns verdict for packet, or -1 for invalid. */ +static int icmpv6_packet(struct ip6_conntrack *ct, + const struct sk_buff *skb, + unsigned int dataoff, + enum ip6_conntrack_info ctinfo) +{ + /* Try to delete connection immediately after all replies: + won't actually vanish as we still have skb, and del_timer + means this will only run once even if count hits zero twice + (theoretically possible with SMP) */ + if (CTINFO2DIR(ctinfo) == IP6_CT_DIR_REPLY) { + if (atomic_dec_and_test(&ct->proto.icmpv6.count) + && del_timer(&ct->timeout)) + ct->timeout.function((unsigned long)ct); + } else { + atomic_inc(&ct->proto.icmpv6.count); + ip6_ct_refresh(ct, ICMPV6_TIMEOUT); + } + + return NF_ACCEPT; +} + +/* Called when a new connection for this protocol found. */ +static int icmpv6_new(struct ip6_conntrack *conntrack, + const struct sk_buff *skb, + unsigned int dataoff) +{ + static u_int8_t valid_new[] = { + [ICMPV6_ECHO_REQUEST] = 1, + [ICMPV6_NI_QUERY] = 1 + }; + + if (conntrack->tuplehash[0].tuple.dst.u.icmpv6.type >= sizeof(valid_new) + || !valid_new[conntrack->tuplehash[0].tuple.dst.u.icmpv6.type]) { + /* Can't create a new ICMPV6 `conn' with this. */ + DEBUGP("icmpv6: can't create new conn with type %u\n", + conntrack->tuplehash[0].tuple.dst.u.icmpv6.type); + DUMP_TUPLE(&conntrack->tuplehash[0].tuple); + return 0; + } + atomic_set(&conntrack->proto.icmpv6.count, 0); + return 1; +} + +struct ip6_conntrack_protocol ip6_conntrack_protocol_icmpv6 += { { NULL, NULL }, IPPROTO_ICMPV6, "icmpv6", + icmpv6_pkt_to_tuple, icmpv6_invert_tuple, icmpv6_print_tuple, + icmpv6_print_conntrack, icmpv6_packet, icmpv6_new, NULL, NULL, NULL }; Index: linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c 2004-08-05 16:15:21.131598578 +0200 @@ -0,0 +1,275 @@ +/* + * TCP extension for IPv6 Connection Tracking + * Linux INET6 implementation + * + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: net/ipv4/netfilter/ip_conntrack_proto_tcp.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +/* Protects conntrack->proto.tcp */ +static DECLARE_RWLOCK(tcp_lock); + +/* FIXME: Examine ipfilter's timeouts and conntrack transitions more + closely. They're more complex. --RR */ + +/* Actually, I believe that neither ipmasq (where this code is stolen + from) nor ipfilter do it exactly right. A new conntrack machine taking + into account packet loss (which creates uncertainty as to exactly + the conntrack of the connection) is required. RSN. --RR */ + +static const char *tcp_conntrack_names[] = { + "NONE", + "ESTABLISHED", + "SYN_SENT", + "SYN_RECV", + "FIN_WAIT", + "TIME_WAIT", + "CLOSE", + "CLOSE_WAIT", + "LAST_ACK", + "LISTEN" +}; + +#define SECS *HZ +#define MINS * 60 SECS +#define HOURS * 60 MINS +#define DAYS * 24 HOURS + + +static unsigned long tcp_timeouts[] += { 30 MINS, /* TCP_CONNTRACK_NONE, */ + 5 DAYS, /* TCP_CONNTRACK_ESTABLISHED, */ + 2 MINS, /* TCP_CONNTRACK_SYN_SENT, */ + 60 SECS, /* TCP_CONNTRACK_SYN_RECV, */ + 2 MINS, /* TCP_CONNTRACK_FIN_WAIT, */ + 2 MINS, /* TCP_CONNTRACK_TIME_WAIT, */ + 10 SECS, /* TCP_CONNTRACK_CLOSE, */ + 60 SECS, /* TCP_CONNTRACK_CLOSE_WAIT, */ + 30 SECS, /* TCP_CONNTRACK_LAST_ACK, */ + 2 MINS, /* TCP_CONNTRACK_LISTEN, */ +}; + +#define sNO TCP_CONNTRACK_NONE +#define sES TCP_CONNTRACK_ESTABLISHED +#define sSS TCP_CONNTRACK_SYN_SENT +#define sSR TCP_CONNTRACK_SYN_RECV +#define sFW TCP_CONNTRACK_FIN_WAIT +#define sTW TCP_CONNTRACK_TIME_WAIT +#define sCL TCP_CONNTRACK_CLOSE +#define sCW TCP_CONNTRACK_CLOSE_WAIT +#define sLA TCP_CONNTRACK_LAST_ACK +#define sLI TCP_CONNTRACK_LISTEN +#define sIV TCP_CONNTRACK_MAX + +static enum tcp_conntrack tcp_conntracks[2][5][TCP_CONNTRACK_MAX] = { + { +/* ORIGINAL */ +/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI */ +/*syn*/ {sSS, sES, sSS, sSR, sSS, sSS, sSS, sSS, sSS, sLI }, +/*fin*/ {sTW, sFW, sSS, sTW, sFW, sTW, sCL, sTW, sLA, sLI }, +/*ack*/ {sES, sES, sSS, sES, sFW, sTW, sCL, sCW, sLA, sES }, +/*rst*/ {sCL, sCL, sSS, sCL, sCL, sTW, sCL, sCL, sCL, sCL }, +/*none*/{sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV } + }, + { +/* REPLY */ +/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI */ +/*syn*/ {sSR, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSR }, +/*fin*/ {sCL, sCW, sSS, sTW, sTW, sTW, sCL, sCW, sLA, sLI }, +/*ack*/ {sCL, sES, sSS, sSR, sFW, sTW, sCL, sCW, sCL, sLI }, +/*rst*/ {sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sLA, sLI }, +/*none*/{sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV } + } +}; + +static int tcp_pkt_to_tuple(const struct sk_buff *skb, + unsigned int dataoff, + struct ip6_conntrack_tuple *tuple) +{ + struct tcphdr hdr; + + /* Actually only need first 8 bytes. */ + if (skb_copy_bits(skb, dataoff, &hdr, 8) != 0) + return 0; + + tuple->src.u.tcp.port = hdr.source; + tuple->dst.u.tcp.port = hdr.dest; + + return 1; +} + +static int tcp_invert_tuple(struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack_tuple *orig) +{ + tuple->src.u.tcp.port = orig->dst.u.tcp.port; + tuple->dst.u.tcp.port = orig->src.u.tcp.port; + return 1; +} + +/* Print out the per-protocol part of the tuple. */ +static unsigned int tcp_print_tuple(char *buffer, + const struct ip6_conntrack_tuple *tuple) +{ + return sprintf(buffer, "sport=%hu dport=%hu ", + ntohs(tuple->src.u.tcp.port), + ntohs(tuple->dst.u.tcp.port)); +} + +/* Print out the private part of the conntrack. */ +static unsigned int tcp_print_conntrack(char *buffer, + const struct ip6_conntrack *conntrack) +{ + enum tcp_conntrack state; + + READ_LOCK(&tcp_lock); + state = conntrack->proto.tcp.state; + READ_UNLOCK(&tcp_lock); + + return sprintf(buffer, "%s ", tcp_conntrack_names[state]); +} + +static unsigned int get_conntrack_index(const struct tcphdr *tcph) +{ + if (tcph->rst) return 3; + else if (tcph->syn) return 0; + else if (tcph->fin) return 1; + else if (tcph->ack) return 2; + else return 4; +} + +/* Returns verdict for packet, or -1 for invalid. */ +static int tcp_packet(struct ip6_conntrack *conntrack, + const struct sk_buff *skb, + unsigned int dataoff, + enum ip6_conntrack_info ctinfo) +{ + enum tcp_conntrack newconntrack, oldtcpstate; + struct tcphdr tcph; + + if (skb_copy_bits(skb, dataoff, &tcph, sizeof(tcph)) != 0) + return -1; + if (skb->len < skb->nh.iph->ihl * 4 + tcph.doff * 4) + return -1; + + WRITE_LOCK(&tcp_lock); + oldtcpstate = conntrack->proto.tcp.state; + newconntrack + = tcp_conntracks + [CTINFO2DIR(ctinfo)] + [get_conntrack_index(&tcph)][oldtcpstate]; + + /* Invalid */ + if (newconntrack == TCP_CONNTRACK_MAX) { + DEBUGP("ip6_conntrack_tcp: Invalid dir=%i index=%u conntrack=%u\n", + CTINFO2DIR(ctinfo), get_conntrack_index(&tcph), + conntrack->proto.tcp.state); + WRITE_UNLOCK(&tcp_lock); + return -1; + } + + conntrack->proto.tcp.state = newconntrack; + + /* Poor man's window tracking: record SYN/ACK for handshake check */ + if (oldtcpstate == TCP_CONNTRACK_SYN_SENT + && CTINFO2DIR(ctinfo) == IP6_CT_DIR_REPLY + && tcph.syn && tcph.ack) + conntrack->proto.tcp.handshake_ack + = htonl(ntohl(tcph.seq) + 1); + + /* If only reply is a RST, we can consider ourselves not to + have an established connection: this is a fairly common + problem case, so we can delete the conntrack + immediately. --RR */ + if (!test_bit(IP6S_SEEN_REPLY_BIT, &conntrack->status) && tcph.rst) { + WRITE_UNLOCK(&tcp_lock); + if (del_timer(&conntrack->timeout)) + conntrack->timeout.function((unsigned long)conntrack); + } else { + /* Set ASSURED if we see see valid ack in ESTABLISHED after SYN_RECV */ + if (oldtcpstate == TCP_CONNTRACK_SYN_RECV + && CTINFO2DIR(ctinfo) == IP6_CT_DIR_ORIGINAL + && tcph.ack && !tcph.syn + && tcph.ack_seq == conntrack->proto.tcp.handshake_ack) + set_bit(IP6S_ASSURED_BIT, &conntrack->status); + + WRITE_UNLOCK(&tcp_lock); + ip6_ct_refresh(conntrack, tcp_timeouts[newconntrack]); + } + + return NF_ACCEPT; +} + +/* Called when a new connection for this protocol found. */ +static int tcp_new(struct ip6_conntrack *conntrack, const struct sk_buff *skb, + unsigned int dataoff) +{ + enum tcp_conntrack newconntrack; + struct tcphdr tcph; + + if (skb_copy_bits(skb, dataoff, &tcph, sizeof(tcph)) != 0) + return -1; + + /* Don't need lock here: this conntrack not in circulation yet */ + newconntrack + = tcp_conntracks[0][get_conntrack_index(&tcph)] + [TCP_CONNTRACK_NONE]; + + /* Invalid: delete conntrack */ + if (newconntrack == TCP_CONNTRACK_MAX) { + DEBUGP("ip6_conntrack_tcp: invalid new deleting.\n"); + return 0; + } + + conntrack->proto.tcp.state = newconntrack; + return 1; +} + +static int tcp_exp_matches_pkt(struct ip6_conntrack_expect *exp, + const struct sk_buff *skb, + unsigned int dataoff) +{ + struct tcphdr tcph; + unsigned int datalen; + + if (skb_copy_bits(skb, dataoff, &tcph, sizeof(tcph)) != 0) + return 0; + datalen = skb->len - dataoff; + + return between(exp->seq, ntohl(tcph.seq), ntohl(tcph.seq) + datalen); +} + +struct ip6_conntrack_protocol ip6_conntrack_protocol_tcp += { { NULL, NULL }, IPPROTO_TCP, "tcp", + tcp_pkt_to_tuple, tcp_invert_tuple, tcp_print_tuple, tcp_print_conntrack, + tcp_packet, tcp_new, NULL, tcp_exp_matches_pkt, NULL }; Index: linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_proto_udp.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_proto_udp.c 2004-08-05 16:14:54.876144687 +0200 @@ -0,0 +1,95 @@ +/* + * UDP extension for IPv6 Connection Tracking + * Linux INET6 implementation + * + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: net/ipv4/netfilter/ip_conntrack_proto_udp.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include + +#define UDP_TIMEOUT (30*HZ) +#define UDP_STREAM_TIMEOUT (180*HZ) + +static int udp_pkt_to_tuple(const struct sk_buff *skb, + unsigned int dataoff, + struct ip6_conntrack_tuple *tuple) +{ + struct udphdr hdr; + + /* Actually only need first 8 bytes. */ + if (skb_copy_bits(skb, dataoff, &hdr, 8) != 0) + return 0; + + tuple->src.u.udp.port = hdr.source; + tuple->dst.u.udp.port = hdr.dest; + + return 1; +} + +static int udp_invert_tuple(struct ip6_conntrack_tuple *tuple, + const struct ip6_conntrack_tuple *orig) +{ + tuple->src.u.udp.port = orig->dst.u.udp.port; + tuple->dst.u.udp.port = orig->src.u.udp.port; + return 1; +} + +/* Print out the per-protocol part of the tuple. */ +static unsigned int udp_print_tuple(char *buffer, + const struct ip6_conntrack_tuple *tuple) +{ + return sprintf(buffer, "sport=%hu dport=%hu ", + ntohs(tuple->src.u.udp.port), + ntohs(tuple->dst.u.udp.port)); +} + +/* Print out the private part of the conntrack. */ +static unsigned int udp_print_conntrack(char *buffer, + const struct ip6_conntrack *conntrack) +{ + return 0; +} + +/* Returns verdict for packet, and may modify conntracktype */ +static int udp_packet(struct ip6_conntrack *conntrack, + const struct sk_buff *skb, + unsigned int dataoff, + enum ip6_conntrack_info conntrackinfo) +{ + /* If we've seen traffic both ways, this is some kind of UDP + stream. Extend timeout. */ + if (test_bit(IP6S_SEEN_REPLY_BIT, &conntrack->status)) { + ip6_ct_refresh(conntrack, UDP_STREAM_TIMEOUT); + /* Also, more likely to be important, and not a probe */ + set_bit(IP6S_ASSURED_BIT, &conntrack->status); + } else + ip6_ct_refresh(conntrack, UDP_TIMEOUT); + + return NF_ACCEPT; +} + +/* Called when a new connection for this protocol found. */ +static int udp_new(struct ip6_conntrack *conntrack, const struct sk_buff *skb, + unsigned int dataoff) +{ + return 1; +} + +struct ip6_conntrack_protocol ip6_conntrack_protocol_udp += { { NULL, NULL }, IPPROTO_UDP, "udp", + udp_pkt_to_tuple, udp_invert_tuple, udp_print_tuple, udp_print_conntrack, + udp_packet, udp_new, NULL, NULL, NULL }; Index: linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_reasm.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_reasm.c 2004-08-05 16:14:54.879144396 +0200 @@ -0,0 +1,990 @@ +/* + * IPv6 fragment reassembly for connection tracking + * Linux INET6 implementation + * + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: net/ipv6/reassembly.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +#define IP6CT_FRAGS_HIGH_THRESH 262144 /* == 256*1024 */ +#define IP6CT_FRAGS_LOW_THRESH 196608 /* == 192*1024 */ +#define IP6CT_FRAGS_TIMEOUT IPV6_FRAG_TIMEOUT + +static int sysctl_ip6_ct_frag_high_thresh = 256*1024; +static int sysctl_ip6_ct_frag_low_thresh = 192*1024; +static int sysctl_ip6_ct_frag_time = IPV6_FRAG_TIMEOUT; + +struct ip6ct_frag_skb_cb +{ + struct inet6_skb_parm h; + int offset; + struct sk_buff *orig; +}; + +#define IP6CT_FRAG6_CB(skb) ((struct ip6ct_frag_skb_cb*)((skb)->cb)) + + +/* + * Equivalent of ipv4 struct ipq + */ + +struct ip6ct_frag_queue +{ + struct ip6ct_frag_queue *next; + struct list_head lru_list; /* lru list member */ + + __u32 id; /* fragment id */ + struct in6_addr saddr; + struct in6_addr daddr; + + spinlock_t lock; + atomic_t refcnt; + struct timer_list timer; /* expire timer */ + struct sk_buff *fragments; + int len; + int meat; + struct timeval stamp; + unsigned int csum; + __u8 last_in; /* has first/last segment arrived? */ +#define COMPLETE 4 +#define FIRST_IN 2 +#define LAST_IN 1 + __u16 nhoffset; + struct ip6ct_frag_queue **pprev; +}; + +/* Hash table. */ + +#define IP6CT_Q_HASHSZ 64 + +static struct ip6ct_frag_queue *ip6_ct_frag_hash[IP6CT_Q_HASHSZ]; +static rwlock_t ip6_ct_frag_lock = RW_LOCK_UNLOCKED; +static u32 ip6_ct_frag_hash_rnd; +static LIST_HEAD(ip6_ct_frag_lru_list); +int ip6_ct_frag_nqueues = 0; + +static __inline__ void __fq_unlink(struct ip6ct_frag_queue *fq) +{ + if(fq->next) + fq->next->pprev = fq->pprev; + *fq->pprev = fq->next; + list_del(&fq->lru_list); + ip6_ct_frag_nqueues--; +} + +static __inline__ void fq_unlink(struct ip6ct_frag_queue *fq) +{ + write_lock(&ip6_ct_frag_lock); + __fq_unlink(fq); + write_unlock(&ip6_ct_frag_lock); +} + +static unsigned int ip6qhashfn(u32 id, struct in6_addr *saddr, + struct in6_addr *daddr) +{ + u32 a, b, c; + + a = saddr->s6_addr32[0]; + b = saddr->s6_addr32[1]; + c = saddr->s6_addr32[2]; + + a += JHASH_GOLDEN_RATIO; + b += JHASH_GOLDEN_RATIO; + c += ip6_ct_frag_hash_rnd; + __jhash_mix(a, b, c); + + a += saddr->s6_addr32[3]; + b += daddr->s6_addr32[0]; + c += daddr->s6_addr32[1]; + __jhash_mix(a, b, c); + + a += daddr->s6_addr32[2]; + b += daddr->s6_addr32[3]; + c += id; + __jhash_mix(a, b, c); + + return c & (IP6CT_Q_HASHSZ - 1); +} + +static struct timer_list ip6_ct_frag_secret_timer; +int sysctl_ip6_ct_frag_secret_interval = 10 * 60 * HZ; + +static void ip6_ct_frag_secret_rebuild(unsigned long dummy) +{ + unsigned long now = jiffies; + int i; + + write_lock(&ip6_ct_frag_lock); + get_random_bytes(&ip6_ct_frag_hash_rnd, sizeof(u32)); + for (i = 0; i < IP6CT_Q_HASHSZ; i++) { + struct ip6ct_frag_queue *q; + + q = ip6_ct_frag_hash[i]; + while (q) { + struct ip6ct_frag_queue *next = q->next; + unsigned int hval = ip6qhashfn(q->id, + &q->saddr, + &q->daddr); + + if (hval != i) { + /* Unlink. */ + if (q->next) + q->next->pprev = q->pprev; + *q->pprev = q->next; + + /* Relink to new hash chain. */ + if ((q->next = ip6_ct_frag_hash[hval]) != NULL) + q->next->pprev = &q->next; + ip6_ct_frag_hash[hval] = q; + q->pprev = &ip6_ct_frag_hash[hval]; + } + + q = next; + } + } + write_unlock(&ip6_ct_frag_lock); + + mod_timer(&ip6_ct_frag_secret_timer, now + sysctl_ip6_ct_frag_secret_interval); +} + +atomic_t ip6_ct_frag_mem = ATOMIC_INIT(0); + +/* Memory Tracking Functions. */ +static inline void frag_kfree_skb(struct sk_buff *skb) +{ + atomic_sub(skb->truesize, &ip6_ct_frag_mem); + if (IP6CT_FRAG6_CB(skb)->orig) + kfree_skb(IP6CT_FRAG6_CB(skb)->orig); + + kfree_skb(skb); +} + +static inline void frag_free_queue(struct ip6ct_frag_queue *fq) +{ + atomic_sub(sizeof(struct ip6ct_frag_queue), &ip6_ct_frag_mem); + kfree(fq); +} + +static inline struct ip6ct_frag_queue *frag_alloc_queue(void) +{ + struct ip6ct_frag_queue *fq = kmalloc(sizeof(struct ip6ct_frag_queue), GFP_ATOMIC); + + if(!fq) + return NULL; + atomic_add(sizeof(struct ip6ct_frag_queue), &ip6_ct_frag_mem); + return fq; +} + +/* Destruction primitives. */ + +/* Complete destruction of fq. */ +static void ip6_ct_frag_destroy(struct ip6ct_frag_queue *fq) +{ + struct sk_buff *fp; + + BUG_TRAP(fq->last_in&COMPLETE); + BUG_TRAP(del_timer(&fq->timer) == 0); + + /* Release all fragment data. */ + fp = fq->fragments; + while (fp) { + struct sk_buff *xp = fp->next; + + frag_kfree_skb(fp); + fp = xp; + } + + frag_free_queue(fq); +} + +static __inline__ void fq_put(struct ip6ct_frag_queue *fq) +{ + if (atomic_dec_and_test(&fq->refcnt)) + ip6_ct_frag_destroy(fq); +} + +/* Kill fq entry. It is not destroyed immediately, + * because caller (and someone more) holds reference count. + */ +static __inline__ void fq_kill(struct ip6ct_frag_queue *fq) +{ + if (del_timer(&fq->timer)) + atomic_dec(&fq->refcnt); + + if (!(fq->last_in & COMPLETE)) { + fq_unlink(fq); + atomic_dec(&fq->refcnt); + fq->last_in |= COMPLETE; + } +} + +static void ip6_ct_frag_evictor(void) +{ + struct ip6ct_frag_queue *fq; + struct list_head *tmp; + + for(;;) { + if (atomic_read(&ip6_ct_frag_mem) <= sysctl_ip6_ct_frag_low_thresh) + return; + read_lock(&ip6_ct_frag_lock); + if (list_empty(&ip6_ct_frag_lru_list)) { + read_unlock(&ip6_ct_frag_lock); + return; + } + tmp = ip6_ct_frag_lru_list.next; + fq = list_entry(tmp, struct ip6ct_frag_queue, lru_list); + atomic_inc(&fq->refcnt); + read_unlock(&ip6_ct_frag_lock); + + spin_lock(&fq->lock); + if (!(fq->last_in&COMPLETE)) + fq_kill(fq); + spin_unlock(&fq->lock); + + fq_put(fq); + } +} + +static void ip6_ct_frag_expire(unsigned long data) +{ + struct ip6ct_frag_queue *fq = (struct ip6ct_frag_queue *) data; + + spin_lock(&fq->lock); + + if (fq->last_in & COMPLETE) + goto out; + + fq_kill(fq); + +out: + spin_unlock(&fq->lock); + fq_put(fq); +} + +/* Creation primitives. */ + + +static struct ip6ct_frag_queue *ip6_ct_frag_intern(unsigned int hash, + struct ip6ct_frag_queue *fq_in) +{ + struct ip6ct_frag_queue *fq; + + write_lock(&ip6_ct_frag_lock); +#ifdef CONFIG_SMP + for (fq = ip6_ct_frag_hash[hash]; fq; fq = fq->next) { + if (fq->id == fq_in->id && + !ipv6_addr_cmp(&fq_in->saddr, &fq->saddr) && + !ipv6_addr_cmp(&fq_in->daddr, &fq->daddr)) { + atomic_inc(&fq->refcnt); + write_unlock(&ip6_ct_frag_lock); + fq_in->last_in |= COMPLETE; + fq_put(fq_in); + return fq; + } + } +#endif + fq = fq_in; + + if (!mod_timer(&fq->timer, jiffies + sysctl_ip6_ct_frag_time)) + atomic_inc(&fq->refcnt); + + atomic_inc(&fq->refcnt); + if((fq->next = ip6_ct_frag_hash[hash]) != NULL) + fq->next->pprev = &fq->next; + ip6_ct_frag_hash[hash] = fq; + fq->pprev = &ip6_ct_frag_hash[hash]; + INIT_LIST_HEAD(&fq->lru_list); + list_add_tail(&fq->lru_list, &ip6_ct_frag_lru_list); + ip6_ct_frag_nqueues++; + write_unlock(&ip6_ct_frag_lock); + return fq; +} + + +static struct ip6ct_frag_queue * +ip6_ct_frag_create(unsigned int hash, u32 id, struct in6_addr *src, struct in6_addr *dst) +{ + struct ip6ct_frag_queue *fq; + + if ((fq = frag_alloc_queue()) == NULL) { + DEBUGP("Can't alloc new queue\n"); + goto oom; + } + + memset(fq, 0, sizeof(struct ip6ct_frag_queue)); + + fq->id = id; + ipv6_addr_copy(&fq->saddr, src); + ipv6_addr_copy(&fq->daddr, dst); + + init_timer(&fq->timer); + fq->timer.function = ip6_ct_frag_expire; + fq->timer.data = (long) fq; + fq->lock = SPIN_LOCK_UNLOCKED; + atomic_set(&fq->refcnt, 1); + + return ip6_ct_frag_intern(hash, fq); + +oom: + return NULL; +} + +static __inline__ struct ip6ct_frag_queue * +fq_find(u32 id, struct in6_addr *src, struct in6_addr *dst) +{ + struct ip6ct_frag_queue *fq; + unsigned int hash = ip6qhashfn(id, src, dst); + + read_lock(&ip6_ct_frag_lock); + for(fq = ip6_ct_frag_hash[hash]; fq; fq = fq->next) { + if (fq->id == id && + !ipv6_addr_cmp(src, &fq->saddr) && + !ipv6_addr_cmp(dst, &fq->daddr)) { + atomic_inc(&fq->refcnt); + read_unlock(&ip6_ct_frag_lock); + return fq; + } + } + read_unlock(&ip6_ct_frag_lock); + + return ip6_ct_frag_create(hash, id, src, dst); +} + + +static int ip6_ct_frag_queue(struct ip6ct_frag_queue *fq, struct sk_buff *skb, + struct frag_hdr *fhdr, int nhoff) +{ + struct sk_buff *prev, *next; + int offset, end; + + if (fq->last_in & COMPLETE) { + DEBUGP("Allready completed\n"); + goto err; + } + + offset = ntohs(fhdr->frag_off) & ~0x7; + end = offset + (ntohs(skb->nh.ipv6h->payload_len) - + ((u8 *) (fhdr + 1) - (u8 *) (skb->nh.ipv6h + 1))); + + if ((unsigned int)end > IPV6_MAXPLEN) { + DEBUGP("offset is too large.\n"); + return -1; + } + + if (skb->ip_summed == CHECKSUM_HW) + skb->csum = csum_sub(skb->csum, + csum_partial(skb->nh.raw, (u8*)(fhdr+1)-skb->nh.raw, 0)); + + /* Is this the final fragment? */ + if (!(fhdr->frag_off & htons(IP6_MF))) { + /* If we already have some bits beyond end + * or have different end, the segment is corrupted. + */ + if (end < fq->len || + ((fq->last_in & LAST_IN) && end != fq->len)) { + DEBUGP("already received last fragment\n"); + goto err; + } + fq->last_in |= LAST_IN; + fq->len = end; + } else { + /* Check if the fragment is rounded to 8 bytes. + * Required by the RFC. + */ + if (end & 0x7) { + /* RFC2460 says always send parameter problem in + * this case. -DaveM + */ + DEBUGP("the end of this message is not rounded to 8 bytes.\n"); + return -1; + } + if (end > fq->len) { + /* Some bits beyond end -> corruption. */ + if (fq->last_in & LAST_IN) { + DEBUGP("last packet already reached.\n"); + goto err; + } + fq->len = end; + } + } + + if (end == offset) + goto err; + + /* Point into the IP datagram 'data' part. */ + if (!pskb_pull(skb, (u8 *) (fhdr + 1) - skb->data)) { + DEBUGP("queue: message is too short.\n"); + goto err; + } + if (end-offset < skb->len) { + if (pskb_trim(skb, end - offset)) { + DEBUGP("Can't trim\n"); + goto err; + } + if (skb->ip_summed != CHECKSUM_UNNECESSARY) + skb->ip_summed = CHECKSUM_NONE; + } + + /* Find out which fragments are in front and at the back of us + * in the chain of fragments so far. We must know where to put + * this fragment, right? + */ + prev = NULL; + for(next = fq->fragments; next != NULL; next = next->next) { + if (IP6CT_FRAG6_CB(next)->offset >= offset) + break; /* bingo! */ + prev = next; + } + + /* We found where to put this one. Check for overlap with + * preceding fragment, and, if needed, align things so that + * any overlaps are eliminated. + */ + if (prev) { + int i = (IP6CT_FRAG6_CB(prev)->offset + prev->len) - offset; + + if (i > 0) { + offset += i; + if (end <= offset) { + DEBUGP("overlap\n"); + goto err; + } + if (!pskb_pull(skb, i)) { + DEBUGP("Can't pull\n"); + goto err; + } + if (skb->ip_summed != CHECKSUM_UNNECESSARY) + skb->ip_summed = CHECKSUM_NONE; + } + } + + /* Look for overlap with succeeding segments. + * If we can merge fragments, do it. + */ + while (next && IP6CT_FRAG6_CB(next)->offset < end) { + int i = end - IP6CT_FRAG6_CB(next)->offset; /* overlap is 'i' bytes */ + + if (i < next->len) { + /* Eat head of the next overlapped fragment + * and leave the loop. The next ones cannot overlap. + */ + DEBUGP("Eat head of the overlapped parts.: %d", i); + if (!pskb_pull(next, i)) + goto err; + IP6CT_FRAG6_CB(next)->offset += i; /* next fragment */ + fq->meat -= i; + if (next->ip_summed != CHECKSUM_UNNECESSARY) + next->ip_summed = CHECKSUM_NONE; + break; + } else { + struct sk_buff *free_it = next; + + /* Old fragmnet is completely overridden with + * new one drop it. + */ + next = next->next; + + if (prev) + prev->next = next; + else + fq->fragments = next; + + fq->meat -= free_it->len; + frag_kfree_skb(free_it); + } + } + + IP6CT_FRAG6_CB(skb)->offset = offset; + + /* Insert this fragment in the chain of fragments. */ + skb->next = next; + if (prev) + prev->next = skb; + else + fq->fragments = skb; + + skb->dev = NULL; + fq->stamp = skb->stamp; + fq->meat += skb->len; + atomic_add(skb->truesize, &ip6_ct_frag_mem); + + /* The first fragment. + * nhoffset is obtained from the first fragment, of course. + */ + if (offset == 0) { + fq->nhoffset = nhoff; + fq->last_in |= FIRST_IN; + } + write_lock(&ip6_ct_frag_lock); + list_move_tail(&fq->lru_list, &ip6_ct_frag_lru_list); + write_unlock(&ip6_ct_frag_lock); + return 0; + +err: + return -1; +} + +/* + * Check if this packet is complete. + * Returns NULL on failure by any reason, and pointer + * to current nexthdr field in reassembled frame. + * + * It is called with locked fq, and caller must check that + * queue is eligible for reassembly i.e. it is not COMPLETE, + * the last and the first frames arrived and all the bits are here. + */ +static struct sk_buff * +ip6_ct_frag_reasm(struct ip6ct_frag_queue *fq, struct net_device *dev) +{ + struct sk_buff *fp, *op, *head = fq->fragments; + int payload_len; + + fq_kill(fq); + + BUG_TRAP(head != NULL); + BUG_TRAP(IP6CT_FRAG6_CB(head)->offset == 0); + + /* Unfragmented part is taken from the first segment. */ + payload_len = (head->data - head->nh.raw) - sizeof(struct ipv6hdr) + fq->len - sizeof(struct frag_hdr); + if (payload_len > IPV6_MAXPLEN) { + DEBUGP("payload len is too large.\n"); + goto out_oversize; + } + + /* Head of list must not be cloned. */ + if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC)) { + DEBUGP("skb is cloned but can't expand head"); + goto out_oom; + } + + /* If the first fragment is fragmented itself, we split + * it to two chunks: the first with data and paged part + * and the second, holding only fragments. */ + if (skb_shinfo(head)->frag_list) { + struct sk_buff *clone; + int i, plen = 0; + + if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL) { + DEBUGP("Can't alloc skb\n"); + goto out_oom; + } + clone->next = head->next; + head->next = clone; + skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list; + skb_shinfo(head)->frag_list = NULL; + for (i=0; inr_frags; i++) + plen += skb_shinfo(head)->frags[i].size; + clone->len = clone->data_len = head->data_len - plen; + head->data_len -= clone->len; + head->len -= clone->len; + clone->csum = 0; + clone->ip_summed = head->ip_summed; + + IP6CT_FRAG6_CB(clone)->orig = NULL; + atomic_add(clone->truesize, &ip6_ct_frag_mem); + } + + /* We have to remove fragment header from datagram and to relocate + * header in order to calculate ICV correctly. */ + head->nh.raw[fq->nhoffset] = head->h.raw[0]; + memmove(head->head + sizeof(struct frag_hdr), head->head, + (head->data - head->head) - sizeof(struct frag_hdr)); + head->mac.raw += sizeof(struct frag_hdr); + head->nh.raw += sizeof(struct frag_hdr); + + skb_shinfo(head)->frag_list = head->next; + head->h.raw = head->data; + skb_push(head, head->data - head->nh.raw); + atomic_sub(head->truesize, &ip6_ct_frag_mem); + + for (fp=head->next; fp; fp = fp->next) { + head->data_len += fp->len; + head->len += fp->len; + if (head->ip_summed != fp->ip_summed) + head->ip_summed = CHECKSUM_NONE; + else if (head->ip_summed == CHECKSUM_HW) + head->csum = csum_add(head->csum, fp->csum); + head->truesize += fp->truesize; + atomic_sub(fp->truesize, &ip6_ct_frag_mem); + } + + head->next = NULL; + head->dev = dev; + head->stamp = fq->stamp; + head->nh.ipv6h->payload_len = ntohs(payload_len); + + /* Yes, and fold redundant checksum back. 8) */ + if (head->ip_summed == CHECKSUM_HW) + head->csum = csum_partial(head->nh.raw, head->h.raw-head->nh.raw, head->csum); + + fq->fragments = NULL; + + /* all original skbs are linked into the IP6CT_FRAG6_CB(head).orig */ + fp = skb_shinfo(head)->frag_list; + if (IP6CT_FRAG6_CB(fp)->orig == NULL) + /* at above code, head skb is divided into two skbs. */ + fp = fp->next; + + op = IP6CT_FRAG6_CB(head)->orig; + for (; fp; fp = fp->next) { + struct sk_buff *orig = IP6CT_FRAG6_CB(fp)->orig; + + op->next = orig; + op = orig; + IP6CT_FRAG6_CB(fp)->orig = NULL; + } + + return head; + +out_oversize: + if (net_ratelimit()) + printk(KERN_DEBUG "ip6_ct_frag_reasm: payload len = %d\n", payload_len); + goto out_fail; +out_oom: + if (net_ratelimit()) + printk(KERN_DEBUG "ip6_ct_frag_reasm: no memory for reassembly\n"); +out_fail: + return NULL; +} + +/* + * find the header just before Fragment Header. + * + * if success return 0 and set ... + * (*prevhdrp): the value of "Next Header Field" in the header + * just before Fragment Header. + * (*prevhoff): the offset of "Next Header Field" in the header + * just before Fragment Header. + * (*fhoff) : the offset of Fragment Header. + * + * Based on ipv6_skip_hdr() in net/ipv6/exthdr.c + * + */ +static int +find_prev_fhdr(struct sk_buff *skb, u8 *prevhdrp, int *prevhoff, int *fhoff) +{ + u8 nexthdr = skb->nh.ipv6h->nexthdr; + u8 prev_nhoff = (u8 *)&skb->nh.ipv6h->nexthdr - skb->data; + int start = (u8 *)(skb->nh.ipv6h+1) - skb->data; + int len = skb->len - start; + u8 prevhdr = NEXTHDR_IPV6; + + while (nexthdr != NEXTHDR_FRAGMENT) { + struct ipv6_opt_hdr hdr; + int hdrlen; + + if (!ipv6_ext_hdr(nexthdr)) { + return -1; + } + if (len < (int)sizeof(struct ipv6_opt_hdr)) { + DEBUGP("too short\n"); + return -1; + } + if (nexthdr == NEXTHDR_NONE) { + DEBUGP("next header is none\n"); + return -1; + } + if (skb_copy_bits(skb, start, &hdr, sizeof(hdr))) + BUG(); + if (nexthdr == NEXTHDR_AUTH) + hdrlen = (hdr.hdrlen+2)<<2; + else + hdrlen = ipv6_optlen(&hdr); + + prevhdr = nexthdr; + prev_nhoff = start; + + nexthdr = hdr.nexthdr; + len -= hdrlen; + start += hdrlen; + } + + if (len < 0) + return -1; + + *prevhdrp = prevhdr; + *prevhoff = prev_nhoff; + *fhoff = start; + + return 0; +} + +struct sk_buff *ip6_ct_gather_frags(struct sk_buff *skb) +{ + struct sk_buff *clone; + struct net_device *dev = skb->dev; + struct frag_hdr *fhdr; + struct ip6ct_frag_queue *fq; + struct ipv6hdr *hdr; + int fhoff, nhoff; + u8 prevhdr; + struct sk_buff *ret_skb = NULL; + + /* Jumbo payload inhibits frag. header */ + if (skb->nh.ipv6h->payload_len == 0) { + DEBUGP("payload len = 0\n"); + return skb; + } + + if (find_prev_fhdr(skb, &prevhdr, &nhoff, &fhoff) < 0) + return skb; + + clone = skb_clone(skb, GFP_ATOMIC); + if (clone == NULL) { + DEBUGP("Can't clone skb\n"); + return skb; + } + + IP6CT_FRAG6_CB(clone)->orig = skb; + + if (!pskb_may_pull(clone, fhoff + sizeof(*fhdr))) { + DEBUGP("message is too short.\n"); + goto ret_orig; + } + + clone->h.raw = clone->data + fhoff; + hdr = clone->nh.ipv6h; + fhdr = (struct frag_hdr *)clone->h.raw; + + if (!(fhdr->frag_off & htons(0xFFF9))) { + DEBUGP("Invalid fragment offset\n"); + /* It is not a fragmented frame */ + goto ret_orig; + } + + if (atomic_read(&ip6_ct_frag_mem) > sysctl_ip6_ct_frag_high_thresh) + ip6_ct_frag_evictor(); + + if ((fq = fq_find(fhdr->identification, &hdr->saddr, &hdr->daddr)) == NULL) { + DEBUGP("Can't find and can't create new queue\n"); + goto ret_orig; + } + + spin_lock(&fq->lock); + + if (ip6_ct_frag_queue(fq, clone, fhdr, nhoff) < 0) { + spin_unlock(&fq->lock); + DEBUGP("Can't insert skb to queue\n"); + fq_put(fq); + goto ret_orig; + } + + if (fq->last_in == (FIRST_IN|LAST_IN) && + fq->meat == fq->len) { + ret_skb = ip6_ct_frag_reasm(fq, dev); + + if (ret_skb == NULL) + DEBUGP("Can't reassemble fragmented packets\n"); + } + spin_unlock(&fq->lock); + + fq_put(fq); + return ret_skb; + +ret_orig: + kfree_skb(clone); + return skb; +} + +int ip6_ct_output_frags(struct sk_buff *skb, struct nf_info *info) +{ + struct sk_buff *s, *s2; + struct nf_info *copy_info; + + for (s = IP6CT_FRAG6_CB(skb)->orig; s;) { + if (skb->nfct) + nf_conntrack_get(skb->nfct); + s->nfct = skb->nfct; + s->nfcache = skb->nfcache; + + /* + * nf_reinject() frees copy_info, + * so I have to copy it every time. (T-T + */ + copy_info = kmalloc(sizeof(*copy_info), GFP_ATOMIC); + if (copy_info == NULL) { + DEBUGP("Can't kmalloc() for nf_info\n"); + return -1; + } + + copy_info->pf = info->pf; + copy_info->hook = info->hook; + copy_info->indev = info->indev; + copy_info->outdev = info->outdev; + copy_info->okfn = info->okfn; + copy_info->elem = info->elem; + + /* + * nf_reinject() put the module "ip6_conntrack". + */ + if (!try_module_get(info->elem->owner)) { + DEBUGP("Can't get module.\n"); + kfree_skb(s); + continue; + } + + if (copy_info->indev) + dev_hold(copy_info->indev); + if (copy_info->outdev) + dev_hold(copy_info->outdev); + + s2 = s->next; + nf_reinject(s, copy_info, NF_ACCEPT); + s = s2; + } + + kfree_skb(skb); + + return 0; +} + +int ip6_ct_kfree_frags(struct sk_buff *skb) +{ + struct sk_buff *s, *s2; + + for (s = IP6CT_FRAG6_CB(skb)->orig; s; s = s2) { + + s2 = s->next; + kfree_skb(s); + } + + kfree_skb(skb); + + return 0; +} + +#ifdef CONFIG_SYSCTL + +#define IP6CT_HIGH_THRESH_NAME "ip6ct_frags_high_thresh" +#define IP6CT_LOW_THRESH_NAME "ip6ct_frags_low_thresh" +#define IP6CT_TIMEOUT_NAME "ip6ct_frags_timeout" + +static struct ctl_table_header *ip6_ct_frags_sysctl_header; + +static ctl_table ip6_ct_frags_table[] = { + { + .ctl_name = IP6CT_FRAGS_HIGH_THRESH, + .procname = IP6CT_HIGH_THRESH_NAME, + .data = &sysctl_ip6_ct_frag_high_thresh, + .maxlen = sizeof(sysctl_ip6_ct_frag_high_thresh), + .mode = 0644, + .proc_handler = proc_dointvec + }, + { + .ctl_name = IP6CT_FRAGS_LOW_THRESH, + .procname = IP6CT_LOW_THRESH_NAME, + .data = &sysctl_ip6_ct_frag_low_thresh, + .maxlen = sizeof(sysctl_ip6_ct_frag_high_thresh), + .mode = 0644, + .proc_handler = proc_dointvec + }, + { + .ctl_name = IP6CT_FRAGS_TIMEOUT, + .procname = IP6CT_TIMEOUT_NAME, + .data = &sysctl_ip6_ct_frag_time, + .maxlen = sizeof(sysctl_ip6_ct_frag_time), + .mode = 0644, + .proc_handler = proc_dointvec + }, + { .ctl_name = 0 } +}; + +static ctl_table ip6_ct_frags_dir_table[] = { + { + .ctl_name = NET_IPV6, + .procname = "ipv6", NULL, + .mode = 0555, + .child = ip6_ct_frags_table + }, + { .ctl_name = 0 } +}; + +static ctl_table ip6_ct_frags_root_table[] = { + { + .ctl_name = CTL_NET, + .procname = "net", + .mode = 0555, + .child = ip6_ct_frags_dir_table + }, + { .ctl_name = 0 } +}; + +#endif /*CONFIG_SYSCTL*/ + +int __init ip6_ct_frags_init(void) +{ +#ifdef CONFIG_SYSCTL + ip6_ct_frags_sysctl_header = register_sysctl_table(ip6_ct_frags_root_table, 0); + + if (ip6_ct_frags_sysctl_header == NULL) { + printk("ip6_ct_frags_init: Can't register sysctl tables.\n"); + return -ENOMEM; + } +#endif + + ip6_ct_frag_hash_rnd = (u32) ((num_physpages ^ (num_physpages>>7)) ^ + (jiffies ^ (jiffies >> 6))); + + init_timer(&ip6_ct_frag_secret_timer); + ip6_ct_frag_secret_timer.function = ip6_ct_frag_secret_rebuild; + ip6_ct_frag_secret_timer.expires = jiffies + sysctl_ip6_ct_frag_secret_interval; + add_timer(&ip6_ct_frag_secret_timer); + + return 0; +} + +void ip6_ct_frags_cleanup(void) +{ + del_timer(&ip6_ct_frag_secret_timer); +#ifdef CONFIG_SYSCTL + unregister_sysctl_table(ip6_ct_frags_sysctl_header); +#endif + sysctl_ip6_ct_frag_low_thresh = 0; + ip6_ct_frag_evictor(); +} Index: linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_standalone.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/net/ipv6/netfilter/ip6_conntrack_standalone.c 2004-08-05 16:14:54.881144202 +0200 @@ -0,0 +1,502 @@ +/* + * IPv6 Connection Tracking + * Linux INET6 implementation + * + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: net/ipv4/netfilter/ip_conntrack_standalone.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +/* This file contains all the functions required for the standalone + ip6_conntrack module. + + These are not required by the compatibility layer. +*/ + +/* (c) 1999 Paul `Rusty' Russell. Licenced under the GNU General + Public Licence. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip6_conntrack_lock) +#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip6_conntrack_lock) + +#include +#include +#include +#include +#include +#include + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +MODULE_LICENSE("GPL"); + +static int kill_proto(const struct ip6_conntrack *i, void *data) +{ + return (i->tuplehash[IP6_CT_DIR_ORIGINAL].tuple.dst.protonum == + *((u_int8_t *) data)); +} + +static unsigned int +print_tuple(char *buffer, const struct ip6_conntrack_tuple *tuple, + struct ip6_conntrack_protocol *proto) +{ + int len; + + len = sprintf(buffer, "src=%x:%x:%x:%x:%x:%x:%x:%x dst=%x:%x:%x:%x:%x:%x:%x:%x ", + NIP6(tuple->src.ip), NIP6(tuple->dst.ip)); + + len += proto->print_tuple(buffer + len, tuple); + + return len; +} + +/* FIXME: Don't print source proto part. --RR */ +static unsigned int +print_expect(char *buffer, const struct ip6_conntrack_expect *expect) +{ + unsigned int len; + + if (expect->expectant->helper->timeout) + len = sprintf(buffer, "EXPECTING: %lu ", + timer_pending(&expect->timeout) + ? (expect->timeout.expires - jiffies)/HZ : 0); + else + len = sprintf(buffer, "EXPECTING: - "); + len += sprintf(buffer + len, "use=%u proto=%u ", + atomic_read(&expect->use), expect->tuple.dst.protonum); + len += print_tuple(buffer + len, &expect->tuple, + __ip6_ct_find_proto(expect->tuple.dst.protonum)); + len += sprintf(buffer + len, "\n"); + return len; +} + +static unsigned int +print_conntrack(char *buffer, struct ip6_conntrack *conntrack) +{ + unsigned int len; + struct ip6_conntrack_protocol *proto + = __ip6_ct_find_proto(conntrack->tuplehash[IP6_CT_DIR_ORIGINAL] + .tuple.dst.protonum); + + len = sprintf(buffer, "%-8s %u %lu ", + proto->name, + conntrack->tuplehash[IP6_CT_DIR_ORIGINAL] + .tuple.dst.protonum, + timer_pending(&conntrack->timeout) + ? (conntrack->timeout.expires - jiffies)/HZ : 0); + + len += proto->print_conntrack(buffer + len, conntrack); + len += print_tuple(buffer + len, + &conntrack->tuplehash[IP6_CT_DIR_ORIGINAL].tuple, + proto); + if (!(test_bit(IP6S_SEEN_REPLY_BIT, &conntrack->status))) + len += sprintf(buffer + len, "[UNREPLIED] "); + len += print_tuple(buffer + len, + &conntrack->tuplehash[IP6_CT_DIR_REPLY].tuple, + proto); + if (test_bit(IP6S_ASSURED_BIT, &conntrack->status)) + len += sprintf(buffer + len, "[ASSURED] "); + len += sprintf(buffer + len, "use=%u ", + atomic_read(&conntrack->ct_general.use)); + len += sprintf(buffer + len, "\n"); + + return len; +} + +/* Returns true when finished. */ +static inline int +conntrack_iterate(const struct ip6_conntrack_tuple_hash *hash, + char *buffer, off_t offset, off_t *upto, + unsigned int *len, unsigned int maxlen) +{ + unsigned int newlen; + IP6_NF_ASSERT(hash->ctrack); + + MUST_BE_READ_LOCKED(&ip6_conntrack_lock); + + /* Only count originals */ + if (DIRECTION(hash)) + return 0; + + if ((*upto)++ < offset) + return 0; + + newlen = print_conntrack(buffer + *len, hash->ctrack); + if (*len + newlen > maxlen) + return 1; + else *len += newlen; + + return 0; +} + +static int +list_conntracks(char *buffer, char **start, off_t offset, int length) +{ + unsigned int i; + unsigned int len = 0; + off_t upto = 0; + struct list_head *e; + + READ_LOCK(&ip6_conntrack_lock); + /* Traverse hash; print originals then reply. */ + for (i = 0; i < ip6_conntrack_htable_size; i++) { + if (LIST_FIND(&ip6_conntrack_hash[i], conntrack_iterate, + struct ip6_conntrack_tuple_hash *, + buffer, offset, &upto, &len, length)) + goto finished; + } + + /* Now iterate through expecteds. */ + for (e = ip6_conntrack_expect_list.next; + e != &ip6_conntrack_expect_list; e = e->next) { + unsigned int last_len; + struct ip6_conntrack_expect *expect + = (struct ip6_conntrack_expect *)e; + if (upto++ < offset) continue; + + last_len = len; + len += print_expect(buffer + len, expect); + if (len > length) { + len = last_len; + goto finished; + } + } + + finished: + READ_UNLOCK(&ip6_conntrack_lock); + + /* `start' hack - see fs/proc/generic.c line ~165 */ + *start = (char *)((unsigned int)upto - offset); + return len; +} + +static unsigned int ip6_confirm(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)); +static unsigned int ip6_conntrack_out(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)); +static unsigned int ip6_conntrack_reasm(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)); +static unsigned int ip6_conntrack_local(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)); + +/* Connection tracking may drop packets, but never alters them, so + make it the first hook. */ +static struct nf_hook_ops ip6_conntrack_in_ops = { + /* Don't forget to change .hook to "ip6_conntrack_input". - zak */ + .hook = ip6_conntrack_reasm, + .owner = THIS_MODULE, + .pf = PF_INET6, + .hooknum = NF_IP6_PRE_ROUTING, + .priority = NF_IP6_PRI_CONNTRACK, +}; + +static struct nf_hook_ops ip6_conntrack_local_out_ops = { + .hook = ip6_conntrack_local, + .owner = THIS_MODULE, + .pf = PF_INET6, + .hooknum = NF_IP6_LOCAL_OUT, + .priority = NF_IP6_PRI_CONNTRACK, +}; + +/* Refragmenter; last chance. */ +static struct nf_hook_ops ip6_conntrack_out_ops = { + .hook = ip6_conntrack_out, + .owner = THIS_MODULE, + .pf = PF_INET6, + .hooknum = NF_IP6_POST_ROUTING, + .priority = NF_IP6_PRI_LAST, +}; + +static struct nf_hook_ops ip6_conntrack_local_in_ops = { + .hook = ip6_confirm, + .owner = THIS_MODULE, + .pf = PF_INET6, + .hooknum = NF_IP6_LOCAL_IN, + .priority = NF_IP6_PRI_LAST-1, +}; + +static unsigned int ip6_confirm(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + int ret; + + ret = ip6_conntrack_confirm(*pskb); + + return ret; +} + +static unsigned int ip6_conntrack_out(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + + if (ip6_conntrack_confirm(*pskb) != NF_ACCEPT) + return NF_DROP; + + return NF_ACCEPT; +} + +static unsigned int ip6_conntrack_reasm(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + struct sk_buff *skb = *pskb; + struct sk_buff **rsmd_pskb = &skb; + int fragd = 0; + int ret; + + skb->nfcache |= NFC_UNKNOWN; + + /* + * Previously seen (loopback)? Ignore. Do this before + * fragment check. + */ + if (skb->nfct) { + DEBUGP("previously seen\n"); + return NF_ACCEPT; + } + + skb = ip6_ct_gather_frags(skb); + + /* queued */ + if (skb == NULL) + return NF_STOLEN; + + if (skb != (*pskb)) + fragd = 1; + + ret = ip6_conntrack_in(hooknum, rsmd_pskb, in, out, okfn); + + if (!fragd) + return ret; + + if (ret == NF_DROP) { + ip6_ct_kfree_frags(skb); + }else{ + struct nf_info info; + + info.pf = PF_INET6; + info.hook = hooknum; + info.indev = in; + info.outdev = out; + info.okfn = okfn; + switch (hooknum) { + case NF_IP6_PRE_ROUTING: + info.elem = &ip6_conntrack_in_ops; + break; + case NF_IP6_LOCAL_OUT: + info.elem = &ip6_conntrack_local_out_ops; + break; + } + + if (ip6_ct_output_frags(skb, &info) <0) + DEBUGP("Can't output fragments\n"); + + } + + return NF_STOLEN; +} + +static unsigned int ip6_conntrack_local(unsigned int hooknum, + struct sk_buff **pskb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + unsigned int ret; + + /* root is playing with raw sockets. */ + if ((*pskb)->len < sizeof(struct ipv6hdr)) { + if (net_ratelimit()) + printk("ip6t_hook: IPv6 header is too short.\n"); + return NF_ACCEPT; + } + + ret = ip6_conntrack_reasm(hooknum, pskb, in, out, okfn); + + return ret; +} + +static int init_or_cleanup(int init) +{ + struct proc_dir_entry *proc; + int ret = 0; + + if (!init) goto cleanup; + + ret = ip6_ct_frags_init(); + if (ret < 0) + goto cleanup_reasm; + + ret = ip6_conntrack_init(); + if (ret < 0) + goto cleanup_nothing; + + proc = proc_net_create("ip6_conntrack",0,list_conntracks); + if (!proc) goto cleanup_init; + proc->owner = THIS_MODULE; + + ret = nf_register_hook(&ip6_conntrack_in_ops); + if (ret < 0) { + printk("ip6_conntrack: can't register pre-routing hook.\n"); + goto cleanup_proc; + } + ret = nf_register_hook(&ip6_conntrack_local_out_ops); + if (ret < 0) { + printk("ip6_conntrack: can't register local out hook.\n"); + goto cleanup_inops; + } + ret = nf_register_hook(&ip6_conntrack_out_ops); + if (ret < 0) { + printk("ip6_conntrack: can't register post-routing hook.\n"); + goto cleanup_inandlocalops; + } + ret = nf_register_hook(&ip6_conntrack_local_in_ops); + if (ret < 0) { + printk("ip6_conntrack: can't register local in hook.\n"); + goto cleanup_inoutandlocalops; + } + + return ret; + + cleanup: + nf_unregister_hook(&ip6_conntrack_local_in_ops); + cleanup_inoutandlocalops: + nf_unregister_hook(&ip6_conntrack_out_ops); + cleanup_inandlocalops: + nf_unregister_hook(&ip6_conntrack_local_out_ops); + cleanup_inops: + nf_unregister_hook(&ip6_conntrack_in_ops); + cleanup_proc: + proc_net_remove("ip6_conntrack"); + cleanup_init: + ip6_conntrack_cleanup(); + cleanup_reasm: + ip6_ct_frags_cleanup(); + cleanup_nothing: + return ret; +} + +/* FIXME: Allow NULL functions and sub in pointers to generic for + them. --RR */ +int ip6_conntrack_protocol_register(struct ip6_conntrack_protocol *proto) +{ + int ret = 0; + struct list_head *i; + + WRITE_LOCK(&ip6_conntrack_lock); + for (i = ip6_protocol_list.next; i != &ip6_protocol_list; i = i->next) { + if (((struct ip6_conntrack_protocol *)i)->proto + == proto->proto) { + ret = -EBUSY; + goto out; + } + } + + list_prepend(&ip6_protocol_list, proto); + + out: + WRITE_UNLOCK(&ip6_conntrack_lock); + return ret; +} + +void ip6_conntrack_protocol_unregister(struct ip6_conntrack_protocol *proto) +{ + WRITE_LOCK(&ip6_conntrack_lock); + + /* ip_ct_find_proto() returns proto_generic in case there is no protocol + * helper. So this should be enough - HW */ + LIST_DELETE(&ip6_protocol_list, proto); + WRITE_UNLOCK(&ip6_conntrack_lock); + + /* Somebody could be still looking at the proto in bh. */ + synchronize_net(); + + /* Remove all contrack entries for this protocol */ + ip6_ct_selective_cleanup(kill_proto, &proto->proto); +} + +static int __init init(void) +{ + return init_or_cleanup(1); +} + +static void __exit fini(void) +{ + init_or_cleanup(0); +} + +module_init(init); +module_exit(fini); + +/* Some modules need us, but don't depend directly on any symbol. + They should call this. */ +void need_ip6_conntrack(void) +{ +} + +EXPORT_SYMBOL(ip6_conntrack_protocol_register); +EXPORT_SYMBOL(ip6_conntrack_protocol_unregister); +EXPORT_SYMBOL(ip6_invert_tuplepr); +EXPORT_SYMBOL(ip6_conntrack_alter_reply); +EXPORT_SYMBOL(ip6_conntrack_destroyed); +EXPORT_SYMBOL(ip6_conntrack_get); +EXPORT_SYMBOL(need_ip6_conntrack); +EXPORT_SYMBOL(ip6_conntrack_helper_register); +EXPORT_SYMBOL(ip6_conntrack_helper_unregister); +EXPORT_SYMBOL(ip6_ct_selective_cleanup); +EXPORT_SYMBOL(ip6_ct_refresh); +EXPORT_SYMBOL(ip6_ct_find_proto); +EXPORT_SYMBOL(__ip6_ct_find_proto); +EXPORT_SYMBOL(ip6_ct_find_helper); +EXPORT_SYMBOL(ip6_conntrack_expect_related); +EXPORT_SYMBOL(ip6_conntrack_unexpect_related); +EXPORT_SYMBOL_GPL(ip6_conntrack_expect_find_get); +EXPORT_SYMBOL_GPL(ip6_conntrack_expect_put); +EXPORT_SYMBOL(ip6_conntrack_tuple_taken); +EXPORT_SYMBOL(ip6_conntrack_htable_size); +EXPORT_SYMBOL(ip6_conntrack_expect_list); +EXPORT_SYMBOL(ip6_conntrack_lock); +EXPORT_SYMBOL_GPL(ip6_conntrack_find_get); +EXPORT_SYMBOL_GPL(ip6_conntrack_put); Index: linux-2.6.7/net/ipv6/netfilter/ip6t_state.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.7/net/ipv6/netfilter/ip6t_state.c 2004-08-05 16:14:54.884143911 +0200 @@ -0,0 +1,80 @@ +/* + * Matching connection tracking information + * Linux INET6 implementation + * + * Copyright (C)2003 USAGI/WIDE Project + * + * Authors: + * Yasuyuki Kozakai + * + * Based on: net/ipv4/netfilter/ip6t_state.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +/* Kernel module to match connection tracking information. + * GPL (C) 1999 Rusty Russell (rusty@rustcorp.com.au). + */ +#include +#include +#include +#include +#include + +static int +match(const struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + const void *matchinfo, + int offset, + const void *hdr, + uint16_t datalen, + int *hotdrop) +{ + const struct ip6t_state_info *sinfo = matchinfo; + enum ip6_conntrack_info ctinfo; + unsigned int statebit; + + if (!ip6_conntrack_get((struct sk_buff *)skb, &ctinfo)) + statebit = IP6T_STATE_INVALID; + else + statebit = IP6T_STATE_BIT(ctinfo); + + return (sinfo->statemask & statebit); +} + +static int check(const char *tablename, + const struct ip6t_ip6 *ip, + void *matchinfo, + unsigned int matchsize, + unsigned int hook_mask) +{ + if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_state_info))) + return 0; + + return 1; +} + +static struct ip6t_match state_match = { + .name = "state", + .match = &match, + .checkentry = &check, + .me = THIS_MODULE, +}; + +static int __init init(void) +{ + need_ip6_conntrack(); + return ip6t_register_match(&state_match); +} + +static void __exit fini(void) +{ + ip6t_unregister_match(&state_match); +} + +module_init(init); +module_exit(fini); +MODULE_LICENSE("GPL");