diff --git a/include/linux/fib_rules.h b/include/linux/fib_rules.h
index 8270aac..2bbfa87 100644
--- a/include/linux/fib_rules.h
+++ b/include/linux/fib_rules.h
@@ -5,8 +5,11 @@
 #include <linux/rtnetlink.h>
 
 /* rule is permanent, and cannot be deleted */
-#define FIB_RULE_PERMANENT	1
-#define FIB_RULE_INVERT		2
+#define FIB_RULE_PERMANENT	0x00000001
+#define FIB_RULE_INVERT		0x00000002
+
+/* try to find source address in routing lookups */
+#define	FIB_RULE_FIND_SADDR	0x00010000
 
 struct fib_rule_hdr
 {
diff --git a/include/linux/snmp.h b/include/linux/snmp.h
index 854aa6b..14098de 100644
--- a/include/linux/snmp.h
+++ b/include/linux/snmp.h
@@ -232,4 +232,30 @@ enum
 	__LINUX_MIB_MAX
 };
 
+/* xfrm mib definitions */
+enum
+{
+	XFRM_MIB_NUM = 0,
+	XFRM_MIB_INERROR,		/* XfrmInError */
+	XFRM_MIB_INHDRERROR,		/* XfrmInHdrError */
+	XFRM_MIB_INSTATEPROTOERROR,	/* XfrmInStateProtoError */
+	XFRM_MIB_INSTATEMODEERROR,	/* XfrmInStateModeError */
+	XFRM_MIB_INSEQOUTOFWINDOW,	/* XfrmInSeqOutOfWindow */
+	XFRM_MIB_INSTATEEXPIRED,	/* XfrmInStateExpired */
+	XFRM_MIB_INSTATEINVALID,	/* XfrmInStateInvalid */
+	XFRM_MIB_INNOSTATES,		/* XfrmInNoStates */
+	XFRM_MIB_INTMPLMISMATCH,	/* XfrmInTmplMismatch */
+	XFRM_MIB_INPOLBLOCK,		/* XfrmInPolBlock */
+	XFRM_MIB_INNOPOLS,		/* XfrmInNoPols */
+	XFRM_MIB_OUTERROR,		/* XfrmOutError */
+	XFRM_MIB_OUTLENGTHERROR,	/* XfrmOutLengthError */
+	XFRM_MIB_OUTSTATEPROTOERROR,	/* XfrmOutStateProtoError */
+	XFRM_MIB_OUTSTATEMODEERROR,	/* XfrmOutStateModeError */
+	XFRM_MIB_OUTSTATEEXPIRED,	/* XfrmOutStateExpired */
+	XFRM_MIB_OUTNOSTATES,		/* XfrmOutNoStates */
+	XFRM_MIB_OUTBUNDLEERROR,	/* XfrmOutBundleError */
+	XFRM_MIB_OUTPOLBLOCK,		/* XfrmOutPolBlock */
+	__XFRM_MIB_MAX
+};
+
 #endif	/* _LINUX_SNMP_H */
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 2c5fb38..9b403d5 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -438,6 +438,7 @@ enum
 	NET_CIPSOV4_RBM_STRICTVALID=121,
 	NET_TCP_AVAIL_CONG_CONTROL=122,
 	NET_TCP_ALLOWED_CONG_CONTROL=123,
