All of lore.kernel.org
 help / color / mirror / Atom feed
* [Patch]: IPv6 Connection Tracking
@ 2003-09-25  5:21 Yasuyuki Kozakai
  2003-09-25  9:28 ` Harald Welte
  2003-09-25 18:48 ` Andras Kis-Szabo
  0 siblings, 2 replies; 15+ messages in thread
From: Yasuyuki Kozakai @ 2003-09-25  5:21 UTC (permalink / raw)
  To: rusty, laforge; +Cc: netfilter-devel, netdev, usagi-core

[-- Attachment #1: Type: Text/Plain, Size: 1987 bytes --]

I failed to send this message, so I resend.
I'm sorry about duplicated messages if you received.

-----------------------
Hello,
I'm writing to you for the first time.
I'm a member of USAGI Project.

A few months ago, I told in Netfilter developer mailinglist that I was coding
IPv6 Connection Tracking in Linux 2.5, 2.6.
I completed that, so I send the patch.

The summaries are below.

	- based on IPv4 Connection Tracking
		* TCP, UDP, ICMPv6 Echo, FTP, state module,
		* multiple expectations can be used.
	- Fragmented packets is handled as below, so "Path MTU Discovery"
	  isn't disturbed, and can be reduced copying skb.
		1. clone skb, and original skb isn't touched.
		2. cloned skbs are reassembled into one skb. the codes for
		   this processing are based on net/ipv6/reassembly.c
		3. reassembled packet is tracked.
		4. set nf_ct_info of reassembled skb to original skbs.
		5. original skbs are passed to next hook function
		   at PREROUTING. (none functions in present)
		6. reassembled packet is discarded.
	- The offset at protocol header is passed to the various functions
	  in order to parse Extension Headers at once. 
	- NAT is not supported
	- Multicast packets aren't handled.
	- Resent patches in patch-o-matic/submitted/* are followed up.

I tested by using regular packets:
	- TCP, UDP, ICMPv6 Echo, ICMPv6 Error, FTP, Fragmented UDP packets

and illegal packets:
	- TCP, FTP, ICMPV6 Error with incorrect checksum
	- TCP, FTP with incorrect sequence number
	- irregular Fragmented UDP packets
	- unknown Extension Header
	- and so on.

The codes handling multiple expectations aren't be tested, because I don't know
such a application with IPv6. But they are same as IPv4 conntrack except names
of functions, variables.

Regards

----------------------------------------
Yasuyuki KOZAKAI

Communication Platform Laboratory,
Corporate Research & Development Center,
Toshiba Corporation

yasuyuki.kozakai@toshiba.co.jp
----------------------------------------

[-- Attachment #2: conntrack6.patch --]
[-- Type: Text/Plain, Size: 156436 bytes --]

diff -Nur linux-2.6.0-test5/include/linux/netfilter.h linux-2.6.0-test5-ct6/include/linux/netfilter.h
--- linux-2.6.0-test5/include/linux/netfilter.h	2003-09-09 04:50:06.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter.h	2003-09-24 11:35:33.000000000 +0900
@@ -155,6 +155,10 @@
 
 extern void (*ip_ct_attach)(struct sk_buff *, struct nf_ct_info *);
 
+#ifdef CONFIG_IPV6
+extern void (*ip6_ct_attach)(struct sk_buff *, struct nf_ct_info *);
+#endif
+
 #ifdef CONFIG_NETFILTER_DEBUG
 extern void nf_dump_skb(int pf, struct sk_buff *skb);
 #endif
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/config.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_tuple.h>
+#include <linux/bitops.h>
+#include <linux/compiler.h>
+#include <asm/atomic.h>
+
+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 <linux/netfilter_ipv6/ip6_conntrack_tcp.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_icmpv6.h>
+
+/* 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 <linux/netfilter_ipv6/ip6_conntrack_ftp.h>
+
+/* 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 <linux/types.h>
+#include <linux/skbuff.h>
+
+#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 <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_core.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_core.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_core.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_core.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter.h>
+#include <linux/netfilter_ipv4/lockhelp.h>
+
+/* 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 */
+
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter_ipv4/lockhelp.h>
+
+/* 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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_helper.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_helper.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_helper.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_helper.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter_ipv6/ip6_conntrack.h>
+
+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*/
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <asm/atomic.h>
+
+struct ip6_ct_icmpv6
+{
+	/* Optimization: when number in == number out, forget immediately. */
+	atomic_t count;
+};
+#endif /* _IP6_CONNTRACK_ICMPv6_H */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/skbuff.h>
+
+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*/
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter.h>
+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 */
+
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/in6.h>
+#include <linux/kernel.h>
+#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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6t_state.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6t_state.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6t_state.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6t_state.h	2003-09-24 11:36:48.000000000 +0900
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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*/
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6.h	2003-09-09 04:50:22.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6.h	2003-09-24 11:36:04.000000000 +0900
@@ -53,6 +53,7 @@
 #define NF_IP6_POST_ROUTING	4
 #define NF_IP6_NUMHOOKS		5
 
+#define SO_ORIGINAL_DST 80
 
 enum nf_ip6_hook_priorities {
 	NF_IP6_PRI_FIRST = INT_MIN,
diff -Nur linux-2.6.0-test5/net/core/netfilter.c linux-2.6.0-test5-ct6/net/core/netfilter.c
--- linux-2.6.0-test5/net/core/netfilter.c	2003-09-09 04:50:03.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/core/netfilter.c	2003-09-24 11:34:52.000000000 +0900
@@ -749,6 +749,9 @@
    and hence manufactured ICMP or RST packets will not be associated
    with it. */
 void (*ip_ct_attach)(struct sk_buff *, struct nf_ct_info *);
+#ifdef CONFIG_IPV6
+void (*ip6_ct_attach)(struct sk_buff *, struct nf_ct_info *);
+#endif
 
 void __init netfilter_init(void)
 {
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/Kconfig linux-2.6.0-test5-ct6/net/ipv6/netfilter/Kconfig
--- linux-2.6.0-test5/net/ipv6/netfilter/Kconfig	2003-09-09 04:50:22.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/Kconfig	2003-09-24 11:32:03.000000000 +0900
@@ -5,6 +5,16 @@
 menu "IPv6: Netfilter Configuration"
 	depends on INET && IPV6!=n && 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
+	  <file:Documentation/modules.txt>.  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
@@ -173,6 +183,31 @@
 	  If you want to compile it as a module, say M here and read
 	  Documentation/modules.txt.  If unsure, say `N'.
 
+config IP6_NF_CONNTRACK
+	tristate "Connection tracking (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
+	  <file:Documentation/modules.txt>.  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
+	  <file:Documentation/modules.txt>.  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
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/Makefile linux-2.6.0-test5-ct6/net/ipv6/netfilter/Makefile
--- linux-2.6.0-test5/net/ipv6/netfilter/Makefile	2003-09-09 04:50:07.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/Makefile	2003-09-24 11:31:27.000000000 +0900
@@ -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
@@ -22,3 +34,4 @@
 obj-$(CONFIG_IP6_NF_QUEUE) += ip6_queue.o
 obj-$(CONFIG_IP6_NF_TARGET_LOG) += ip6t_LOG.o
 obj-$(CONFIG_IP6_NF_MATCH_HL) += ip6t_hl.o
+obj-$(CONFIG_IP6_NF_MATCH_HL) += ip6t_state.o
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_core.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_core.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_core.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_core.c	2003-09-24 13:01:42.000000000 +0900
@@ -0,0 +1,1614 @@
+/*
+ * IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <laforge@gnumonks.org>
+ *     - new API and handling of conntrack/nat helpers
+ *     - now capable of multiple expectations for one master
+ * 16 Jul 2002: Harald Welte <laforge@gnumonks.org>
+ *     - add usage/reference counts to ip_conntrack_expect
+ *     - export ip_conntrack[_expect]_{find_get,put} functions
+ * */
+
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/icmpv6.h>
+#include <linux/ipv6.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/vmalloc.h>
+#include <net/checksum.h>
+#include <linux/stddef.h>
+#include <linux/sysctl.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/jhash.h>
+#include <net/ipv6.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+
+/* 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 <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_core.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#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;
+	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);
+			ip6_conntrack_put(ct->master->expectant);
+		}
+		kfree(ct->master);
+	}
+	WRITE_UNLOCK(&ip6_conntrack_lock);
+
+	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("SO_ORIGINAL_DST: Not a TCP socket\n");
+		return -ENOPROTOOPT;
+	}
+
+	if ((unsigned int) *len < sizeof(struct sockaddr_in)) {
+		DEBUGP("SO_ORIGINAL_DST: len %u not %u\n",
+		       *len, sizeof(struct sockaddr_in));
+		return -EINVAL;
+	}
+
+	h = 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("SO_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("SO_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	= SO_ORIGINAL_DST,
+	.get_optmax	= SO_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;
+}
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_ftp.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_ftp.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_ftp.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_ftp.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,555 @@
+/*
+ * FTP extension for IPv6 connection tracking.
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/config.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/ipv6.h>
+#include <linux/ctype.h>
+#include <net/checksum.h>
+#include <net/tcp.h>
+#include <net/ipv6.h>
+#include <linux/kernel.h>
+
+#include <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_ftp.h>
+
+/* 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 revised to inet_pton() in glibc-2.2.4-19.3.src.rpm
+   of RedHat 7.2 - kozakai */
+
+#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;
+	size_t v4len;
+
+        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++) {
+		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);
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_generic.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_generic.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_generic.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_generic.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,82 @@
+/*
+ * IPv6 generic protocol extension for IPv6 connection tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+
+#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 };
+
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c	2003-09-24 13:06:58.000000000 +0900
@@ -0,0 +1,132 @@
+/*
+ * ICMPv6 extension for IPv6 connection tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/in.h>
+#include <linux/icmpv6.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+
+#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,
+	};
+
+	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,
+	};
+
+	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 };
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,273 @@
+/*
+ * TCP extension for IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/module.h>
+#include <linux/in.h>
+#include <linux/ipv6.h>
+#include <linux/tcp.h>
+#include <linux/string.h>
+
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+#include <linux/netfilter_ipv4/lockhelp.h>
+
+#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;
+
+	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 };
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_udp.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_udp.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_udp.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_udp.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,95 @@
+/*
+ * UDP extension for IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/udp.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+
+#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 };
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_reasm.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_reasm.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_reasm.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_reasm.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,989 @@
+/*
+ * IPv6 fragment reassembly for connection tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/jiffies.h>
+#include <linux/net.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/in6.h>
+#include <linux/ipv6.h>
+#include <linux/icmpv6.h>
+#include <linux/random.h>
+#include <linux/jhash.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/transp_v6.h>
+#include <net/rawv6.h>
+#include <net/ndisc.h>
+#include <net/addrconf.h>
+#include <linux/sysctl.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#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; i<skb_shinfo(head)->nr_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; s = s2) {
+		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);
+	}
+
+	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();
+}
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_standalone.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_standalone.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_standalone.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_standalone.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,502 @@
+/*
+ * IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/ipv6.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/version.h>
+#include <net/checksum.h>
+
+#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 <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_core.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_reasm.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#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);
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6t_state.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6t_state.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6t_state.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6t_state.c	2003-09-24 11:30:59.000000000 +0900
@@ -0,0 +1,80 @@
+/*
+ * Matching connection tracking information
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_state.h>
+
+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");
diff -Nur linux-2.6.0-test5/net/netsyms.c linux-2.6.0-test5-ct6/net/netsyms.c
--- linux-2.6.0-test5/net/netsyms.c	2003-09-09 04:50:02.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/netsyms.c	2003-09-24 11:34:11.000000000 +0900
@@ -606,6 +606,9 @@
 EXPORT_SYMBOL(nf_setsockopt);
 EXPORT_SYMBOL(nf_getsockopt);
 EXPORT_SYMBOL(ip_ct_attach);
+#if defined(CONFIG_IPV6)
+EXPORT_SYMBOL(ip6_ct_attach);
+#endif
 #ifdef CONFIG_INET
 #include <linux/netfilter_ipv4.h>
 EXPORT_SYMBOL(ip_route_me_harder);

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Patch]: IPv6 Connection Tracking
  2003-09-25  5:21 [Patch]: IPv6 Connection Tracking Yasuyuki Kozakai
@ 2003-09-25  9:28 ` Harald Welte
  2003-09-25 10:50   ` [netfilter-core] " Jozsef Kadlecsik
                     ` (3 more replies)
  2003-09-25 18:48 ` Andras Kis-Szabo
  1 sibling, 4 replies; 15+ messages in thread
From: Harald Welte @ 2003-09-25  9:28 UTC (permalink / raw)
  To: Yasuyuki Kozakai; +Cc: coreteam, netfilter-devel, netdev, usagi-core

[-- Attachment #1: Type: text/plain, Size: 3453 bytes --]

On Thu, Sep 25, 2003 at 02:21:10PM +0900, Yasuyuki Kozakai wrote:
> I failed to send this message, so I resend.

I perfectly received the message you sent yesterday. 

> Hello,
> I'm writing to you for the first time.
> I'm a member of USAGI Project.

Yes, Yoshifuji Hideaki already informed me at OLS that there is some
effort on IPv6 connection tracking within the USAGI project.

> I completed that, so I send the patch.

Thank you very much.

First of all, it is great that somebody is working on this subject, and
that there is now working code.

However, we are now facing the problem on how we want to proceed.

Your ipv6 connection tracking is what I would like to call a
'copy+paste' port.  (please don't misunderstand me, this does _not_ mean
your work is not appreciated!)

We've already had the same kind of copy+paste port from iptables to
ip6tables (and now arptables).  Maintaining the same codebase for
different l3 protocols introduces the usual problems of code
duplication:
- large parts of the object code doing the same job
- bug fixes get applied only to one tree, not to the other
- will lead to further code replication (ipt_mac / ip6t_mac, userspace
  tools, ...)

So we've had some very painful experience with this kind of 'port'
throughout the last two years.  This is the main reason why I am now
working on a replacement for all those copy+paste ports in order to have
one unified codebase again (pkttables).

Thus, for conntrack we are now facing the same problem :(

On the one hand it is tempting to include your ip6_conntrack into the
2.6.x kernel series 'as is'.  On the other hand, this would really
increase the maintainance work needed.

On the other hand, I'd much rather see some work done on a layer 3
independent connection tracking core.  By this I mean splitting the
current ip_contnrack into nf_conntrack and nf_conntrack_ipv4 - a generic
part and a ipv4 specific part.  Then it is very easy to add the ipv6
specific part to it - and we have a even more powerful and clean
infrastructure [that would even allow us to have one end of the
connection reside in ipv4 address space, the other one in ipv6 and do
some stateful ipv4-to-ivp6 nat at a minimal cost].

Yasuyuki, what is your take on this?  Do you (or your company) regard
the work as 'finished' now, or are you interested on helping us to
implement a l3 independent connection tracking (and write the ipv6 part
of it)?

To summarize, I see the following options:

1) submit ip6_conntrack to the 2.6.x kernel
   We would then need somebody committe to maintaining it, assuring
   it keeps in sync with the work done on ip_conntrack.  I do not want
   to put the burden of ip_conntrack / ip6_conntrack synchronization on
   everybody who submits patches/bugfixes/...

2) keep ip6_conntrack in patch-o-matic and start work on a l3
   independent conntrack system.
   This would give more users to the code, since most advanced 
   netfilter users are using patch-o-matic anyway.

> Regards
> Yasuyuki KOZAKAI
-- 
- Harald Welte <laforge@netfilter.org>             http://www.netfilter.org/
============================================================================
  "Fragmentation is like classful addressing -- an interesting early
   architectural error that shows how much experimentation was going
   on while IP was being designed."                    -- Paul Vixie

[-- Attachment #2: Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [netfilter-core] Re: [Patch]: IPv6 Connection Tracking
  2003-09-25  9:28 ` Harald Welte
@ 2003-09-25 10:50   ` Jozsef Kadlecsik
  2003-09-25 10:51   ` David S. Miller
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 15+ messages in thread
From: Jozsef Kadlecsik @ 2003-09-25 10:50 UTC (permalink / raw)
  To: Harald Welte
  Cc: Yasuyuki Kozakai, coreteam, netfilter-devel, netdev, usagi-core

On Thu, 25 Sep 2003, Harald Welte wrote:

> However, we are now facing the problem on how we want to proceed.

I completely agree with Harald and does not like the idea of adding
(more) duplicated code to the system. There are already too much
duplication between IPv4/IPv6 at netfilter level, which must be sorted
out.

> 1) submit ip6_conntrack to the 2.6.x kernel
>    We would then need somebody committe to maintaining it, assuring
>    it keeps in sync with the work done on ip_conntrack.  I do not want
>    to put the burden of ip_conntrack / ip6_conntrack synchronization on
>    everybody who submits patches/bugfixes/...
>
> 2) keep ip6_conntrack in patch-o-matic and start work on a l3
>    independent conntrack system.
>    This would give more users to the code, since most advanced
>    netfilter users are using patch-o-matic anyway.

Yes, let's go with the latter solution.

Best regards,
Jozsef
-
E-mail  : kadlec@blackhole.kfki.hu, kadlec@sunserv.kfki.hu
PGP key : http://www.kfki.hu/~kadlec/pgp_public_key.txt
Address : KFKI Research Institute for Particle and Nuclear Physics
          H-1525 Budapest 114, POB. 49, Hungary

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Patch]: IPv6 Connection Tracking
  2003-09-25  9:28 ` Harald Welte
  2003-09-25 10:50   ` [netfilter-core] " Jozsef Kadlecsik
