From c099b1dcac7c639771a09cce77e47980a762ae70 Mon Sep 17 00:00:00 2001
From: Masahide NAKAMURA <nakam@linux-ipv6.org>
Date: Mon, 19 Feb 2007 19:36:23 +0900
Subject: [PATCH] [XFRM] TUNNEL: Add sysctl to send ICMP error when no tunnel found.

Currently kernel tunnel common part (net/{ipv4/tunnel4.c,ipv6/tunnel6.c})
sends ICMP error (port unreachable) back when it is not found any
mathcing configuration for received tunnel packet.

It doesn't seem to be clarified by any RFCs how node acts
at this case. I agree to send something errored as default.

With this patch we can omit to send ICMP error when setting
/proc/sys/net/ipv[46]/tunnel_send_icmp_error to 0.
The default value is 1 to keep the current behavior.

Background:

Some people (TAHI Project) believe such ICMP error should not be
sent by Mobile IPv6 Home Agent (HA) (See their test specification:
TAHI Conformance Test For Home Agent HA_6_2_1).
Fortunately, HA probably can drop the unexpected tunnel packet
by IPsec SP since it should be an IPsec gateway and we can pass the
test; On linux we would make it to add bypass SP for each Mobile Node
and to set a discard SP as the last resort.
However, tunnel gateway is not always an IPsec gateway.
---
 include/linux/sysctl.h     |    2 +
 net/ipv4/tunnel4.c         |   52 ++++++++++++++++++++++++++++++++++++++++++-
 net/ipv6/sysctl_net_ipv6.c |    1 +
 net/ipv6/tunnel6.c         |   53 +++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 106 insertions(+), 2 deletions(-)

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/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/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);
-- 
1.5.0.3

