From c675752f79918f8a0c04e30fb4e24fe43e52752d Mon Sep 17 00:00:00 2001
From: Matteo Croce <matteo@openwrt.org>
Date: Mon, 10 Jul 2017 16:23:18 +0200
Subject: [PATCH 2/3] add stealth mode

Add option to disable any reply not related to a listening socket,
like RST/ACK for TCP and ICMP Port-Unreachable for UDP.
Also disables ICMP replies to echo request and timestamp.
The stealth mode can be enabled selectively for a single interface.
---
 Documentation/networking/ip-sysctl.txt | 14 ++++++++++++++
 include/linux/inetdevice.h             |  1 +
 include/linux/ipv6.h                   |  1 +
 include/uapi/linux/ip.h                |  1 +
 net/ipv4/devinet.c                     |  1 +
 net/ipv4/icmp.c                        |  6 ++++++
 net/ipv4/ip_input.c                    |  5 +++--
 net/ipv4/tcp_ipv4.c                    |  3 ++-
 net/ipv4/udp.c                         |  4 +++-
 net/ipv6/addrconf.c                    |  7 +++++++
 net/ipv6/icmp.c                        |  3 ++-
 net/ipv6/ip6_input.c                   |  5 +++--
 net/ipv6/tcp_ipv6.c                    |  2 +-
 net/ipv6/udp.c                         |  3 ++-
 14 files changed, 47 insertions(+), 9 deletions(-)

diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt
index 974ab47ae53a..0f4355be47a5 100644
--- a/Documentation/networking/ip-sysctl.txt
+++ b/Documentation/networking/ip-sysctl.txt
@@ -1299,6 +1299,13 @@ igmp_link_local_mcast_reports - BOOLEAN
 	224.0.0.X range.
 	Default TRUE
 
+stealth - BOOLEAN
+	Disable any reply not related to a listening socket,
+	like RST/ACK for TCP and ICMP Port-Unreachable for UDP.
+	Also disables ICMP replies to echo requests and timestamp
+	and ICMP errors for unknown protocols.
+	Default value is 0.
+
 Alexey Kuznetsov.
 kuznet@ms2.inr.ac.ru
 
@@ -1768,6 +1775,13 @@ enhanced_dad - BOOLEAN
 	conf/{all,interface}/enhanced_dad are set to FALSE.
 	Default: TRUE
 
+stealth - BOOLEAN
+	Disable any reply not related to a listening socket,
+	like RST/ACK for TCP and ICMP Port-Unreachable for UDP.
+	Also disables ICMPv6 replies to echo requests
+	and ICMP errors for unknown protocols.
+	Default value is 0.
+
 icmp/*:
 ratelimit - INTEGER
 	Limit the maximal rates for sending ICMPv6 packets.
diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h
index a2e9d6ea1349..283504fcc83e 100644
--- a/include/linux/inetdevice.h
+++ b/include/linux/inetdevice.h
@@ -128,6 +128,7 @@ static inline void ipv4_devconf_setall(struct in_device *in_dev)
 #define IN_DEV_ARP_ANNOUNCE(in_dev)	IN_DEV_MAXCONF((in_dev), ARP_ANNOUNCE)
 #define IN_DEV_ARP_IGNORE(in_dev)	IN_DEV_MAXCONF((in_dev), ARP_IGNORE)
 #define IN_DEV_ARP_NOTIFY(in_dev)	IN_DEV_MAXCONF((in_dev), ARP_NOTIFY)
+#define IN_DEV_STEALTH(in_dev)		IN_DEV_MAXCONF((in_dev), STEALTH)
 
 struct in_ifaddr {
 	struct hlist_node	hash;
diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
index e1b442996f81..8472d0684954 100644
--- a/include/linux/ipv6.h
+++ b/include/linux/ipv6.h
@@ -58,6 +58,7 @@ struct ipv6_devconf {
 	__s32           ndisc_notify;
 	__s32		suppress_frag_ndisc;
 	__s32		accept_ra_mtu;
+	__s32		stealth;
 	__s32		drop_unsolicited_na;
 	struct ipv6_stable_secret {
 		bool initialized;
diff --git a/include/uapi/linux/ip.h b/include/uapi/linux/ip.h
index f291569768dd..682cf0c9085c 100644
--- a/include/uapi/linux/ip.h
+++ b/include/uapi/linux/ip.h
@@ -167,6 +167,7 @@ enum
 	IPV4_DEVCONF_IGNORE_ROUTES_WITH_LINKDOWN,
 	IPV4_DEVCONF_DROP_UNICAST_IN_L2_MULTICAST,
 	IPV4_DEVCONF_DROP_GRATUITOUS_ARP,
+	IPV4_DEVCONF_STEALTH,
 	__IPV4_DEVCONF_MAX
 };
 
diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c
index df14815a3b8c..73a246089ac3 100644
--- a/net/ipv4/devinet.c
+++ b/net/ipv4/devinet.c
@@ -2247,6 +2247,7 @@ static struct devinet_sysctl_table {
 					      "route_localnet"),
 		DEVINET_SYSCTL_FLUSHING_ENTRY(DROP_UNICAST_IN_L2_MULTICAST,
 					      "drop_unicast_in_l2_multicast"),
+		DEVINET_SYSCTL_RW_ENTRY(STEALTH, "stealth"),
 	},
 };
 
diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
index 9144fa7df2ad..7129be4b67fd 100644
--- a/net/ipv4/icmp.c
+++ b/net/ipv4/icmp.c
@@ -922,6 +922,9 @@ static bool icmp_echo(struct sk_buff *skb)
 {
 	struct net *net;
 
+	if (IN_DEV_STEALTH(__in_dev_get_rcu(skb->dev)))
+		return true;
+
 	net = dev_net(skb_dst(skb)->dev);
 	if (!net->ipv4.sysctl_icmp_echo_ignore_all) {
 		struct icmp_bxm icmp_param;
@@ -954,6 +957,9 @@ static bool icmp_timestamp(struct sk_buff *skb)
 	if (skb->len < 4)
 		goto out_err;
 
+	if (IN_DEV_STEALTH(__in_dev_get_rcu(skb->dev)))
+		return true;
+
 	/*
 	 *	Fill in the current time as ms since midnight UT:
 	 */