@ 2003-09-25 10:51   ` David S. Miller
  2003-09-25 13:10   ` Yasuyuki Kozakai
  2003-10-03 11:19   ` Yasuyuki Kozakai
  3 siblings, 0 replies; 15+ messages in thread
From: David S. Miller @ 2003-09-25 10:51 UTC (permalink / raw)
  To: Harald Welte
  Cc: yasuyuki.kozakai, coreteam, netfilter-devel, netdev, usagi-core

On Thu, 25 Sep 2003 11:28:06 +0200
Harald Welte <laforge@netfilter.org> wrote:

> On the other hand, I'd much rather see some work done on a layer 3
> independent connection tracking core.

Me too.

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Patch]: IPv6 Connection Tracking
  2003-09-25  9:28 ` Harald Welte
  2003-09-25 10:50   ` [netfilter-core] " Jozsef Kadlecsik
  2003-09-25 10:51   ` David S. Miller
@ 2003-09-25 13:10   ` Yasuyuki Kozakai
  2003-10-03 11:19   ` Yasuyuki Kozakai
  3 siblings, 0 replies; 15+ messages in thread
From: Yasuyuki Kozakai @ 2003-09-25 13:10 UTC (permalink / raw)
  To: laforge; +Cc: yasuyuki.kozakai, coreteam, netfilter-devel, netdev, usagi-core