+	NET_IPV4_TUNNEL_SEND_ICMP_ERROR=124,
 };
 
 enum {
@@ -541,6 +542,7 @@ enum {
 	NET_IPV6_IP6FRAG_TIME=23,
 	NET_IPV6_IP6FRAG_SECRET_INTERVAL=24,
 	NET_IPV6_MLD_MAX_MSF=25,
+	NET_IPV6_TUNNEL_SEND_ICMP_ERROR=26,
 };
 
 enum {
diff --git a/include/linux/xfrm.h b/include/linux/xfrm.h
index 15ca89e..931ab6e 100644
--- a/include/linux/xfrm.h
+++ b/include/linux/xfrm.h
@@ -315,7 +315,8 @@ struct xfrm_userpolicy_info {
 #define XFRM_POLICY_ALLOW	0
 #define XFRM_POLICY_BLOCK	1
 	__u8				flags;
-#define XFRM_POLICY_LOCALOK	1	/* Allow user to override global policy */
+#define XFRM_POLICY_LOCALOK		0x01	/* Allow user to override global policy */
+#define XFRM_POLICY_X_MULTILOCAL	0x02	/* Exclude inbound flow with multilocal state */
 	__u8				share;
 };
 
diff --git a/include/net/flow.h b/include/net/flow.h
index ce4b10d..1dce23a 100644
--- a/include/net/flow.h
+++ b/include/net/flow.h
@@ -49,6 +49,7 @@ struct flowi {
 	__u8	proto;
 	__u8	flags;
 #define FLOWI_FLAG_MULTIPATHOLDROUTE 0x01
+#define FLOWI_FLAG_MULTILOCAL 0x02
 	union {
 		struct {
 			__be16	sport;
@@ -97,4 +98,10 @@ extern void *flow_cache_lookup(struct flowi *key, u16 family, u8 dir,
 extern void flow_cache_flush(void);
 extern atomic_t flow_cache_genid;
 
+static inline int flow_cache_uli_match(struct flowi *fl1, struct flowi *fl2)
+{
+	return (fl1->proto == fl2->proto &&
+		!memcmp(&fl1->uli_u, &fl2->uli_u, sizeof(fl1->uli_u)));
+}
+
 #endif
diff --git a/include/net/snmp.h b/include/net/snmp.h
index 464970e..a4519e9 100644
--- a/include/net/snmp.h
+++ b/include/net/snmp.h
@@ -106,8 +106,14 @@ struct linux_mib {
 	unsigned long	mibs[LINUX_MIB_MAX];
 };
 
+/* Xfrm */
+#define XFRM_MIB_MAX	__XFRM_MIB_MAX
+struct xfrm_mib {
+	unsigned long	mibs[XFRM_MIB_MAX];
+};
 
-/* 
+
+/*
  * FIXME: On x86 and some other CPUs the split into user and softirq parts
  * is not needed because addl $1,memory is atomic against interrupts (but 
  * atomic_inc would be overkill because of the lock cycles). Wants new 
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 5a00aa8..29755ba 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -23,6 +23,17 @@
 #define MODULE_ALIAS_XFRM_MODE(family, encap) \
 	MODULE_ALIAS("xfrm-mode-" __stringify(family) "-" __stringify(encap))
 
+#ifdef CONFIG_XFRM_STATISTICS
+DECLARE_SNMP_STAT(struct xfrm_mib, xfrm_statistics);
+#define XFRM_INC_STATS(field)		SNMP_INC_STATS(xfrm_statistics, field)
+#define XFRM_INC_STATS_BH(field)	SNMP_INC_STATS_BH(xfrm_statistics, field)
+#define XFRM_INC_STATS_USER(field) 	SNMP_INC_STATS_USER(xfrm_statistics, field)
+#else
+#define XFRM_INC_STATS(field)
+#define XFRM_INC_STATS_BH(field)
+#define XFRM_INC_STATS_USER(field)
+#endif
+
 extern struct sock *xfrm_nl;
 extern u32 sysctl_xfrm_aevent_etime;
 extern u32 sysctl_xfrm_aevent_rseqth;
@@ -269,6 +280,7 @@ struct xfrm_type
 	__u8			proto;
 	__u8			flags;
 #define XFRM_TYPE_NON_FRAGMENT	1
+#define XFRM_TYPE_MULTILOCAL	2
 
 	int			(*init_state)(struct xfrm_state *x);
 	void			(*destructor)(struct xfrm_state *);
@@ -584,6 +596,12 @@ struct xfrm_dst
 		struct rt6_info		rt6;
 	} u;
 	struct dst_entry *route;
+#ifdef CONFIG_XFRM_SUB_POLICY
+	u8 flags;
+#define XFRM_DST_MULTIPOLICY	1
+	struct flowi origin;
+	struct xfrm_selector partner;
+#endif
 	u32 genid;
 	u32 route_mtu_cached;
 	u32 child_mtu_cached;
@@ -904,9 +922,12 @@ extern void xfrm_state_init(void);
 extern void xfrm4_state_init(void);
 extern void xfrm6_state_init(void);
 extern void xfrm6_state_fini(void);
+extern int xfrm_proc_init(void);
 
 extern int xfrm_state_walk(u8 proto, int (*func)(struct xfrm_state *, int, void*), void *);
 extern struct xfrm_state *xfrm_state_alloc(void);
+extern int xfrm_state_trigger(struct flowi *fl, struct xfrm_policy *pol,
+			      struct xfrm_tmpl *tmpl, unsigned short family);
 extern struct xfrm_state *xfrm_state_find(xfrm_address_t *daddr, xfrm_address_t *saddr, 
 					  struct flowi *fl, struct xfrm_tmpl *tmpl,
 					  struct xfrm_policy *pol, int *err,
diff --git a/net/ipv4/tunnel4.c b/net/ipv4/tunnel4.c
index a794a8c..dc65f57 100644
--- a/net/ipv4/tunnel4.c
+++ b/net/ipv4/tunnel4.c
@@ -8,6 +8,9 @@
 #include <linux/mutex.h>
 #include <linux/netdevice.h>
 #include <linux/skbuff.h>
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
 #include <net/icmp.h>
 #include <net/ip.h>
 #include <net/protocol.h>
@@ -17,6 +20,44 @@ static struct xfrm_tunnel *tunnel4_handlers;
 static struct xfrm_tunnel *tunnel64_handlers;
 static DEFINE_MUTEX(tunnel4_mutex);
 
+static int sysctl_tunnel4_send_icmp_error __read_mostly = 1;
+
+#ifdef CONFIG_SYSCTL
+static struct ctl_table_header *tunnel4_sysctl_header;
+
+static ctl_table tunnel4_table[] = {
+	{
+		.ctl_name	= NET_IPV4_TUNNEL_SEND_ICMP_ERROR,
+		.procname	= "tunnel_send_icmp_error",
+		.data		= &sysctl_tunnel4_send_icmp_error,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &proc_dointvec
+	},
+        { .ctl_name = 0 }
+};
+
+static ctl_table ipv4_net_table[] = {
+	{
+		.ctl_name	= NET_IPV4,
+		.procname	= "ipv4",
+		.mode		= 0555,
+		.child		= tunnel4_table
+	},
+        { .ctl_name = 0 }
+};
+
+static ctl_table ipv4_root_table[] = {
+	{
+		.ctl_name	= CTL_NET,
+		.procname	= "net",
+		.mode		= 0555,
+		.child		= ipv4_net_table
+	},
+        { .ctl_name = 0 }
+};
+#endif
+
 int xfrm4_tunnel_register(struct xfrm_tunnel *handler, unsigned short family)
 {
 	struct xfrm_tunnel **pprev;
@@ -82,7 +123,8 @@ static int tunnel4_rcv(struct sk_buff *skb)
 		if (!handler->handler(skb))
 			return 0;
 
-	icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
+	if (sysctl_tunnel4_send_icmp_error)
+		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
 
 drop:
 	kfree_skb(skb);
@@ -134,6 +176,10 @@ static struct net_protocol tunnel64_protocol = {
 
 static int __init tunnel4_init(void)
 {
+#ifdef CONFIG_SYSCTL
+	tunnel4_sysctl_header = register_sysctl_table(ipv4_root_table);
+#endif
+
 	if (inet_add_protocol(&tunnel4_protocol, IPPROTO_IPIP)) {
 		printk(KERN_ERR "tunnel4 init: can't add protocol\n");
 		return -EAGAIN;
@@ -156,6 +202,10 @@ static void __exit tunnel4_fini(void)
 #endif
 	if (inet_del_protocol(&tunnel4_protocol, IPPROTO_IPIP))
 		printk(KERN_ERR "tunnel4 close: can't remove protocol\n");
+
+#ifdef CONFIG_SYSCTL
+	unregister_sysctl_table(tunnel4_sysctl_header);
+#endif
 }
 
 module_init(tunnel4_init);
diff --git a/net/ipv4/xfrm4_input.c b/net/ipv4/xfrm4_input.c
index 78e80de..96afb09 100644
--- a/net/ipv4/xfrm4_input.c
+++ b/net/ipv4/xfrm4_input.c
@@ -62,35 +62,51 @@ int xfrm4_rcv_encap(struct sk_buff *skb, __u16 encap_type)
 	int xfrm_nr = 0;
 	int decaps = 0;
 
-	if ((err = xfrm4_parse_spi(skb, skb->nh.iph->protocol, &spi, &seq)) != 0)
+	if ((err = xfrm4_parse_spi(skb, skb->nh.iph->protocol, &spi, &seq)) != 0) {
+		XFRM_INC_STATS(XFRM_MIB_INHDRERROR);
 		goto drop;
+	}
 
 	do {
 		struct iphdr *iph = skb->nh.iph;
 
-		if (xfrm_nr == XFRM_MAX_DEPTH)
+		if (xfrm_nr == XFRM_MAX_DEPTH) {
+			XFRM_INC_STATS(XFRM_MIB_INERROR);
 			goto drop;
+		}
 
 		x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi,
 				iph->protocol != IPPROTO_IPV6 ? iph->protocol : IPPROTO_IPIP, AF_INET);
-		if (x == NULL)
+		if (x == NULL) {
+			XFRM_INC_STATS(XFRM_MIB_INNOSTATES);
 			goto drop;
+		}
 
 		spin_lock(&x->lock);
-		if (unlikely(x->km.state != XFRM_STATE_VALID))
+		if (unlikely(x->km.state != XFRM_STATE_VALID)) {
+			XFRM_INC_STATS(XFRM_MIB_INSTATEINVALID);
 			goto drop_unlock;
+		}
 
-		if ((x->encap ? x->encap->encap_type : 0) != encap_type)
+		if ((x->encap ? x->encap->encap_type : 0) != encap_type) {
+			XFRM_INC_STATS(XFRM_MIB_INSTATEINVALID);
 			goto drop_unlock;
+		}
 
-		if (x->props.replay_window && xfrm_replay_check(x, seq))
+		if (x->props.replay_window && xfrm_replay_check(x, seq)) {
+			XFRM_INC_STATS(XFRM_MIB_INSEQOUTOFWINDOW);
 			goto drop_unlock;
+		}
 
-		if (xfrm_state_check_expire(x))
+		if (xfrm_state_check_expire(x)) {
+			XFRM_INC_STATS(XFRM_MIB_INSTATEEXPIRED);
 			goto drop_unlock;
+		}
 
-		if (x->type->input(x, skb))
+		if (x->type->input(x, skb)) {
+			XFRM_INC_STATS(XFRM_MIB_INSTATEPROTOERROR);
 			goto drop_unlock;
+		}
 
 		/* only the first xfrm gets the encap type */
 		encap_type = 0;
@@ -105,16 +121,20 @@ int xfrm4_rcv_encap(struct sk_buff *skb, __u16 encap_type)
 
 		xfrm_vec[xfrm_nr++] = x;
 
-		if (x->mode->input(x, skb))
+		if (x->mode->input(x, skb)) {
+			XFRM_INC_STATS(XFRM_MIB_INSTATEMODEERROR);
 			goto drop;
+		}
 
 		if (x->props.mode == XFRM_MODE_TUNNEL) {
 			decaps = 1;
 			break;
 		}
 
-		if ((err = xfrm_parse_spi(skb, skb->nh.iph->protocol, &spi, &seq)) < 0)
+		if ((err = xfrm_parse_spi(skb, skb->nh.iph->protocol, &spi, &seq)) < 0) {
+			XFRM_INC_STATS(XFRM_MIB_INHDRERROR);
 			goto drop;
+		}
 	} while (!err);
 
 	/* Allocate new secpath or COW existing one. */
@@ -122,14 +142,18 @@ int xfrm4_rcv_encap(struct sk_buff *skb, __u16 encap_type)
 	if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
 		struct sec_path *sp;
 		sp = secpath_dup(skb->sp);
-		if (!sp)
+		if (!sp) {
+			XFRM_INC_STATS(XFRM_MIB_INERROR);
 			goto drop;
+		}
 		if (skb->sp)
 			secpath_put(skb->sp);
 		skb->sp = sp;
 	}
-	if (xfrm_nr + skb->sp->len > XFRM_MAX_DEPTH)
+	if (xfrm_nr + skb->sp->len > XFRM_MAX_DEPTH) {
+		XFRM_INC_STATS(XFRM_MIB_INERROR);
 		goto drop;
+	}
 
 	memcpy(skb->sp->xvec + skb->sp->len, xfrm_vec,
 	       xfrm_nr * sizeof(xfrm_vec[0]));
diff --git a/net/ipv4/xfrm4_output.c b/net/ipv4/xfrm4_output.c
index 038ca16..a16dd1f 100644
--- a/net/ipv4/xfrm4_output.c
+++ b/net/ipv4/xfrm4_output.c
@@ -50,14 +50,18 @@ static int xfrm4_output_one(struct sk_buff *skb)
 
 	if (skb->ip_summed == CHECKSUM_PARTIAL) {
 		err = skb_checksum_help(skb);
-		if (err)
+		if (err) {
+			XFRM_INC_STATS(XFRM_MIB_OUTERROR);
 			goto error_nolock;
+		}
 	}
 
 	if (x->props.mode == XFRM_MODE_TUNNEL) {
 		err = xfrm4_tunnel_check_size(skb);
-		if (err)
+		if (err) {
+			XFRM_INC_STATS(XFRM_MIB_OUTLENGTHERROR);
 			goto error_nolock;
+		}
 	}
 
 	do {
@@ -67,12 +71,16 @@ static int xfrm4_output_one(struct sk_buff *skb)
 			goto error;
 
 		err = x->mode->output(x, skb);
-		if (err)
+		if (err) {
+			XFRM_INC_STATS(XFRM_MIB_OUTSTATEMODEERROR);
 			goto error;
+		}
 
 		err = x->type->output(x, skb);
-		if (err)
+		if (err) {
+			XFRM_INC_STATS(XFRM_MIB_OUTSTATEPROTOERROR);
 			goto error;
+		}
 
 		x->curlft.bytes += skb->len;
 		x->curlft.packets++;
@@ -80,6 +88,7 @@ static int xfrm4_output_one(struct sk_buff *skb)
 		spin_unlock_bh(&x->lock);
 
 		if (!(skb->dst = dst_pop(dst))) {
+			XFRM_INC_STATS(XFRM_MIB_OUTERROR);
 			err = -EHOSTUNREACH;
 			goto error_nolock;
 		}
diff --git a/net/ipv6/fib6_rules.c b/net/ipv6/fib6_rules.c
index 0862809..48c6d32 100644
--- a/net/ipv6/fib6_rules.c
+++ b/net/ipv6/fib6_rules.c
@@ -17,6 +17,7 @@
 
 #include <net/fib_rules.h>
 #include <net/ipv6.h>
+#include <net/addrconf.h>
 #include <net/ip6_route.h>
 #include <net/netlink.h>
 
@@ -95,8 +96,27 @@ static int fib6_rule_action(struct fib_rule *rule, struct flowi *flp,
 	if (table)
 		rt = lookup(table, flp, flags);
 
-	if (rt != &ip6_null_entry)
+	if (rt != &ip6_null_entry) {
+		struct fib6_rule *r = (struct fib6_rule *)rule;
+
+		/*
+		 * If we need to find a source address for this traffic,
+		 * we check the result if it meets requirement of the rule.
+		 */
+		if ((rule->flags & FIB_RULE_FIND_SADDR) &&
+		    r->src.plen && !(flags & RT6_LOOKUP_F_HAS_SADDR)) {
+			struct in6_addr saddr;
+			if (ipv6_get_saddr(&rt->u.dst, &flp->fl6_dst,
+					   &saddr))
+				goto again;
+			if (!ipv6_prefix_equal(&saddr, &r->src.addr,
+					       r->src.plen))
+				goto again;
+			ipv6_addr_copy(&flp->fl6_src, &saddr);
+		}
 		goto out;
+	}
+again:
 	dst_release(&rt->u.dst);
 	rt = NULL;
 	goto out;
@@ -117,9 +137,17 @@ static int fib6_rule_match(struct fib_rule *rule, struct flowi *fl, int flags)
 	    !ipv6_prefix_equal(&fl->fl6_dst, &r->dst.addr, r->dst.plen))
 		return 0;
 
+	/*
+	 * If FIB_RULE_FIND_SADDR is set and we do not have a
+	 * source address for the traffic, we defer check for
+	 * source address.
+	 */
 	if (r->src.plen) {
-		if (!(flags & RT6_LOOKUP_F_HAS_SADDR) ||
-		    !ipv6_prefix_equal(&fl->fl6_src, &r->src.addr, r->src.plen))
+		if (flags & RT6_LOOKUP_F_HAS_SADDR) {
+			if (!ipv6_prefix_equal(&fl->fl6_src, &r->src.addr,
+					       r->src.plen))
+				return 0;
+		} else if (!(r->common.flags & FIB_RULE_FIND_SADDR))
 			return 0;
 	}
 
diff --git a/net/ipv6/mip6.c b/net/ipv6/mip6.c
index 0afcabd..716c7bc 100644
--- a/net/ipv6/mip6.c
+++ b/net/ipv6/mip6.c
@@ -454,7 +454,7 @@ static struct xfrm_type mip6_rthdr_type =
 	.description	= "MIP6RT",
 	.owner		= THIS_MODULE,
 	.proto	     	= IPPROTO_ROUTING,
-	.flags		= XFRM_TYPE_NON_FRAGMENT,
+	.flags		= XFRM_TYPE_NON_FRAGMENT | XFRM_TYPE_MULTILOCAL,
 	.init_state	= mip6_rthdr_init_state,
 	.destructor	= mip6_rthdr_destroy,
 	.input		= mip6_rthdr_input,
diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c
index 3fb4427..6f40ba1 100644
--- a/net/ipv6/sysctl_net_ipv6.c
+++ b/net/ipv6/sysctl_net_ipv6.c
@@ -80,6 +80,7 @@ static ctl_table ipv6_table[] = {
 		.mode		= 0644,
 		.proc_handler	= &proc_dointvec
 	},
+
 	{ .ctl_name = 0 }
 };
 
diff --git a/net/ipv6/tunnel6.c b/net/ipv6/tunnel6.c
index 23e2809..f6c4503 100644
--- a/net/ipv6/tunnel6.c
+++ b/net/ipv6/tunnel6.c
@@ -25,6 +25,9 @@
 #include <linux/mutex.h>
 #include <linux/netdevice.h>
 #include <linux/skbuff.h>
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
 #include <net/ipv6.h>
 #include <net/protocol.h>
 #include <net/xfrm.h>
@@ -33,6 +36,44 @@ static struct xfrm6_tunnel *tunnel6_handlers;
 static struct xfrm6_tunnel *tunnel46_handlers;
 static DEFINE_MUTEX(tunnel6_mutex);
 
+static int sysctl_tunnel6_send_icmp_error __read_mostly = 1;
+
+#ifdef CONFIG_SYSCTL
+static struct ctl_table_header *tunnel6_sysctl_header;
+
+static ctl_table tunnel6_table[] = {
+	{
+		.ctl_name	= NET_IPV6_TUNNEL_SEND_ICMP_ERROR,
+		.procname	= "tunnel_send_icmp_error",
+		.data		= &sysctl_tunnel6_send_icmp_error,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &proc_dointvec
+	},
+        { .ctl_name = 0 }
+};
+
+static ctl_table ipv6_net_table[] = {
+	{
+		.ctl_name	= NET_IPV6,
+		.procname	= "ipv6",
+		.mode		= 0555,
+		.child		= tunnel6_table
+	},
+        { .ctl_name = 0 }
+};
+
+static ctl_table ipv6_root_table[] = {
+	{
+		.ctl_name	= CTL_NET,
+		.procname	= "net",
+		.mode		= 0555,
+		.child		= ipv6_net_table
+	},
+        { .ctl_name = 0 }
+};
+#endif
+
 int xfrm6_tunnel_register(struct xfrm6_tunnel *handler, unsigned short family)
 {
 	struct xfrm6_tunnel **pprev;
@@ -99,7 +140,9 @@ static int tunnel6_rcv(struct sk_buff **pskb)
 		if (!handler->handler(skb))
 			return 0;
 
-	icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0, skb->dev);
+	if (sysctl_tunnel6_send_icmp_error)
+		icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH,
+			    0, skb->dev);
 
 drop:
 	kfree_skb(skb);
@@ -149,6 +192,10 @@ static struct inet6_protocol tunnel46_protocol = {
 
 static int __init tunnel6_init(void)
 {
+#ifdef CONFIG_SYSCTL
+	tunnel6_sysctl_header = register_sysctl_table(ipv6_root_table);
+#endif
+
 	if (inet6_add_protocol(&tunnel6_protocol, IPPROTO_IPV6)) {
 		printk(KERN_ERR "tunnel6 init(): can't add protocol\n");
 		return -EAGAIN;
@@ -167,6 +214,10 @@ static void __exit tunnel6_fini(void)
 		printk(KERN_ERR "tunnel6 close: can't remove protocol\n");
 	if (inet6_del_protocol(&tunnel6_protocol, IPPROTO_IPV6))
 		printk(KERN_ERR "tunnel6 close: can't remove protocol\n");
+
+#ifdef CONFIG_SYSCTL
+	unregister_sysctl_table(tunnel6_sysctl_header);
+#endif
 }
 
 module_init(tunnel6_init);
diff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c
index 31f651f..7b46f6a 100644
--- a/net/ipv6/xfrm6_input.c
+++ b/net/ipv6/xfrm6_input.c
@@ -31,32 +31,47 @@ int xfrm6_rcv_spi(struct sk_buff *skb, __be32 spi)
 	nexthdr = skb->nh.raw[nhoff];
 
 	seq = 0;
-	if (!spi && (err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) != 0)
+	if (!spi && (err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) != 0) {
+		XFRM_INC_STATS(XFRM_MIB_INHDRERROR);
 		goto drop;
+	}
 
 	do {
 		struct ipv6hdr *iph = skb->nh.ipv6h;
 
-		if (xfrm_nr == XFRM_MAX_DEPTH)
+		if (xfrm_nr == XFRM_MAX_DEPTH) {
+			XFRM_INC_STATS(XFRM_MIB_INERROR);
 			goto drop;
+		}
 
 		x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi,
 				nexthdr != IPPROTO_IPIP ? nexthdr : IPPROTO_IPV6, AF_INET6);
-		if (x == NULL)
+		if (x == NULL) {
+			XFRM_INC_STATS(XFRM_MIB_INNOSTATES);
 			goto drop;
+		}
+
 		spin_lock(&x->lock);
-		if (unlikely(x->km.state != XFRM_STATE_VALID))
+		if (unlikely(x->km.state != XFRM_STATE_VALID)) {
+			XFRM_INC_STATS(XFRM_MIB_INSTATEINVALID);
 			goto drop_unlock;
+		}
 
-		if (x->props.replay_window && xfrm_replay_check(x, seq))
+		if (x->props.replay_window && xfrm_replay_check(x, seq)) {
+			XFRM_INC_STATS(XFRM_MIB_INSEQOUTOFWINDOW);
 			goto drop_unlock;
+		}
 
-		if (xfrm_state_check_expire(x))
+		if (xfrm_state_check_expire(x)) {
+			XFRM_INC_STATS(XFRM_MIB_INSTATEEXPIRED);
 			goto drop_unlock;
+		}
 
 		nexthdr = x->type->input(x, skb);
-		if (nexthdr <= 0)
+		if (nexthdr <= 0) {
+			XFRM_INC_STATS(XFRM_MIB_INSTATEPROTOERROR);
 			goto drop_unlock;
+		}
 
 		skb->nh.raw[nhoff] = nexthdr;
 
@@ -70,31 +85,39 @@ int xfrm6_rcv_spi(struct sk_buff *skb, __be32 spi)
 
 		xfrm_vec[xfrm_nr++] = x;
 
-		if (x->mode->input(x, skb))
+		if (x->mode->input(x, skb)) {
+			XFRM_INC_STATS(XFRM_MIB_INSTATEMODEERROR);
 			goto drop;
+		}
 
 		if (x->props.mode == XFRM_MODE_TUNNEL) { /* XXX */
 			decaps = 1;
 			break;
 		}
 
-		if ((err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) < 0)
+		if ((err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) < 0) {
+			XFRM_INC_STATS(XFRM_MIB_INHDRERROR);
 			goto drop;
+		}
 	} while (!err);
 
 	/* Allocate new secpath or COW existing one. */
 	if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
 		struct sec_path *sp;
 		sp = secpath_dup(skb->sp);
-		if (!sp)
+		if (!sp) {
+			XFRM_INC_STATS(XFRM_MIB_INERROR);
 			goto drop;
+		}
 		if (skb->sp)
 			secpath_put(skb->sp);
 		skb->sp = sp;
 	}
 
-	if (xfrm_nr + skb->sp->len > XFRM_MAX_DEPTH)
+	if (xfrm_nr + skb->sp->len > XFRM_MAX_DEPTH) {
+		XFRM_INC_STATS(XFRM_MIB_INERROR);
 		goto drop;
+	}
 
 	memcpy(skb->sp->xvec + skb->sp->len, xfrm_vec,
 	       xfrm_nr * sizeof(xfrm_vec[0]));
@@ -221,22 +244,28 @@ int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr,
 		break;
 	}
 
-	if (!xfrm_vec_one)
+	if (!xfrm_vec_one) {
+		XFRM_INC_STATS(XFRM_MIB_INNOSTATES);
 		goto drop;
+	}
 
 	/* Allocate new secpath or COW existing one. */
 	if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
 		struct sec_path *sp;
 		sp = secpath_dup(skb->sp);
-		if (!sp)
+		if (!sp) {
+			XFRM_INC_STATS(XFRM_MIB_INERROR);
 			goto drop;
+		}
 		if (skb->sp)
 			secpath_put(skb->sp);
 		skb->sp = sp;
 	}
 
-	if (1 + skb->sp->len > XFRM_MAX_DEPTH)
+	if (1 + skb->sp->len > XFRM_MAX_DEPTH) {
+		XFRM_INC_STATS(XFRM_MIB_INERROR);
 		goto drop;
+	}
 
 	skb->sp->xvec[skb->sp->len] = xfrm_vec_one;
 	skb->sp->len ++;
diff --git a/net/ipv6/xfrm6_output.c b/net/ipv6/xfrm6_output.c
index d6d786b..bedb6ca 100644
--- a/net/ipv6/xfrm6_output.c
+++ b/net/ipv6/xfrm6_output.c
@@ -49,14 +49,18 @@ static int xfrm6_output_one(struct sk_buff *skb)
 
 	if (skb->ip_summed == CHECKSUM_PARTIAL) {
 		err = skb_checksum_help(skb);
-		if (err)
+		if (err) {
+			XFRM_INC_STATS(XFRM_MIB_OUTERROR);
 			goto error_nolock;
+		}
 	}
 
 	if (x->props.mode == XFRM_MODE_TUNNEL) {
 		err = xfrm6_tunnel_check_size(skb);
-		if (err)
+		if (err) {
+			XFRM_INC_STATS(XFRM_MIB_OUTLENGTHERROR);
 			goto error_nolock;
+		}
 	}
 
 	do {
@@ -66,12 +70,16 @@ static int xfrm6_output_one(struct sk_buff *skb)
 			goto error;
 
 		err = x->mode->output(x, skb);
-		if (err)
+		if (err) {
+			XFRM_INC_STATS(XFRM_MIB_OUTSTATEMODEERROR);
 			goto error;
+		}
 
 		err = x->type->output(x, skb);
-		if (err)
+		if (err) {
+			XFRM_INC_STATS(XFRM_MIB_OUTSTATEPROTOERROR);
 			goto error;
+		}
 
 		x->curlft.bytes += skb->len;
 		x->curlft.packets++;
@@ -83,6 +91,7 @@ static int xfrm6_output_one(struct sk_buff *skb)
 		skb->nh.raw = skb->data;
 
 		if (!(skb->dst = dst_pop(dst))) {
+			XFRM_INC_STATS(XFRM_MIB_OUTERROR);
 			err = -EHOSTUNREACH;
 			goto error_nolock;
 		}
diff --git a/net/xfrm/Kconfig b/net/xfrm/Kconfig
index 577a4f8..b4fb2e4 100644
--- a/net/xfrm/Kconfig
+++ b/net/xfrm/Kconfig
@@ -35,6 +35,16 @@ config XFRM_MIGRATE
 
 	  If unsure, say N.
 
+config XFRM_STATISTICS
+	bool "Transformation statistics (EXPERIMENTAL)"
+	depends on INET && XFRM && PROC_FS && EXPERIMENTAL
+	---help---
+	  This statistics is not a SNMP/MIB specification but shows
+	  statistics about transformation error (or almost error) factor
+	  for developer.
+
+	  If unsure, say N.
+
 config NET_KEY
 	tristate "PF_KEY sockets"
 	select XFRM
diff --git a/net/xfrm/Makefile b/net/xfrm/Makefile
index de3c1a6..2d97a18 100644
--- a/net/xfrm/Makefile
+++ b/net/xfrm/Makefile
@@ -3,6 +3,6 @@
 #
 
 obj-$(CONFIG_XFRM) := xfrm_policy.o xfrm_state.o xfrm_hash.o \
-		      xfrm_input.o xfrm_algo.o
+		      xfrm_input.o xfrm_algo.o xfrm_proc.o
 obj-$(CONFIG_XFRM_USER) += xfrm_user.o
 
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 785c3e3..2573617 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -941,7 +941,9 @@ static int xfrm_policy_match(struct xfrm_policy *pol, struct flowi *fl,
 	int match, ret = -ESRCH;
 
 	if (pol->family != family ||
-	    pol->type != type)
+	    pol->type != type ||
+	    ((pol->flags & XFRM_POLICY_X_MULTILOCAL) &&
+	     (fl->flags & FLOWI_FLAG_MULTILOCAL)))
 		return ret;
 
 	match = xfrm_selector_match(sel, fl, family);
@@ -1349,6 +1351,9 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl,
 	int pi;
 	struct xfrm_state *xfrm[XFRM_MAX_DEPTH];
 	struct dst_entry *dst, *dst_orig = *dst_p;
+#ifdef CONFIG_XFRM_SUB_POLICY
+	struct xfrm_dst *xdst;
+#endif
 	int nx = 0;
 	int err;
 	u32 genid;
@@ -1394,6 +1399,7 @@ restart:
 	switch (policy->action) {
 	case XFRM_POLICY_BLOCK:
 		/* Prohibit the flow */
+		XFRM_INC_STATS(XFRM_MIB_OUTPOLBLOCK);
 		err = -EPERM;
 		goto error;
 
@@ -1413,6 +1419,7 @@ restart:
 		 */
 		dst = xfrm_find_bundle(fl, policy, family);
 		if (IS_ERR(dst)) {
+			XFRM_INC_STATS(XFRM_MIB_OUTBUNDLEERROR);
 			err = PTR_ERR(dst);
 			goto error;
 		}
@@ -1431,6 +1438,7 @@ restart:
 					goto error;
 				}
 				if (pols[1]->action == XFRM_POLICY_BLOCK) {
+					XFRM_INC_STATS(XFRM_MIB_OUTPOLBLOCK);
 					err = -EPERM;
 					goto error;
 				}
@@ -1469,6 +1477,7 @@ restart:
 				nx = xfrm_tmpl_resolve(pols, npols, fl, xfrm, family);
 
 				if (nx == -EAGAIN && signal_pending(current)) {
+					XFRM_INC_STATS(XFRM_MIB_OUTNOSTATES);
 					err = -ERESTART;
 					goto error;
 				}
@@ -1479,8 +1488,10 @@ restart:
 				}
 				err = nx;
 			}