diff --git a/net/ipv4/ip_input.c b/net/ipv4/ip_input.c
index fa2dc8f692c6..12f1dfd49a5b 100644
--- a/net/ipv4/ip_input.c
+++ b/net/ipv4/ip_input.c
@@ -223,8 +223,9 @@ static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_b
 			if (!raw) {
 				if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
 					__IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS);
-					icmp_send(skb, ICMP_DEST_UNREACH,
-						  ICMP_PROT_UNREACH, 0);
+					if (!IN_DEV_STEALTH(__in_dev_get_rcu(skb->dev)))
+						icmp_send(skb, ICMP_DEST_UNREACH,
+							ICMP_PROT_UNREACH, 0);
 				}
 				kfree_skb(skb);
 			} else {
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 5ab2aac5ca19..30bdea59b29a 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -76,6 +76,7 @@
 #include <net/busy_poll.h>
 
 #include <linux/inet.h>
+#include <linux/inetdevice.h>
 #include <linux/ipv6.h>
 #include <linux/stddef.h>
 #include <linux/proc_fs.h>
@@ -1734,7 +1735,7 @@ int tcp_v4_rcv(struct sk_buff *skb)
 		__TCP_INC_STATS(net, TCP_MIB_CSUMERRORS);
 bad_packet:
 		__TCP_INC_STATS(net, TCP_MIB_INERRS);
-	} else {
+	} else if (!IN_DEV_STEALTH(__in_dev_get_rcu(skb->dev))) {
 		tcp_v4_send_reset(NULL, skb);
 	}
 
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 1d6219bf2d6b..fbe4b3e02e20 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -96,6 +96,7 @@
 #include <linux/timer.h>
 #include <linux/mm.h>
 #include <linux/inet.h>
+#include <linux/inetdevice.h>
 #include <linux/netdevice.h>
 #include <linux/slab.h>
 #include <net/tcp_states.h>
@@ -1953,7 +1954,8 @@ int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
 		goto csum_error;
 
 	__UDP_INC_STATS(net, UDP_MIB_NOPORTS, proto == IPPROTO_UDPLITE);
-	icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
+	if (!IN_DEV_STEALTH(__in_dev_get_rcu(skb->dev)))
+		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
 
 	/*
 	 * Hmm.  We got an UDP packet to a port to which we
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 1d2dbace42ff..fa84c7abfb79 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -6361,6 +6361,13 @@ static const struct ctl_table addrconf_sysctl[] = {
 		.proc_handler   = addrconf_sysctl_disable_policy,
 	},
 	{
+		.procname	= "stealth",
+		.data		= &ipv6_devconf.stealth,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec,
+	},
+	{
 		/* sentinel */
 	}
 };
diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c
index 8d7b113958b1..2ee00082b443 100644
--- a/net/ipv6/icmp.c
+++ b/net/ipv6/icmp.c
@@ -836,7 +836,8 @@ static int icmpv6_rcv(struct sk_buff *skb)
 
 	switch (type) {
 	case ICMPV6_ECHO_REQUEST:
-		icmpv6_echo_reply(skb);
+		if (!idev->cnf.stealth)
+			icmpv6_echo_reply(skb);
 		break;
 
 	case ICMPV6_ECHO_REPLY:
diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c
index 9ee208a348f5..0a5050245460 100644
--- a/net/ipv6/ip6_input.c
+++ b/net/ipv6/ip6_input.c
@@ -302,8 +302,9 @@ static int ip6_input_finish(struct net *net, struct sock *sk, struct sk_buff *sk
 			if (xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) {
 				__IP6_INC_STATS(net, idev,
 						IPSTATS_MIB_INUNKNOWNPROTOS);
-				icmpv6_send(skb, ICMPV6_PARAMPROB,
-					    ICMPV6_UNK_NEXTHDR, nhoff);
+				if (!idev->cnf.stealth)
+					icmpv6_send(skb, ICMPV6_PARAMPROB,
+						ICMPV6_UNK_NEXTHDR, nhoff);
 			}
 			kfree_skb(skb);
 		} else {
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index 4f4310a36a04..3bfe62cb0d4e 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -1515,7 +1515,7 @@ static int tcp_v6_rcv(struct sk_buff *skb)
 		__TCP_INC_STATS(net, TCP_MIB_CSUMERRORS);
 bad_packet:
 		__TCP_INC_STATS(net, TCP_MIB_INERRS);
-	} else {
+	} else if (!__in6_dev_get(skb->dev)->cnf.stealth) {
 		tcp_v6_send_reset(NULL, skb);
 	}
 
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 75703fda23e7..bf20c06871c7 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -845,7 +845,8 @@ int __udp6_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
 		goto csum_error;
 
 	__UDP6_INC_STATS(net, UDP_MIB_NOPORTS, proto == IPPROTO_UDPLITE);
-	icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0);
+	if (!__in6_dev_get(skb->dev)->cnf.stealth)
+		icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0);
 
 	kfree_skb(skb);
 	return 0;
-- 
2.13.2