Hi, Harald.

Thank you for your concern, and I understand what you say.

> To summarize, I see the following options:
> 
> 1) submit ip6_conntrack to the 2.6.x kernel
>    We would then need somebody committe to maintaining it, assuring
>    it keeps in sync with the work done on ip_conntrack.  I do not want
>    to put the burden of ip_conntrack / ip6_conntrack synchronization on
>    everybody who submits patches/bugfixes/...
> 
> 2) keep ip6_conntrack in patch-o-matic and start work on a l3
>    independent conntrack system.
>    This would give more users to the code, since most advanced 
>    netfilter users are using patch-o-matic anyway.
> 

I interested on a L3 independent connection tracking.
Could you show me about concrete loadmap and the scheme of nf_conntrack ?

----------------------------------------
Yasuyuki KOZAKAI

Communication Platform Laboratory,
Corporate Research & Development Center,
Toshiba Corporation

yasuyuki.kozakai@toshiba.co.jp
----------------------------------------

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Patch]: IPv6 Connection Tracking
  2003-09-25  5:21 [Patch]: IPv6 Connection Tracking Yasuyuki Kozakai
  2003-09-25  9:28 ` Harald Welte
@ 2003-09-25 18:48 ` Andras Kis-Szabo
  2003-09-25 18:57   ` Pekka Savola
  2003-09-26  3:54   ` Yasuyuki Kozakai
  1 sibling, 2 replies; 15+ messages in thread
From: Andras Kis-Szabo @ 2003-09-25 18:48 UTC (permalink / raw)
  To: Yasuyuki Kozakai; +Cc: Netfilter Devel, Netdev, usagi-core

Dear Yasuyuki,

I have some questions against the code.
The first question is about the extension headers.
I have used an own 'external header skipper' routine which was very
close the the kernel's one. So I would like to update the netfilter code
to use the kernel's function. For this, we have to export the
ipv6_skip_exthdr() function from net/ipv6/exthdrs.c . I have checked
your code, too. It looks very close to the kernel's code.
As I have noticed, the differences:
- handling of the fragments
  your code checks that the member of the extension are in the skb or
not since the common part checks only the basic extension header size.
After it your code linearizes the skb to cover the extension header.
So, the kernel does not check the size and does not linearize. After
these fixes the 2 codes will be similar.
Would not be better to export the kernel's function and use the
ipv6_skip_exthdr() in the netfilter codes?

My second commet is near this area. I have planned that an offset value
which points after the last extension header and a variable which
contain the last nexthdr value would be very helpful for the future -
but I was too lazy to do this work. With the connection tracking this
function (ipv6_skip_exthdr) will be called several time on the same
packet (in the main kernel, at every LOG, at every match, at every ct,
...) With USAGI we could - probably - find the space for this 2
variable. Do you have any recommendation?

Your FTP code uses EPSV and EPRT from rfc2428. What's about the FOOBAR
RFC (1639)? OK, it's a joke :)
Could we open an IPv4 data connection next to the IPv6 controll
connection?

Regards,

	kisza

-- 
    Andras Kis-Szabo       Security Development, Design and Audit