-			if (err < 0)
+			if (err < 0) {
+				XFRM_INC_STATS(XFRM_MIB_OUTNOSTATES);
 				goto error;
+			}
 		}
 		if (nx == 0) {
 			/* Flow passes not transformed. */
@@ -1495,6 +1506,7 @@ restart:
 			int i;
 			for (i=0; i<nx; i++)
 				xfrm_state_put(xfrm[i]);
+			XFRM_INC_STATS(XFRM_MIB_OUTBUNDLEERROR);
 			goto error;
 		}
 
@@ -1515,9 +1527,19 @@ restart:
 			if (dst)
 				dst_free(dst);
 
+			XFRM_INC_STATS(XFRM_MIB_OUTBUNDLEERROR);
 			err = -EHOSTUNREACH;
 			goto error;
 		}
+#ifdef CONFIG_XFRM_SUB_POLICY
+		xdst = (struct xfrm_dst *)dst;
+		if (npols > 1) {
+			memcpy(&xdst->partner, &pols[1]->selector,
+			       sizeof(xdst->partner));
+			xdst->flags |= XFRM_DST_MULTIPOLICY;
+		} else
+			memcpy(&xdst->origin, fl, sizeof(xdst->origin));
+#endif
 		dst->next = policy->bundles;
 		policy->bundles = dst;
 		dst_hold(dst);