-------------------------/        Zorp, NetFilter and IPv6
 kisza@SecurityAudit.hu /------------------------------------------->

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Patch]: IPv6 Connection Tracking
  2003-09-25 18:48 ` Andras Kis-Szabo
@ 2003-09-25 18:57   ` Pekka Savola
  2003-09-25 19:07     ` Andras Kis-Szabo
  2003-09-29  8:42     ` Harald Welte
  2003-09-26  3:54   ` Yasuyuki Kozakai
  1 sibling, 2 replies; 15+ messages in thread
From: Pekka Savola @ 2003-09-25 18:57 UTC (permalink / raw)
  To: Andras Kis-Szabo; +Cc: Yasuyuki Kozakai, Netfilter Devel, Netdev, usagi-core

First, a meta-comment:

What I fear is that in the end, nothing gets done because having the goal
set to perfection.  If there is no energy to drive through the
L3-independent connecting tracking, the end result is that the user does
not have this feature (remember ip6tables REJECT target?  That must have
been sitting in netfilter for some 2+ years, and not having been
integrated in the mainline kernel and the users still do not have the
feature!).

So, my personal take is:
 - if a L3-independent conn tracking can be done *quickly*, fine,
 - if not, just merge the current one, start working on L3 independent 
conn tracking, and add it when available.

.. but I'm not the one who's answering the support emails, so in all 
fairness, I should be silent now..

Two questions/comments inline:

On 25 Sep 2003, Andras Kis-Szabo wrote:
[...]
> Your FTP code uses EPSV and EPRT from rfc2428. What's about the FOOBAR
> RFC (1639)? OK, it's a joke :)
> Could we open an IPv4 data connection next to the IPv6 controll
> connection?

What about LPRT and LPSV?

Btw, I would appreciate any comments regarding my draft documenting some 
IPv6-related Firewalling issues (some certainly come up when 
implementing): 
http://www.ietf.org/internet-drafts/draft-savola-v6ops-firewalling-01.txt

-- 
Pekka Savola                 "You each name yourselves king, yet the
Netcore Oy                    kingdom bleeds."
Systems. Networks. Security. -- George R.R. Martin: A Clash of Kings

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Patch]: IPv6 Connection Tracking
  2003-09-25 18:57   ` Pekka Savola
@ 2003-09-25 19:07     ` Andras Kis-Szabo
  2003-09-25 19:14       ` Pekka Savola
  2003-09-29  8:42     ` Harald Welte
  1 sibling, 1 reply; 15+ messages in thread
From: Andras Kis-Szabo @ 2003-09-25 19:07 UTC (permalink / raw)
  To: Pekka Savola; +Cc: Netfilter Devel, Netdev, usagi-core

Hi,

> What I fear is that in the end, nothing gets done because having the goal
> set to perfection.  If there is no energy to drive through the
> L3-independent connecting tracking, the end result is that the user does
> not have this feature (remember ip6tables REJECT target?  That must have
> been sitting in netfilter for some 2+ years, and not having been
> integrated in the mainline kernel and the users still do not have the
> feature!).
I have felt the same on Brad Chapman's port. That code has lost in time
:(

> > Your FTP code uses EPSV and EPRT from rfc2428. What's about the FOOBAR
> > RFC (1639)? OK, it's a joke :)
> > Could we open an IPv4 data connection next to the IPv6 controll
> > connection?
> What about LPRT and LPSV?
This is the rfc1639/foobar :)

Regards,

kisza

-- 
    Andras Kis-Szabo       Security Development, Design and Audit
-------------------------/        Zorp, NetFilter and IPv6
 kisza@SecurityAudit.hu /------------------------------------------->

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Patch]: IPv6 Connection Tracking
  2003-09-25 19:07     ` Andras Kis-Szabo
@ 2003-09-25 19:14       ` Pekka Savola
  0 siblings, 0 replies; 15+ messages in thread
From: Pekka Savola @ 2003-09-25 19:14 UTC (permalink / raw)
  To: Andras Kis-Szabo; +Cc: Netfilter Devel, Netdev, usagi-core

On 25 Sep 2003, Andras Kis-Szabo wrote:
[...]
> > > Your FTP code uses EPSV and EPRT from rfc2428. What's about the FOOBAR
> > > RFC (1639)? OK, it's a joke :)
> > > Could we open an IPv4 data connection next to the IPv6 controll
> > > connection?
> > What about LPRT and LPSV?
>
> This is the rfc1639/foobar :)

Too bad I've seen it implemented and used (AFAIR) ... so it definitely 
isn't a joke...

-- 
Pekka Savola                 "You each name yourselves king, yet the
Netcore Oy                    kingdom bleeds."
Systems. Networks. Security. -- George R.R. Martin: A Clash of Kings

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Patch]: IPv6 Connection Tracking
  2003-09-25 18:48 ` Andras Kis-Szabo
  2003-09-25 18:57   ` Pekka Savola
@ 2003-09-26  3:54   ` Yasuyuki Kozakai
  1 sibling, 0 replies; 15+ messages in thread
From: Yasuyuki Kozakai @ 2003-09-26  3:54 UTC (permalink / raw)
  To: kisza; +Cc: yasuyuki.kozakai, netfilter-devel, netdev, usagi-core


Hi, Andras,

From: Andras Kis-Szabo <kisza@securityaudit.hu>
Subject: Re: [Patch]: IPv6 Connection Tracking
Date: 25 Sep 2003 20:48:01 +0200

kisza> I have used an own 'external header skipper' routine which was very
kisza> close the the kernel's one. So I would like to update the netfilter code
kisza> to use the kernel's function. For this, we have to export the
kisza> ipv6_skip_exthdr() function from net/ipv6/exthdrs.c . I have checked
kisza> your code, too. It looks very close to the kernel's code.
kisza> As I have noticed, the differences:
kisza> - handling of the fragments

No, ip6_ct_skip_exthdr() in my code may return NEXTHDR_NONE.
I want to distinguish the packet with NEXTHDR_NONE and the packet with unknown
extension header. I want to let "proto_generic" module to handle it.

BTW, handling of the fragments is bug fix.
"fhdr->frag_off" in ipv6_skip_exthdr() is out of area of "hdr".
I thought that it's already reported but look like still not fixed...

kisza>   your code checks that the member of the extension are in the skb or
kisza> not since the common part checks only the basic extension header size.
kisza> After it your code linearizes the skb to cover the extension header.
kisza> So, the kernel does not check the size and does not linearize. After
kisza> these fixes the 2 codes will be similar.

Excuse me, I don't understand which code you said about. find_prev_fhdr() ?
This function finds previous header of fragment header. We can't use
ipv6_skip_exthdr() to do this. Even though we can find offset of fragment
header, we can't find offset of previous header because we can't parse
extension header backwards.

kisza> Would not be better to export the kernel's function and use the
kisza> ipv6_skip_exthdr() in the netfilter codes?

I think so about ip6_ct_skip_exthdr(). I'm going to delete this function
from my patch if 

	we don't need to distinguish packets with NEXTHDR_NONE and the packet
	with unknown extension header.

and when

	the bug in ipv6_skip_exthdr() is fixed.

kisza> My second commet is near this area. I have planned that an offset value
kisza> which points after the last extension header and a variable which
kisza> contain the last nexthdr value would be very helpful for the future -
kisza> but I was too lazy to do this work. With the connection tracking this
kisza> function (ipv6_skip_exthdr) will be called several time on the same
kisza> packet (in the main kernel, at every LOG, at every match, at every ct,
kisza> ...) With USAGI we could - probably - find the space for this 2
kisza> variable. Do you have any recommendation?

I think we'd be better that extension header is parsed in L3 specific code
at once, and the offset and the nexthdr value is passed the functions
which need one as argument. This eases writing l3 independent codes in future.

kisza> Your FTP code uses EPSV and EPRT from rfc2428. What's about the FOOBAR
kisza> RFC (1639)? OK, it's a joke :)

I want to think that's joke, too...

kisza> Could we open an IPv4 data connection next to the IPv6 controll
kisza> connection?

You can't open even though you load ip_conntrack_ftp and ip6_conntrack_ftp.
I don't want to allow that because I don't know what happens.

Regards,

----------------------------------------
Yasuyuki KOZAKAI

Communication Platform Laboratory,
Corporate Research & Development Center,
Toshiba Corporation

yasuyuki.kozakai@toshiba.co.jp
----------------------------------------

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Patch]: IPv6 Connection Tracking
  2003-09-25 18:57   ` Pekka Savola
  2003-09-25 19:07     ` Andras Kis-Szabo
@ 2003-09-29  8:42     ` Harald Welte
  1 sibling, 0 replies; 15+ messages in thread
From: Harald Welte @ 2003-09-29  8:42 UTC (permalink / raw)
  To: Pekka Savola
  Cc: Andras Kis-Szabo, Yasuyuki Kozakai, Netfilter Devel, Netdev, usagi-core

[-- Attachment #1: Type: text/plain, Size: 2112 bytes --]

On Thu, Sep 25, 2003 at 09:57:47PM +0300, Pekka Savola wrote:
> First, a meta-comment:
> 
> What I fear is that in the end, nothing gets done because having the goal
> set to perfection.  If there is no energy to drive through the
> L3-independent connecting tracking, the end result is that the user
> does not have this feature 

well, that's the reason why I'd like to see it in 

> (remember ip6tables REJECT target?  That must have been sitting in
> netfilter for some 2+ years, and not having been integrated in the
> mainline kernel and the users still do not have the feature!).

Mh, nobody has bugged me about that in all those 2 years.  At least I
don't remember somebody asking me for kernel inclusion... 

Since ipv4 REJECT has now changed

> So, my personal take is:
>  - if a L3-independent conn tracking can be done *quickly*, fine,

I've started to write a small paper about the envisioned desgign.  It
doesn't look all that complicated, if somebody can concentrate on this
job.  I personally (as indicated before) do not have the time to work on
that issue.  But I'm happy to give advise.

>  - if not, just merge the current one, start working on L3 independent 
> conn tracking, and add it when available.

I understand your point.  However, I fear to be the one responsible of
keeping ip6_conntrack in sync with ip_conntrack.  If there is a
volunteer (maybe Yasuyuki?) who would really commit himself to look at
which changes go into ip_conntack, and sending me patches to sync
ip6_conntrack, I'd be more inclined to submit ip6_conntrack to the
mainline kernel.  

> .. but I'm not the one who's answering the support emails, so in all 
> fairness, I should be silent now..

;)

-- 
- Harald Welte <laforge@netfilter.org>             http://www.netfilter.org/
============================================================================
  "Fragmentation is like classful addressing -- an interesting early
   architectural error that shows how much experimentation was going
   on while IP was being designed."                    -- Paul Vixie

[-- Attachment #2: Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Patch]: IPv6 Connection Tracking
  2003-09-25  9:28 ` Harald Welte
                     ` (2 preceding siblings ...)
  2003-09-25 13:10   ` Yasuyuki Kozakai
@ 2003-10-03 11:19   ` Yasuyuki Kozakai
  2003-10-07 12:30     ` Harald Welte
  3 siblings, 1 reply; 15+ messages in thread
From: Yasuyuki Kozakai @ 2003-10-03 11:19 UTC (permalink / raw)
  To: laforge; +Cc: yasuyuki.kozakai, coreteam, netfilter-devel, netdev, usagi-core


Hi, Harald,

Sorry for the late reply.

From: Harald Welte <laforge@netfilter.org>
Date: Thu, 25 Sep 2003 11:28:06 +0200

> To summarize, I see the following options:
> 
> 1) submit ip6_conntrack to the 2.6.x kernel
>    We would then need somebody committe to maintaining it, assuring
>    it keeps in sync with the work done on ip_conntrack.  I do not want
>    to put the burden of ip_conntrack / ip6_conntrack synchronization on
>    everybody who submits patches/bugfixes/...
> 
> 2) keep ip6_conntrack in patch-o-matic and start work on a l3
>    independent conntrack system.
>    This would give more users to the code, since most advanced 
>    netfilter users are using patch-o-matic anyway.

I select 2), too.

Harald, Could you add my patch to patch-o-matic ?

Regards,

----------------------------------------
Yasuyuki KOZAKAI

Communication Platform Laboratory,
Corporate Research & Development Center,
Toshiba Corporation

yasuyuki.kozakai@toshiba.co.jp
----------------------------------------

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Patch]: IPv6 Connection Tracking
  2003-10-03 11:19   ` Yasuyuki Kozakai
@ 2003-10-07 12:30     ` Harald Welte
  0 siblings, 0 replies; 15+ messages in thread
From: Harald Welte @ 2003-10-07 12:30 UTC (permalink / raw)
  To: Yasuyuki Kozakai; +Cc: coreteam, netfilter-devel, netdev, usagi-core

[-- Attachment #1: Type: text/plain, Size: 645 bytes --]

On Fri, Oct 03, 2003 at 08:19:07PM +0900, Yasuyuki Kozakai wrote:
> I select 2), too.

Ok, thanks for your patience and understanding.

> Harald, Could you add my patch to patch-o-matic ?

Ok, I'll put it into patch-o-matic asap.

> Regards,
> Yasuyuki KOZAKAI

-- 
- Harald Welte <laforge@netfilter.org>             http://www.netfilter.org/
============================================================================
  "Fragmentation is like classful addressing -- an interesting early
   architectural error that shows how much experimentation was going
   on while IP was being designed."                    -- Paul Vixie