@@ -1583,7 +1605,8 @@ xfrm_state_ok(struct xfrm_tmpl *tmpl, struct xfrm_state *x,
  * Otherwise "-2 - errored_index" is returned.
  */
 static inline int
-xfrm_policy_ok(struct xfrm_tmpl *tmpl, struct sec_path *sp, int start,
+xfrm_policy_ok(struct flowi *fl, struct xfrm_policy *pol,
+	       struct xfrm_tmpl *tmpl, struct sec_path *sp, int start,
 	       unsigned short family)
 {
 	int idx = start;
@@ -1591,6 +1614,8 @@ xfrm_policy_ok(struct xfrm_tmpl *tmpl, struct sec_path *sp, int start,
 	if (tmpl->optional) {
 		if (tmpl->mode == XFRM_MODE_TRANSPORT)
 			return start;
+		else if (tmpl->mode == XFRM_MODE_IN_TRIGGER)
+			xfrm_state_trigger(fl, pol, tmpl, family);
 	} else
 		start = -1;
 	for (; idx < sp->len; idx++) {
@@ -1645,19 +1670,33 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
 	u8 fl_dir = policy_to_flow_dir(dir);
 	int xerr_idx = -1;
 
-	if (xfrm_decode_session(skb, &fl, family) < 0)
+	if (xfrm_decode_session(skb, &fl, family) < 0) {
+		XFRM_INC_STATS(XFRM_MIB_INHDRERROR);
 		return 0;
+	}
+
 	nf_nat_decode_session(skb, &fl, family);
 
 	/* First, check used SA against their selectors. */
 	if (skb->sp) {
+		int ml = 0;
 		int i;
 
 		for (i=skb->sp->len-1; i>=0; i--) {
 			struct xfrm_state *x = skb->sp->xvec[i];
-			if (!xfrm_selector_match(&x->sel, &fl, family))
+			if (!xfrm_selector_match(&x->sel, &fl, family)) {
+				XFRM_INC_STATS(XFRM_MIB_INSTATEINVALID);
 				return 0;
+			}
+
+			if (!ml && (x->type->flags & XFRM_TYPE_MULTILOCAL))
+				ml = 1;
+			else if (ml == 1 && x->props.mode == XFRM_MODE_TUNNEL)
+				ml = -1;
 		}
+
+		if (ml == 1)
+			fl.flags |= FLOWI_FLAG_MULTILOCAL;
 	}
 
 	pol = NULL;
@@ -1677,6 +1716,7 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
 	if (!pol) {
 		if (skb->sp && secpath_has_nontransport(skb->sp, 0, &xerr_idx)) {
 			xfrm_secpath_reject(xerr_idx, skb, &fl);
+			XFRM_INC_STATS(XFRM_MIB_INNOPOLS);
 			return 0;
 		}
 		return 1;
@@ -1714,10 +1754,14 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
 
 		for (pi = 0; pi < npols; pi++) {
 			if (pols[pi] != pol &&
-			    pols[pi]->action != XFRM_POLICY_ALLOW)
+			    pols[pi]->action != XFRM_POLICY_ALLOW) {
+				XFRM_INC_STATS(XFRM_MIB_INPOLBLOCK);
 				goto reject;
-			if (ti + pols[pi]->xfrm_nr >= XFRM_MAX_DEPTH)
+			}
+			if (ti + pols[pi]->xfrm_nr >= XFRM_MAX_DEPTH) {
+				XFRM_INC_STATS(XFRM_MIB_INERROR);
 				goto reject_error;
+			}
 			for (i = 0; i < pols[pi]->xfrm_nr; i++)
 				tpp[ti++] = &pols[pi]->xfrm_vec[i];
 		}
@@ -1734,21 +1778,25 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
 		 * are implied between each two transformations.
 		 */
 		for (i = xfrm_nr-1, k = 0; i >= 0; i--) {
-			k = xfrm_policy_ok(tpp[i], sp, k, family);
+			k = xfrm_policy_ok(&fl, pol, tpp[i], sp, k, family);
 			if (k < 0) {
 				if (k < -1)
 					/* "-2 - errored_index" returned */
 					xerr_idx = -(2+k);
+				XFRM_INC_STATS(XFRM_MIB_INTMPLMISMATCH);
 				goto reject;
 			}
 		}
 
-		if (secpath_has_nontransport(sp, k, &xerr_idx))
+		if (secpath_has_nontransport(sp, k, &xerr_idx)) {
+			XFRM_INC_STATS(XFRM_MIB_INTMPLMISMATCH);
 			goto reject;
+		}
 
 		xfrm_pols_put(pols, npols);
 		return 1;
 	}
+	XFRM_INC_STATS(XFRM_MIB_INPOLBLOCK);
 
 reject:
 	xfrm_secpath_reject(xerr_idx, skb, &fl);
@@ -1933,6 +1981,17 @@ int xfrm_bundle_ok(struct xfrm_policy *pol, struct xfrm_dst *first,
 	if (!dst_check(dst->path, ((struct xfrm_dst *)dst)->path_cookie) ||
 	    (dst->dev && !netif_running(dst->dev)))
 		return 0;
+#ifdef CONFIG_XFRM_SUB_POLICY
+	if (fl) {
+		if (first->flags & XFRM_DST_MULTIPOLICY) {
+			if (!xfrm_selector_match(&first->partner, fl, family))
+				return 0;
+		} else {
+			if (!flow_cache_uli_match(&first->origin, fl))
+				return 0;
+		}
+	}
+#endif
 
 	last = NULL;
 
@@ -2110,6 +2169,43 @@ void xfrm_audit_log(uid_t auid, u32 sid, int type, int result,
 EXPORT_SYMBOL(xfrm_audit_log);
 #endif /* CONFIG_AUDITSYSCALL */
 
+#ifdef CONFIG_XFRM_STATISTICS
+#include <net/snmp.h>
+
+DEFINE_SNMP_STAT(struct xfrm_mib, xfrm_statistics) __read_mostly;
+EXPORT_SYMBOL(xfrm_statistics);
+
+static int xfrm_statistics_init(void)
+{
+	xfrm_statistics[0] = alloc_percpu(struct xfrm_mib);
+	if (!xfrm_statistics[0])
+		goto err0;
+
+	xfrm_statistics[1] = alloc_percpu(struct xfrm_mib);
+	if (!xfrm_statistics[1])
+		goto err1;
+
+	return 0;
+
+err1:
+	free_percpu(xfrm_statistics[0]);
+	xfrm_statistics[0] = NULL;
+err0:
+	return -ENOMEM;
+}
+
+#if 0
+static void xfrm_statistics_cleanup(void)
+{
+	if (xfrm_statistics[0])
+		free_percpu(xfrm_statistics[0]);
+	if (xfrm_statistics[1])
+		free_percpu(xfrm_statistics[1]);
+	xfrm_statistics[0] = xfrm_statistics[1] = NULL;
+}
+#endif
+#endif
+
 int xfrm_policy_register_afinfo(struct xfrm_policy_afinfo *afinfo)
 {
 	int err = 0;
@@ -2250,9 +2346,13 @@ static void __init xfrm_policy_init(void)
 
 void __init xfrm_init(void)
 {
+#ifdef CONFIG_XFRM_STATISTICS
+	xfrm_statistics_init();
+#endif
 	xfrm_state_init();
 	xfrm_policy_init();
 	xfrm_input_init();
+	xfrm_proc_init();
 }
 
 #ifdef CONFIG_XFRM_MIGRATE
diff --git a/net/xfrm/xfrm_proc.c b/net/xfrm/xfrm_proc.c
new file mode 100644
index 0000000..1453aa9
--- /dev/null
+++ b/net/xfrm/xfrm_proc.c
@@ -0,0 +1,397 @@
+/*
+ * Author:	Masahide NAKAMURA <nakam@linux-ipv6.org>
+ *
+ *		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 <stdarg.h>
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <net/xfrm.h>
+#include <linux/in.h>
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+#include <linux/in6.h>
+#endif
+
+#define XFRM_SEQ_NUM_BUF 32
+#define XFRM_PROC_DEBUG
+
+#ifdef CONFIG_XFRM_STATISTICS
+#include <net/snmp.h>
+
+static struct snmp_mib xfrm_mib_list[] = {
+	SNMP_MIB_ITEM("XfrmInError", XFRM_MIB_INERROR),
+	SNMP_MIB_ITEM("XfrmInHdrError", XFRM_MIB_INHDRERROR),
+	SNMP_MIB_ITEM("XfrmInStateProtoError", XFRM_MIB_INSTATEPROTOERROR),
+	SNMP_MIB_ITEM("XfrmInStateModeError", XFRM_MIB_INSTATEMODEERROR),
+	SNMP_MIB_ITEM("XfrmInSeqOutOfWindow", XFRM_MIB_INSEQOUTOFWINDOW),
+	SNMP_MIB_ITEM("XfrmInStateExpired", XFRM_MIB_INSTATEEXPIRED),
+	SNMP_MIB_ITEM("XfrmInStateInvalid", XFRM_MIB_INSTATEINVALID),
+	SNMP_MIB_ITEM("XfrmInNoStates", XFRM_MIB_INNOSTATES),
+	SNMP_MIB_ITEM("XfrmInTmplMismatch", XFRM_MIB_INTMPLMISMATCH),
+	SNMP_MIB_ITEM("XfrmInPolBlock", XFRM_MIB_INPOLBLOCK),
+	SNMP_MIB_ITEM("XfrmInNoPols", XFRM_MIB_INNOPOLS),
+	SNMP_MIB_ITEM("XfrmOutError", XFRM_MIB_OUTERROR),
+	SNMP_MIB_ITEM("XfrmOutLengthError", XFRM_MIB_OUTLENGTHERROR),
+	SNMP_MIB_ITEM("XfrmOutStateProtoError", XFRM_MIB_OUTSTATEPROTOERROR),
+	SNMP_MIB_ITEM("XfrmOutStateModeError", XFRM_MIB_OUTSTATEMODEERROR),
+	SNMP_MIB_ITEM("XfrmOutStateExpired", XFRM_MIB_OUTSTATEEXPIRED),
+	SNMP_MIB_ITEM("XfrmOutNoStates", XFRM_MIB_OUTNOSTATES),
+	SNMP_MIB_ITEM("XfrmOutPolBlock", XFRM_MIB_OUTPOLBLOCK),
+	SNMP_MIB_ITEM("XfrmOutBundleError", XFRM_MIB_OUTBUNDLEERROR),
+	SNMP_MIB_SENTINEL
+};
+
+static unsigned long
+fold_field(void *mib[], int offt)
+{
+        unsigned long res = 0;
+        int i;
+
+        for_each_possible_cpu(i) {
+                res += *(((unsigned long *)per_cpu_ptr(mib[0], i)) + offt);
+                res += *(((unsigned long *)per_cpu_ptr(mib[1], i)) + offt);
+        }
+        return res;
+}
+
+static int xfrm_statistics_seq_show(struct seq_file *seq, void *v)
+{
+	int i;
+	for (i=0; xfrm_mib_list[i].name; i++)
+		seq_printf(seq, "%-24s\t%lu\n", xfrm_mib_list[i].name,
+			   fold_field((void **)xfrm_statistics,
+				      xfrm_mib_list[i].entry));
+	return 0;
+}
+
+static int xfrm_statistics_seq_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, xfrm_statistics_seq_show, NULL);
+}
+
+static struct file_operations proc_net_xfrm_stats_seq_fops = {
+	.owner	 = THIS_MODULE,
+	.open	 = xfrm_statistics_seq_open,
+	.read	 = seq_read,
+	.llseek	 = seq_lseek,
+	.release = single_release,
+};
+
+static struct proc_dir_entry *proc_net_xfrm_stats;
+
+#endif /* CONFIG_XFRM_STATISTICS */
+
+#ifdef XFRM_PROC_DEBUG
+#include <net/neighbour.h>
+#include <linux/if_arp.h>
+#endif
+
+#ifdef CONFIG_PROC_FS
+
+static char* xfrm_seq_sprintf(char *buf, const char *fmt, ...)
+{
+	va_list args;
+	int i;
+
+	va_start(args, fmt);
+	i = vsnprintf(buf, INT_MAX, fmt, args);
+	va_end(args);
+	return buf;
+}
+
+#ifdef XFRM_PROC_DEBUG
+static int __xfrm_bundle_neigh_seq_print(struct seq_file *seq,
+					 struct neighbour *n)
+{
+	if (n->tbl) {
+		switch (n->tbl->family) {
+		case AF_INET:
+		{
+			struct in_addr *addr;
+			if (n->tbl->key_len >= sizeof(*addr)) {
+				addr = (struct in_addr *)n->primary_key;
+				seq_printf(seq, NIPQUAD_FMT, NIPQUAD(*addr));
+			}
+			break;
+		}
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+		case AF_INET6:
+		{
+			struct in6_addr *addr;
+			if (n->tbl->key_len >= sizeof(*addr)) {
+				addr = (struct in6_addr *)n->primary_key;
+				seq_printf(seq, NIP6_FMT, NIP6(*addr));
+			}
+			break;
+		}
+#endif
+		default:
+			break;
+		}
+	}
+
+	if (n->dev) {
+		int i;
+
+		seq_printf(seq, " dev %s", n->dev->name);
+
+		switch (n->type) {
+		case ARPHRD_ETHER:
+			seq_printf(seq, " lladdr ");
+			for (i = 0; i < n->dev->addr_len; i++) {
+				if (i > 0)
+					seq_printf(seq, ":");
+				seq_printf(seq, "%02x", n->ha[i]);
+			}
+			break;
+		/* XXX: other types should be written */
+		default:
+			seq_printf(seq, " %u", n->type);
+			break;
+		}
+	}
+
+	return 0;
+}
+#endif
+
+static int __xfrm_bundle_state_seq_print(struct seq_file *seq,
+					 struct xfrm_state *x)
+{
+	char buf[XFRM_SEQ_NUM_BUF];
+
+	seq_printf(seq, "%-3u %s", x->id.proto,
+		   ((x->props.mode == XFRM_MODE_TRANSPORT) ? "transport" :
+		    (x->props.mode == XFRM_MODE_TUNNEL) ? "tunnel" :
+		    (x->props.mode == XFRM_MODE_ROUTEOPTIMIZATION) ? "ro" :
+		    (x->props.mode == XFRM_MODE_IN_TRIGGER) ? "in_trigger" :
+		    (x->props.mode == XFRM_MODE_BEET) ? "beet" :
+		    xfrm_seq_sprintf(buf, "%u", x->props.mode)));
+
+	switch (x->props.family) {
+	case AF_INET:
+		seq_printf(seq, " " NIPQUAD_FMT " " NIPQUAD_FMT,
+			   NIPQUAD(*(struct in_addr *)&x->props.saddr),
+			   NIPQUAD(*(struct in_addr *)&x->id.daddr));
+		if (x->coaddr)
+			seq_printf(seq, " coa " NIPQUAD_FMT,
+				   NIPQUAD(*(struct in_addr *)x->coaddr));
+		break;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+	case AF_INET6:
+		seq_printf(seq, " " NIP6_FMT " " NIP6_FMT,
+			   NIP6(*(struct in6_addr *)&x->props.saddr),
+			   NIP6(*(struct in6_addr *)&x->id.daddr));
+		if (x->coaddr)
+			seq_printf(seq, " coa " NIP6_FMT,
+				   NIP6(*(struct in6_addr *)x->coaddr));
+		break;
+#endif
+	default:
+		break;
+	}
+
+	if (xfrm_id_proto_match(x->id.proto, IPSEC_PROTO_ANY))
+		seq_printf(seq, " spi 0x%08x", ntohl(x->id.spi));
+
+	seq_printf(seq, " ref %u", atomic_read(&x->refcnt));
+
+	return 0;
+}
+
+static int __xfrm_bundle_xdst_seq_print(struct seq_file *seq,
+					struct xfrm_dst *xdst)
+{
+	if (!xdst->u.dst.ops)
+		return 0;
+
+	switch (xdst->u.dst.ops->family) {
+	case AF_INET:
+		seq_printf(seq, " rt");
+		/* XXX: should be written */
+		break;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+	case AF_INET6:
+	{
+		struct rt6_info *rt6i = &xdst->u.rt6;
+
+		seq_printf(seq, " rt6");
+
+		seq_printf(seq, " " NIP6_FMT "/%d", NIP6(rt6i->rt6i_dst.addr),
+			   rt6i->rt6i_dst.plen);
+		if (rt6i->rt6i_src.plen) {
+			seq_printf(seq, " src " NIP6_FMT "/%d",
+				   NIP6(rt6i->rt6i_src.addr),
+				   rt6i->rt6i_src.plen);
+		}
+		seq_printf(seq, " via " NIP6_FMT,
+			   NIP6(rt6i->rt6i_gateway));
+		seq_printf(seq, " dev %s %s",
+			   ((rt6i->rt6i_idev && rt6i->rt6i_idev->dev) ?
+			    rt6i->rt6i_idev->dev->name : "null"),
+			   ((rt6i->rt6i_flags&RTF_CACHE)?"cache":""));
+		seq_printf(seq, " sernum %u",
+			   (rt6i->rt6i_node ? rt6i->rt6i_node->fn_sernum : 0));
+		seq_printf(seq, " ref %u", atomic_read(&rt6i->rt6i_ref));
+		break;
+	}
+#endif
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int __xfrm_bundle_seq_show_dst(struct seq_file *seq,
+				      struct xfrm_dst *xdst, int gen)
+{
+	seq_printf(seq, "  gen %d", gen);
+	seq_printf(seq, " ref %u", atomic_read(&xdst->u.dst.__refcnt));
+	if (!gen) {
+		seq_printf(seq, " rtcook %u", xdst->route_cookie);
+		seq_printf(seq, " ptcook %u", xdst->path_cookie);
+	}
+	seq_printf(seq, "\n");
+
+	if (xdst->u.dst.ops) {
+		seq_printf(seq, "    dst ");
+		__xfrm_bundle_xdst_seq_print(seq, xdst);
+		seq_printf(seq, "\n");
+
+#ifdef XFRM_PROC_DEBUG
+		if (xdst->u.dst.neighbour) {
+			struct neighbour *n = xdst->u.dst.neighbour;
+			seq_printf(seq, "    neigh ");
+			read_lock(&n->lock);
+			__xfrm_bundle_neigh_seq_print(seq, n);
+			read_unlock(&n->lock);
+			seq_printf(seq, "\n");
+		}
+#endif
+	}
+	if (xdst->u.dst.xfrm) {
+		struct xfrm_state *x = xdst->u.dst.xfrm;
+		seq_printf(seq, "    xfrm ");
+		spin_lock(&x->lock);
+		__xfrm_bundle_state_seq_print(seq, x);
+		spin_unlock(&x->lock);
+		seq_printf(seq, "\n");
+	}
+	return 0;
+}
+
+static int xfrm_bundle_seq_show_one(struct xfrm_policy *xp, int dir, int count,
+				    void *data)
+{
+	struct seq_file *seq = (struct seq_file *)data;
+	char buf[XFRM_SEQ_NUM_BUF];
+	struct dst_entry *dst;
+
+	read_lock_bh(&xp->lock);
+
+	if (!xp->bundles)
+		goto out;
+
+	seq_printf(seq, "%-4s %011u %-3s\n",
+		   ((xp->type == XFRM_POLICY_TYPE_MAIN) ? "main" :
+		    (xp->type == XFRM_POLICY_TYPE_SUB) ? "sub" :
+		    xfrm_seq_sprintf(buf, "%u", xp->type)),
+		   xp->index,
+		   ((dir == XFRM_POLICY_IN) ? "in" :
+		    (dir == XFRM_POLICY_OUT) ? "out" :
+		    (dir == XFRM_POLICY_FWD) ? "fwd" :
+		    xfrm_seq_sprintf(buf, "%u", dir)));
+
+	for (dst = xp->bundles; dst; dst = dst->next) {
+		struct dst_entry *d = dst;
+		int gen = 0;
+
+		for (d = dst; d; d = d->child) {
+			struct xfrm_dst *xdst = (struct xfrm_dst*)d;
+			__xfrm_bundle_seq_show_dst(seq, xdst, gen++);
+		}
+		seq_printf(seq, "\n"); /* for delimiter */
+	}
+
+ out:
+	read_unlock_bh(&xp->lock);
+	return 0;
+}
+
+static int xfrm_bundle_seq_show(struct seq_file *seq, void *v)
+{
+	xfrm_policy_walk(XFRM_POLICY_TYPE_MAIN, xfrm_bundle_seq_show_one, seq);
+#ifdef CONFIG_XFRM_SUB_POLICY
+	xfrm_policy_walk(XFRM_POLICY_TYPE_SUB,  xfrm_bundle_seq_show_one, seq);
+#endif
+	return 0;
+}
+
+static int xfrm_bundle_seq_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, xfrm_bundle_seq_show, NULL);
+}
+
+static struct file_operations proc_net_xfrm_bundle_seq_fops = {
+	.owner	 = THIS_MODULE,
+	.open	 = xfrm_bundle_seq_open,
+	.read	 = seq_read,
+	.llseek	 = seq_lseek,
+	.release = single_release,
+};
+
+static struct proc_dir_entry *proc_net_xfrm;
+static struct proc_dir_entry *proc_net_xfrm_bundle;
+
+int __init xfrm_proc_init(void)
+{
+	int rc = 0;
+
+	proc_net_xfrm = proc_mkdir("xfrm", proc_net);
+	if (!proc_net_xfrm)
+		goto proc_net_xfrm_fail;
+
+	proc_net_xfrm_bundle = create_proc_entry("bundle", S_IRUGO,
+						 proc_net_xfrm);
+	if (!proc_net_xfrm_bundle)
+		goto proc_net_xfrm_bundle_fail;
+
+	proc_net_xfrm_bundle->proc_fops = &proc_net_xfrm_bundle_seq_fops;
+
+#ifdef CONFIG_XFRM_STATISTICS
+	proc_net_xfrm_stats = create_proc_entry("stats", S_IRUGO,
+						proc_net_xfrm);
+	if (!proc_net_xfrm_stats)
+		goto proc_net_xfrm_stats_fail;
+
+	proc_net_xfrm_stats->proc_fops = &proc_net_xfrm_stats_seq_fops;
+#endif
+
+ out:
+	return rc;
+
+#ifdef CONFIG_XFRM_STATISTICS
+ proc_net_xfrm_stats_fail:
+	remove_proc_entry("bundle", proc_net_xfrm);
+#endif
+ proc_net_xfrm_bundle_fail:
+	remove_proc_entry("xfrm", proc_net);
+ proc_net_xfrm_fail:
+	rc = -ENOMEM;
+	goto out;
+}
+
+#if 0
+void xfrm_proc_exit(void)
+{
+	remove_proc_entry("bundle", proc_net_xfrm);
+	remove_proc_entry("xfrm", proc_net);
+}
+#endif
+
+#endif	/* CONFIG_PROC_FS */
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 5c5f6dc..473804f 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -522,6 +522,85 @@ static void xfrm_hash_grow_check(int have_hash_collision)
 		schedule_work(&xfrm_hash_work);
 }
 
+static inline int
+__xfrm_state_query(struct flowi *fl, struct xfrm_policy *pol,
+		   struct xfrm_tmpl *tmpl, xfrm_address_t *daddr,
+		   xfrm_address_t *saddr, unsigned short family,
+		   struct xfrm_state **statep)
+{
+	struct xfrm_state *x;
+	unsigned int h;
+	int error = 0;
+
+	x = xfrm_state_alloc();
+	if (x == NULL) {
+		error = -ENOMEM;
+		goto out;
+	}
+	/* Initialize temporary selector matching only
+	 * to current session. */
+	xfrm_init_tempsel(x, fl, tmpl, daddr, saddr, family);
+
+	error = security_xfrm_state_alloc_acquire(x, pol->security, fl->secid);
+	if (error) {
+		x->km.state = XFRM_STATE_DEAD;
+		xfrm_state_put(x);
+		x = NULL;
+		goto out;
+	}
+
+	error = km_query(x, tmpl, pol);
+	if (error) {
+		x->km.state = XFRM_STATE_DEAD;
+		xfrm_state_put(x);
+		x = NULL;
+		error = -ESRCH;
+		goto out;
+	}
+
+	x->km.state = XFRM_STATE_ACQ;
+	h = xfrm_dst_hash(daddr, saddr, tmpl->reqid, family);
+	hlist_add_head(&x->bydst, xfrm_state_bydst+h);
+	h = xfrm_src_hash(daddr, saddr, family);
+	hlist_add_head(&x->bysrc, xfrm_state_bysrc+h);
+	if (x->id.spi) {
+		h = xfrm_spi_hash(&x->id.daddr, x->id.spi, x->id.proto, family);
+		hlist_add_head(&x->byspi, xfrm_state_byspi+h);
+	}
+	x->lft.hard_add_expires_seconds = XFRM_ACQ_EXPIRES;
+	x->timer.expires = jiffies + XFRM_ACQ_EXPIRES*HZ;
+	add_timer(&x->timer);
+	xfrm_state_num++;
+	xfrm_hash_grow_check(x->bydst.next != NULL);
+
+ out:
+	if (statep)
+		*statep = x;
+	return error;
+
+}
+
+int xfrm_state_trigger(struct flowi *fl, struct xfrm_policy *pol,
+		       struct xfrm_tmpl *tmpl, unsigned short family)
+{
+	xfrm_address_t *daddr = xfrm_flowi_daddr(fl, family);
+	xfrm_address_t *saddr = xfrm_flowi_saddr(fl, family);
+	struct xfrm_state *x0;
+	int err;
+
+	x0 = __xfrm_state_lookup_byaddr(daddr, saddr, tmpl->id.proto, family);
+	if (x0 != NULL) {
+		xfrm_state_put(x0);
+		err = -EEXIST;
+		goto out;
+	}
+
+	err = __xfrm_state_query(fl, pol, tmpl, daddr, saddr, family, NULL);
+
+ out:
+	return err;
+}
+
 struct xfrm_state *
 xfrm_state_find(xfrm_address_t *daddr, xfrm_address_t *saddr,
 		struct flowi *fl, struct xfrm_tmpl *tmpl,
@@ -586,43 +665,9 @@ xfrm_state_find(xfrm_address_t *daddr, xfrm_address_t *saddr,
 			error = -EEXIST;
 			goto out;
 		}
-		x = xfrm_state_alloc();
-		if (x == NULL) {
-			error = -ENOMEM;
-			goto out;
-		}
-		/* Initialize temporary selector matching only
-		 * to current session. */
-		xfrm_init_tempsel(x, fl, tmpl, daddr, saddr, family);
-
-		error = security_xfrm_state_alloc_acquire(x, pol->security, fl->secid);
-		if (error) {
-			x->km.state = XFRM_STATE_DEAD;
-			xfrm_state_put(x);
-			x = NULL;
-			goto out;
-		}
 
-		if (km_query(x, tmpl, pol) == 0) {
-			x->km.state = XFRM_STATE_ACQ;
-			hlist_add_head(&x->bydst, xfrm_state_bydst+h);
-			h = xfrm_src_hash(daddr, saddr, family);
-			hlist_add_head(&x->bysrc, xfrm_state_bysrc+h);
-			if (x->id.spi) {
-				h = xfrm_spi_hash(&x->id.daddr, x->id.spi, x->id.proto, family);
-				hlist_add_head(&x->byspi, xfrm_state_byspi+h);
-			}
-			x->lft.hard_add_expires_seconds = XFRM_ACQ_EXPIRES;
-			x->timer.expires = jiffies + XFRM_ACQ_EXPIRES*HZ;
-			add_timer(&x->timer);
-			xfrm_state_num++;
-			xfrm_hash_grow_check(x->bydst.next != NULL);
-		} else {
-			x->km.state = XFRM_STATE_DEAD;
-			xfrm_state_put(x);
-			x = NULL;
-			error = -ESRCH;
-		}
+		error = __xfrm_state_query(fl, pol, tmpl, daddr, saddr, family,
+					   &x);
 	}
 out:
 	if (x)
@@ -1088,9 +1133,13 @@ static int xfrm_state_check_space(struct xfrm_state *x, struct sk_buff *skb)
 int xfrm_state_check(struct xfrm_state *x, struct sk_buff *skb)
 {
 	int err = xfrm_state_check_expire(x);
-	if (err < 0)
+	if (err < 0) {
+		XFRM_INC_STATS(XFRM_MIB_OUTSTATEEXPIRED);
 		goto err;
+	}
 	err = xfrm_state_check_space(x, skb);
+	if (err)
+		XFRM_INC_STATS(XFRM_MIB_OUTLENGTHERROR);
 err:
 	return err;
 }