[-- Attachment #2: Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [Patch]: IPv6 Connection Tracking
@ 2003-09-24 11:59 Yasuyuki Kozakai
  0 siblings, 0 replies; 15+ messages in thread
From: Yasuyuki Kozakai @ 2003-09-24 11:59 UTC (permalink / raw)
  To: netfilter-devel

[-- Attachment #1: Type: Text/Plain, Size: 1960 bytes --]

I resend this message because I misstook my mail address.
I'm sorry about duplicated messages if you received.

-----------------------
Hello,
I'm a member of USAGI Project.

A few months ago, I told in Netfilter developer mailinglist that I was coding
IPv6 Connection Tracking in Linux 2.5, 2.6.
I completed that, so I send a patch.

The summaries are below.

	- based on IPv4 Connection Tracking
		* TCP, UDP, ICMPv6 Echo, FTP, state module,
		* multiple expectations can be used.
	- Fragmented packets is handled as below, so "Path MTU Discovery"
	  isn't disturbed, and can be reduced copying skb.
		1. clone skb, and original skb isn't touched.
		2. cloned skbs are reassembled into one skb. the codes for
		   this processing are based on net/ipv6/reassembly.c
		3. reassembled packet is tracked.
		4. set nf_ct_info of reassembled skb to original skbs.
		5. original skbs are passed to next hook function
		   at PREROUTING. (none functions in present)
		6. reassembled packet is discarded.
	- The offset at protocol header is passed to the various functions
	  in order to parse Extension Headers at once. 
	- NAT is not supported
	- Multicast packets aren't handled.
	- Resent patches in patch-o-matic/submitted/* are followed up.

I tested by using regular packets:
	- TCP, UDP, ICMPv6 Echo, ICMPv6 Error, FTP, Fragmented UDP packets

and illegal packets:
	- TCP, FTP, ICMPV6 Error with incorrect checksum
	- TCP, FTP with incorrect sequence number
	- irregular Fragmented UDP packets
	- unknown Extension Header
	- and so on.

The codes handling multiple expectations aren't be tested, because I don't know
such a application with IPv6. But they are same as IPv4 conntrack except names
of functions, variables.

Regards

----------------------------------------
Yasuyuki KOZAKAI

Communication Platform Laboratory,
Corporate Research & Development Center,
Toshiba Corporation

yasuyuki.kozakai@toshiba.co.jp
----------------------------------------

[-- Attachment #2: conntrack6.patch --]
[-- Type: Text/Plain, Size: 156436 bytes --]

diff -Nur linux-2.6.0-test5/include/linux/netfilter.h linux-2.6.0-test5-ct6/include/linux/netfilter.h
--- linux-2.6.0-test5/include/linux/netfilter.h	2003-09-09 04:50:06.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter.h	2003-09-24 11:35:33.000000000 +0900
@@ -155,6 +155,10 @@
 
 extern void (*ip_ct_attach)(struct sk_buff *, struct nf_ct_info *);
 
+#ifdef CONFIG_IPV6
+extern void (*ip6_ct_attach)(struct sk_buff *, struct nf_ct_info *);
+#endif
+
 #ifdef CONFIG_NETFILTER_DEBUG
 extern void nf_dump_skb(int pf, struct sk_buff *skb);
 #endif
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/config.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_tuple.h>
+#include <linux/bitops.h>
+#include <linux/compiler.h>
+#include <asm/atomic.h>
+
+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 <linux/netfilter_ipv6/ip6_conntrack_tcp.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_icmpv6.h>
+
+/* 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 <linux/netfilter_ipv6/ip6_conntrack_ftp.h>
+
+/* 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 <linux/types.h>
+#include <linux/skbuff.h>
+
+#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 <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_core.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_core.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_core.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_core.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter.h>
+#include <linux/netfilter_ipv4/lockhelp.h>
+
+/* 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 */
+
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter_ipv4/lockhelp.h>
+
+/* 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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_helper.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_helper.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_helper.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_helper.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter_ipv6/ip6_conntrack.h>
+
+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*/
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <asm/atomic.h>
+
+struct ip6_ct_icmpv6
+{
+	/* Optimization: when number in == number out, forget immediately. */
+	atomic_t count;
+};
+#endif /* _IP6_CONNTRACK_ICMPv6_H */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/skbuff.h>
+
+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*/
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter.h>
+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 */
+
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/in6.h>
+#include <linux/kernel.h>
+#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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6t_state.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6t_state.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6t_state.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6t_state.h	2003-09-24 11:36:48.000000000 +0900
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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*/
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6.h	2003-09-09 04:50:22.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6.h	2003-09-24 11:36:04.000000000 +0900
@@ -53,6 +53,7 @@
 #define NF_IP6_POST_ROUTING	4
 #define NF_IP6_NUMHOOKS		5
 
+#define SO_ORIGINAL_DST 80
 
 enum nf_ip6_hook_priorities {
 	NF_IP6_PRI_FIRST = INT_MIN,
diff -Nur linux-2.6.0-test5/net/core/netfilter.c linux-2.6.0-test5-ct6/net/core/netfilter.c
--- linux-2.6.0-test5/net/core/netfilter.c	2003-09-09 04:50:03.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/core/netfilter.c	2003-09-24 11:34:52.000000000 +0900
@@ -749,6 +749,9 @@
    and hence manufactured ICMP or RST packets will not be associated
    with it. */
 void (*ip_ct_attach)(struct sk_buff *, struct nf_ct_info *);
+#ifdef CONFIG_IPV6
+void (*ip6_ct_attach)(struct sk_buff *, struct nf_ct_info *);
+#endif
 
 void __init netfilter_init(void)
 {
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/Kconfig linux-2.6.0-test5-ct6/net/ipv6/netfilter/Kconfig
--- linux-2.6.0-test5/net/ipv6/netfilter/Kconfig	2003-09-09 04:50:22.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/Kconfig	2003-09-24 11:32:03.000000000 +0900
@@ -5,6 +5,16 @@
 menu "IPv6: Netfilter Configuration"
 	depends on INET && IPV6!=n && 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
+	  <file:Documentation/modules.txt>.  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
@@ -173,6 +183,31 @@
 	  If you want to compile it as a module, say M here and read
 	  Documentation/modules.txt.  If unsure, say `N'.
 
+config IP6_NF_CONNTRACK
+	tristate "Connection tracking (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
+	  <file:Documentation/modules.txt>.  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
+	  <file:Documentation/modules.txt>.  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
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/Makefile linux-2.6.0-test5-ct6/net/ipv6/netfilter/Makefile
--- linux-2.6.0-test5/net/ipv6/netfilter/Makefile	2003-09-09 04:50:07.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/Makefile	2003-09-24 11:31:27.000000000 +0900
@@ -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
@@ -22,3 +34,4 @@
 obj-$(CONFIG_IP6_NF_QUEUE) += ip6_queue.o
 obj-$(CONFIG_IP6_NF_TARGET_LOG) += ip6t_LOG.o
 obj-$(CONFIG_IP6_NF_MATCH_HL) += ip6t_hl.o
+obj-$(CONFIG_IP6_NF_MATCH_HL) += ip6t_state.o
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_core.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_core.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_core.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_core.c	2003-09-24 13:01:42.000000000 +0900
@@ -0,0 +1,1614 @@
+/*
+ * IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <laforge@gnumonks.org>
+ *     - new API and handling of conntrack/nat helpers
+ *     - now capable of multiple expectations for one master
+ * 16 Jul 2002: Harald Welte <laforge@gnumonks.org>
+ *     - add usage/reference counts to ip_conntrack_expect
+ *     - export ip_conntrack[_expect]_{find_get,put} functions
+ * */
+
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/icmpv6.h>
+#include <linux/ipv6.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/vmalloc.h>
+#include <net/checksum.h>
+#include <linux/stddef.h>
+#include <linux/sysctl.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/jhash.h>
+#include <net/ipv6.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+
+/* 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 <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_core.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#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;
+	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);
+			ip6_conntrack_put(ct->master->expectant);
+		}
+		kfree(ct->master);
+	}
+	WRITE_UNLOCK(&ip6_conntrack_lock);
+
+	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("SO_ORIGINAL_DST: Not a TCP socket\n");
+		return -ENOPROTOOPT;
+	}
+
+	if ((unsigned int) *len < sizeof(struct sockaddr_in)) {
+		DEBUGP("SO_ORIGINAL_DST: len %u not %u\n",
+		       *len, sizeof(struct sockaddr_in));
+		return -EINVAL;
+	}
+
+	h = 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("SO_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("SO_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	= SO_ORIGINAL_DST,
+	.get_optmax	= SO_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;
+}
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_ftp.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_ftp.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_ftp.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_ftp.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,555 @@
+/*
+ * FTP extension for IPv6 connection tracking.
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/config.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/ipv6.h>
+#include <linux/ctype.h>
+#include <net/checksum.h>
+#include <net/tcp.h>
+#include <net/ipv6.h>
+#include <linux/kernel.h>
+
+#include <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_ftp.h>
+
+/* 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 revised to inet_pton() in glibc-2.2.4-19.3.src.rpm
+   of RedHat 7.2 - kozakai */
+
+#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;
+	size_t v4len;
+
+        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++) {
+		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);
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_generic.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_generic.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_generic.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_generic.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,82 @@
+/*
+ * IPv6 generic protocol extension for IPv6 connection tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+
+#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 };
+
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c	2003-09-24 13:06:58.000000000 +0900
@@ -0,0 +1,132 @@
+/*
+ * ICMPv6 extension for IPv6 connection tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/in.h>
+#include <linux/icmpv6.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+
+#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,
+	};
+
+	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,
+	};
+
+	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 };
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,273 @@
+/*
+ * TCP extension for IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/module.h>
+#include <linux/in.h>
+#include <linux/ipv6.h>
+#include <linux/tcp.h>
+#include <linux/string.h>
+
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+#include <linux/netfilter_ipv4/lockhelp.h>
+
+#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;
+
+	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 };
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_udp.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_udp.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_udp.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_udp.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,95 @@
+/*
+ * UDP extension for IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/udp.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+
+#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 };
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_reasm.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_reasm.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_reasm.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_reasm.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,989 @@
+/*
+ * IPv6 fragment reassembly for connection tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/jiffies.h>
+#include <linux/net.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/in6.h>
+#include <linux/ipv6.h>
+#include <linux/icmpv6.h>
+#include <linux/random.h>
+#include <linux/jhash.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/transp_v6.h>
+#include <net/rawv6.h>
+#include <net/ndisc.h>
+#include <net/addrconf.h>
+#include <linux/sysctl.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#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; i<skb_shinfo(head)->nr_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; s = s2) {
+		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);
+	}
+
+	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();
+}
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_standalone.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_standalone.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_standalone.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_standalone.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,502 @@
+/*
+ * IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/ipv6.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/version.h>
+#include <net/checksum.h>
+
+#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 <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_core.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_reasm.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#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);
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6t_state.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6t_state.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6t_state.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6t_state.c	2003-09-24 11:30:59.000000000 +0900
@@ -0,0 +1,80 @@
+/*
+ * Matching connection tracking information
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_state.h>
+
+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");
diff -Nur linux-2.6.0-test5/net/netsyms.c linux-2.6.0-test5-ct6/net/netsyms.c
--- linux-2.6.0-test5/net/netsyms.c	2003-09-09 04:50:02.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/netsyms.c	2003-09-24 11:34:11.000000000 +0900
@@ -606,6 +606,9 @@
 EXPORT_SYMBOL(nf_setsockopt);
 EXPORT_SYMBOL(nf_getsockopt);
 EXPORT_SYMBOL(ip_ct_attach);
+#if defined(CONFIG_IPV6)
+EXPORT_SYMBOL(ip6_ct_attach);
+#endif
 #ifdef CONFIG_INET
 #include <linux/netfilter_ipv4.h>
 EXPORT_SYMBOL(ip_route_me_harder);

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [Patch]: IPv6 Connection Tracking
@ 2003-09-24  8:00 Yasuyuki Kozakai
  0 siblings, 0 replies; 15+ messages in thread
From: Yasuyuki Kozakai @ 2003-09-24  8:00 UTC (permalink / raw)
  To: laforge; +Cc: netfilter-devel

[-- Attachment #1: Type: Text/Plain, Size: 1928 bytes --]

Hello, Harald,

I'm writing to you for the first time.
I'm a member of USAGI Project.

A few months ago, I told in Netfilter developer mailing list that I was coding
IPv6 Connection Tracking in Linux 2.5, 2.6.
I completed that, so I send a patch.

The summaries are below.

	- based on IPv4 Connection Tracking
		* TCP, UDP, ICMPv6 Echo, FTP, state module,
		* multiple expectations can be used.
	- Fragmented packets is handled as below, so "Path MTU Discovery"
	  isn't disturbed, and can be reduced copying skb.
		1. The skb is cloned, and original skb isn't touched.
		2. the cloned skbs are reassembled into one skb. The codes for
		   this processing are based on net/ipv6/reassembly.c
		3. The reassembled packet is tracked.
		4. "nf_ct_info" of reassembled skb is set to original skbs.
		5. The original skbs are passed to next hook function
		   at PREROUTING. (none functions in present)
		6. The reassembled packet is discarded.
	- The offset at protocol header is passed to the various functions
	  in order to parse Extension Headers at once. 
	- NAT is not supported
	- Multicast packets aren't handled.
	- Recent patches in patch-o-matic/submitted/* are followed up.

I tested by using regular packets:
	- TCP, UDP, ICMPv6 Echo, ICMPv6 Error, FTP, Fragmented UDP packets

and illegal packets:
	- TCP, FTP, ICMPV6 Error with incorrect checksum
	- TCP, FTP with incorrect sequence number
	- illegal Fragmented UDP packets
	- unknown Extension Header, truncated Extension Header
	- and so on.

The codes handling multiple expectations aren't be tested, because I don't know
such a application with IPv6. But they are same as IPv4 conntrack except names
of functions, variables.

Regards

----------------------------------------
Yasuyuki KOZAKAI

Communication Platform Laboratory,
Corporate Research & Development Center,
Toshiba Corporation

yasuyuki.kozakai@toshiba.co.jp
----------------------------------------

[-- Attachment #2: conntrack6.patch --]
[-- Type: Text/Plain, Size: 156436 bytes --]

diff -Nur linux-2.6.0-test5/include/linux/netfilter.h linux-2.6.0-test5-ct6/include/linux/netfilter.h
--- linux-2.6.0-test5/include/linux/netfilter.h	2003-09-09 04:50:06.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter.h	2003-09-24 11:35:33.000000000 +0900
@@ -155,6 +155,10 @@
 
 extern void (*ip_ct_attach)(struct sk_buff *, struct nf_ct_info *);
 
+#ifdef CONFIG_IPV6
+extern void (*ip6_ct_attach)(struct sk_buff *, struct nf_ct_info *);
+#endif
+
 #ifdef CONFIG_NETFILTER_DEBUG
 extern void nf_dump_skb(int pf, struct sk_buff *skb);
 #endif
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/config.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_tuple.h>
+#include <linux/bitops.h>
+#include <linux/compiler.h>
+#include <asm/atomic.h>
+
+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 <linux/netfilter_ipv6/ip6_conntrack_tcp.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_icmpv6.h>
+
+/* 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 <linux/netfilter_ipv6/ip6_conntrack_ftp.h>
+
+/* 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 <linux/types.h>
+#include <linux/skbuff.h>
+
+#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 <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_core.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_core.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_core.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_core.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter.h>
+#include <linux/netfilter_ipv4/lockhelp.h>
+
+/* 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 */
+
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_ftp.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter_ipv4/lockhelp.h>
+
+/* 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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_helper.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_helper.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_helper.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_helper.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter_ipv6/ip6_conntrack.h>
+
+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*/
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_icmpv6.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <asm/atomic.h>
+
+struct ip6_ct_icmpv6
+{
+	/* Optimization: when number in == number out, forget immediately. */
+	atomic_t count;
+};
+#endif /* _IP6_CONNTRACK_ICMPv6_H */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_protocol.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/skbuff.h>
+
+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*/
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_reasm.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/netfilter.h>
+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 */
+
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tcp.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6_conntrack_tuple.h	2003-09-24 11:36:37.000000000 +0900
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/in6.h>
+#include <linux/kernel.h>
+#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 */
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6t_state.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6t_state.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6/ip6t_state.h	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6/ip6t_state.h	2003-09-24 11:36:48.000000000 +0900
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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*/
diff -Nur linux-2.6.0-test5/include/linux/netfilter_ipv6.h linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6.h
--- linux-2.6.0-test5/include/linux/netfilter_ipv6.h	2003-09-09 04:50:22.000000000 +0900
+++ linux-2.6.0-test5-ct6/include/linux/netfilter_ipv6.h	2003-09-24 11:36:04.000000000 +0900
@@ -53,6 +53,7 @@
 #define NF_IP6_POST_ROUTING	4
 #define NF_IP6_NUMHOOKS		5
 
+#define SO_ORIGINAL_DST 80
 
 enum nf_ip6_hook_priorities {
 	NF_IP6_PRI_FIRST = INT_MIN,
diff -Nur linux-2.6.0-test5/net/core/netfilter.c linux-2.6.0-test5-ct6/net/core/netfilter.c
--- linux-2.6.0-test5/net/core/netfilter.c	2003-09-09 04:50:03.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/core/netfilter.c	2003-09-24 11:34:52.000000000 +0900
@@ -749,6 +749,9 @@
    and hence manufactured ICMP or RST packets will not be associated
    with it. */
 void (*ip_ct_attach)(struct sk_buff *, struct nf_ct_info *);
+#ifdef CONFIG_IPV6
+void (*ip6_ct_attach)(struct sk_buff *, struct nf_ct_info *);
+#endif
 
 void __init netfilter_init(void)
 {
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/Kconfig linux-2.6.0-test5-ct6/net/ipv6/netfilter/Kconfig
--- linux-2.6.0-test5/net/ipv6/netfilter/Kconfig	2003-09-09 04:50:22.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/Kconfig	2003-09-24 11:32:03.000000000 +0900
@@ -5,6 +5,16 @@
 menu "IPv6: Netfilter Configuration"
 	depends on INET && IPV6!=n && 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
+	  <file:Documentation/modules.txt>.  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
@@ -173,6 +183,31 @@
 	  If you want to compile it as a module, say M here and read
 	  Documentation/modules.txt.  If unsure, say `N'.
 
+config IP6_NF_CONNTRACK
+	tristate "Connection tracking (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
+	  <file:Documentation/modules.txt>.  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
+	  <file:Documentation/modules.txt>.  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
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/Makefile linux-2.6.0-test5-ct6/net/ipv6/netfilter/Makefile
--- linux-2.6.0-test5/net/ipv6/netfilter/Makefile	2003-09-09 04:50:07.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/Makefile	2003-09-24 11:31:27.000000000 +0900
@@ -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
@@ -22,3 +34,4 @@
 obj-$(CONFIG_IP6_NF_QUEUE) += ip6_queue.o
 obj-$(CONFIG_IP6_NF_TARGET_LOG) += ip6t_LOG.o
 obj-$(CONFIG_IP6_NF_MATCH_HL) += ip6t_hl.o
+obj-$(CONFIG_IP6_NF_MATCH_HL) += ip6t_state.o
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_core.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_core.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_core.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_core.c	2003-09-24 13:01:42.000000000 +0900
@@ -0,0 +1,1614 @@
+/*
+ * IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <laforge@gnumonks.org>
+ *     - new API and handling of conntrack/nat helpers
+ *     - now capable of multiple expectations for one master
+ * 16 Jul 2002: Harald Welte <laforge@gnumonks.org>
+ *     - add usage/reference counts to ip_conntrack_expect
+ *     - export ip_conntrack[_expect]_{find_get,put} functions
+ * */
+
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/icmpv6.h>
+#include <linux/ipv6.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/vmalloc.h>
+#include <net/checksum.h>
+#include <linux/stddef.h>
+#include <linux/sysctl.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/jhash.h>
+#include <net/ipv6.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+
+/* 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 <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_core.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#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;
+	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);
+			ip6_conntrack_put(ct->master->expectant);
+		}
+		kfree(ct->master);
+	}
+	WRITE_UNLOCK(&ip6_conntrack_lock);
+
+	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("SO_ORIGINAL_DST: Not a TCP socket\n");
+		return -ENOPROTOOPT;
+	}
+
+	if ((unsigned int) *len < sizeof(struct sockaddr_in)) {
+		DEBUGP("SO_ORIGINAL_DST: len %u not %u\n",
+		       *len, sizeof(struct sockaddr_in));
+		return -EINVAL;
+	}
+
+	h = 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("SO_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("SO_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	= SO_ORIGINAL_DST,
+	.get_optmax	= SO_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;
+}
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_ftp.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_ftp.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_ftp.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_ftp.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,555 @@
+/*
+ * FTP extension for IPv6 connection tracking.
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/config.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/ipv6.h>
+#include <linux/ctype.h>
+#include <net/checksum.h>
+#include <net/tcp.h>
+#include <net/ipv6.h>
+#include <linux/kernel.h>
+
+#include <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_ftp.h>
+
+/* 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 revised to inet_pton() in glibc-2.2.4-19.3.src.rpm
+   of RedHat 7.2 - kozakai */
+
+#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;
+	size_t v4len;
+
+        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++) {
+		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);
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_generic.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_generic.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_generic.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_generic.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,82 @@
+/*
+ * IPv6 generic protocol extension for IPv6 connection tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+
+#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 };
+
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_icmpv6.c	2003-09-24 13:06:58.000000000 +0900
@@ -0,0 +1,132 @@
+/*
+ * ICMPv6 extension for IPv6 connection tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/in.h>
+#include <linux/icmpv6.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+
+#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,
+	};
+
+	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,
+	};
+
+	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 };
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_tcp.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,273 @@
+/*
+ * TCP extension for IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/module.h>
+#include <linux/in.h>
+#include <linux/ipv6.h>
+#include <linux/tcp.h>
+#include <linux/string.h>
+
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+#include <linux/netfilter_ipv4/lockhelp.h>
+
+#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;
+
+	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 };
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_udp.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_udp.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_proto_udp.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_proto_udp.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,95 @@
+/*
+ * UDP extension for IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/udp.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+
+#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 };
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_reasm.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_reasm.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_reasm.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_reasm.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,989 @@
+/*
+ * IPv6 fragment reassembly for connection tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/jiffies.h>
+#include <linux/net.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/in6.h>
+#include <linux/ipv6.h>
+#include <linux/icmpv6.h>
+#include <linux/random.h>
+#include <linux/jhash.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/transp_v6.h>
+#include <net/rawv6.h>
+#include <net/ndisc.h>
+#include <net/addrconf.h>
+#include <linux/sysctl.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#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; i<skb_shinfo(head)->nr_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; s = s2) {
+		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);
+	}
+
+	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();
+}
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_standalone.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_standalone.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6_conntrack_standalone.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6_conntrack_standalone.c	2003-09-24 11:30:53.000000000 +0900
@@ -0,0 +1,502 @@
+/*
+ * IPv6 Connection Tracking
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/types.h>
+#include <linux/ipv6.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/version.h>
+#include <net/checksum.h>
+
+#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 <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_protocol.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_core.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_helper.h>
+#include <linux/netfilter_ipv6/ip6_conntrack_reasm.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#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);
diff -Nur linux-2.6.0-test5/net/ipv6/netfilter/ip6t_state.c linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6t_state.c
--- linux-2.6.0-test5/net/ipv6/netfilter/ip6t_state.c	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/ipv6/netfilter/ip6t_state.c	2003-09-24 11:30:59.000000000 +0900
@@ -0,0 +1,80 @@
+/*
+ * Matching connection tracking information
+ * Linux INET6 implementation
+ *
+ * Copyright (C)2003 USAGI/WIDE Project
+ *
+ * Authors:
+ *	Yasuyuki Kozakai	<yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv6/ip6_conntrack.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_state.h>
+
+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");
diff -Nur linux-2.6.0-test5/net/netsyms.c linux-2.6.0-test5-ct6/net/netsyms.c
--- linux-2.6.0-test5/net/netsyms.c	2003-09-09 04:50:02.000000000 +0900
+++ linux-2.6.0-test5-ct6/net/netsyms.c	2003-09-24 11:34:11.000000000 +0900
@@ -606,6 +606,9 @@
 EXPORT_SYMBOL(nf_setsockopt);
 EXPORT_SYMBOL(nf_getsockopt);
 EXPORT_SYMBOL(ip_ct_attach);
+#if defined(CONFIG_IPV6)
+EXPORT_SYMBOL(ip6_ct_attach);
+#endif
 #ifdef CONFIG_INET
 #include <linux/netfilter_ipv4.h>
 EXPORT_SYMBOL(ip_route_me_harder);

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2003-10-07 12:30 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2003-09-25  5:21 [Patch]: IPv6 Connection Tracking Yasuyuki Kozakai
2003-09-25  9:28 ` Harald Welte
2003-09-25 10:50   ` [netfilter-core] " Jozsef Kadlecsik
2003-09-25 10:51   ` David S. Miller
2003-09-25 13:10   ` Yasuyuki Kozakai
2003-10-03 11:19   ` Yasuyuki Kozakai
2003-10-07 12:30     ` Harald Welte
2003-09-25 18:48 ` Andras Kis-Szabo
2003-09-25 18:57   ` Pekka Savola
2003-09-25 19:07     ` Andras Kis-Szabo
2003-09-25 19:14       ` Pekka Savola
2003-09-29  8:42     ` Harald Welte
2003-09-26  3:54   ` Yasuyuki Kozakai
  -- strict thread matches above, loose matches on Subject: below --
2003-09-24 11:59 Yasuyuki Kozakai
2003-09-24  8:00 Yasuyuki Kozakai

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.