[RFC] ipv4: add ICMP socket kind

This patch adds IPPROTO_ICMP socket kind.  It makes it possible to send
ICMP_ECHO messages and receive corresponding ICMP_ECHOREPLY messages
without any special privileges.  In other words, the patch makes it
possible to implement setuid-less /bin/ping.

A new ping socket is created with

    socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP)

Message identifiers (octets 4-5 of ICMP header) are interpreted as local
ports. Addresses are stored in struct sockaddr_in. No port numbers are
reserved for privileged processes, port 0 is reserved for API ("let the
kernel pick a free number"). There is no notion of remote ports, remote
port numbers provided by the user (e.g. in connect()) are ignored.

Data sent and received include ICMP headers. This is deliberate to:
1) Avoid the need to transport headers values like sequence numbers by other means
2) Make it easier to port existing programs using raw sockets.

ICMP headers given to send() are checked and sanitized. The type
must be ICMP_ECHO and the code must be zero (future extensions might relax
this, see below). The id is set to the number (local port) of the socket,
the checksum is always recomputed.

ICMP reply packets received from the network are demultiplexed according
to their id's and returned by recv() without any modifications.
IP header information and ICMP errors of those packets may be obtained
via ancillary data (IP_RECVTTL, IP_RETOPTS, and IP_RECVERR). ICMP source
quenches and redirects are reported as fake errors via the error queue
(IP_RECVERR); the next hop address for redirects is saved to ee_info (in
network order).

The existing code might be (in the unlikely case anyone needs it)
extended rather easily to handle other similar pairs of ICMP messages
(Timestamp/Reply, Information Request/Reply, Address Mask Request/Reply
etc.).


Userspace ping util & patch for it:
ftp://mirrors.kernel.org/openwall/Owl/current/sources/Owl/packages/iputils/iputils-ss020927.tar.gz
ftp://ftp.intelib.org/pub/segoon/iputils-ss020927-pingsock.diff


Similar functionality is implemented in Mac OS X: 
http://www.manpagez.com/man/4/icmp/


TODO: implement ICMPv6 sockets.


All ping options (-b, -p, -Q, -R, -s, -t, -T, -M, -I), are tested with
the patch.

Tested with 2K "ping -i0.2" running on Core i7 (8 cores), Load average = 80,
and with 20 simultanious "ping -c1", each for half an hour.


Initially this functionality was written by Pavel Kankovsky for linux
2.4.32, but unfortunately it was never made public.


All comments are appreciated, especially about:

1) locking (get_port, lookup, unhash) - IMO ping sockets are too rarely
  used to optimise it.
2) necessity of ICMP_TIMESTAMP, ICMP_INFO_REQUEST, ICMP_ADDRESS - does
  anybody use it nowadays?
3) whether it's better to stay in net/ipv4/ping.c or move to net/ipv4/icmp.c

Signed-off-by: Vasiliy Kulikov <segoon@openwall.com>
Signed-off-by: Pavel Kankovsky <peak@argo.troja.mff.cuni.cz>
--
Index: linux-2.6.18/include/net/ping.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.18/include/net/ping.h	2010-12-27 22:10:54.000000000 +0300
@@ -0,0 +1,64 @@
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		Definitions for the "ping" module.
+ *
+ *		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 _PING_H
+#define _PING_H
+
+//#include <net/netns/hash.h>
+#include <net/inet_sock.h>
+
+#ifdef CONFIG_IP_PING_DEBUG
+#define ping_debug(fmt, x...) printk(KERN_INFO fmt, ## x)
+#else
+#define ping_debug(fmt, x...) do {} while (0)
+#endif
+
+/* PING_HTABLE_SIZE must be power of 2 */
+#define PING_HTABLE_SIZE 	64
+#define PING_HTABLE_MASK 	(PING_HTABLE_SIZE-1)
+
+#define ping_portaddr_for_each_entry(__sk, node, list) \
+	hlist_nulls_for_each_entry(__sk, node, list, sk_nulls_node)
+
+#define MAX_PING_IDENT 	65536
+
+
+struct ping_table {
+	struct hlist_head	hash[PING_HTABLE_SIZE];
+	rwlock_t		lock;
+};
+
+struct ping_iter_state {
+	int			bucket;
+};
+
+extern struct proto ping_prot;
+
+
+#ifdef CONFIG_IP_PING
+#define icmp_echoreply ping_rcv
+#else
+#define icmp_echoreply icmp_discard
+#endif
+
+extern void ping_rcv(struct sk_buff *);
+extern void ping_err(struct sk_buff *, u32 info);
+
+#ifdef CONFIG_PROC_FS
+extern int __init ping_proc_init(void);
+extern void ping_proc_exit(void);
+#endif
+
+void __init ping_init(void);
+
+
+#endif /* _PING_H */
Index: linux-2.6.18/net/ipv4/Kconfig
===================================================================
--- linux-2.6.18.orig/net/ipv4/Kconfig	2010-12-27 20:50:29.000000000 +0300
+++ linux-2.6.18/net/ipv4/Kconfig	2010-12-27 22:28:48.000000000 +0300
@@ -14,6 +14,26 @@
 	  <file:Documentation/networking/multicast.txt>. For most people, it's
 	  safe to say N.
 
+config IP_PING
+	bool "IP: ping socket"
+	help
+	  This option introduces a new kind of sockets - "ping sockets".
+
+	  A ping socket makes it possible to send ICMP Echo messages and receive
+	  corresponding ICMP Echo Reply messages without any special privileges.
+	  In other words, it makes is possible to implement setuid-less /bin/ping.
+
+	  A new ping socket is created with socket(PF_INET, SOCK_DGRAM, PROT_ICMP).
+
+config IP_PING_DEBUG
+	bool "IP: ping socket debug output"
+	depends on IP_PING
+	default n
+	help
+	  Enable the inclusion of debug code in the ICMP ping sockets.
+	  Be aware that doing this will impact performance.
+	  If unsure say N.
+
 config IP_ADVANCED_ROUTER
 	bool "IP: advanced router"
 	---help---
Index: linux-2.6.18/net/ipv4/Makefile
===================================================================
--- linux-2.6.18.orig/net/ipv4/Makefile	2010-12-27 20:50:29.000000000 +0300
+++ linux-2.6.18/net/ipv4/Makefile	2010-12-27 20:50:32.000000000 +0300
@@ -20,6 +20,7 @@
 obj-$(CONFIG_PROC_FS) += proc.o
 obj-$(CONFIG_IP_MULTIPLE_TABLES) += fib_rules.o
 obj-$(CONFIG_IP_MROUTE) += ipmr.o
+obj-$(CONFIG_IP_PING) += ping.o
 obj-$(CONFIG_NET_IPIP) += ipip.o
 obj-$(CONFIG_NET_IPGRE) += ip_gre.o
 obj-$(CONFIG_SYN_COOKIES) += syncookies.o
Index: linux-2.6.18/net/ipv4/af_inet.c
===================================================================
--- linux-2.6.18.orig/net/ipv4/af_inet.c	2010-12-27 20:50:29.000000000 +0300
+++ linux-2.6.18/net/ipv4/af_inet.c	2010-12-27 22:22:36.000000000 +0300
@@ -104,6 +104,7 @@
 #include <net/inet_connection_sock.h>
 #include <net/tcp.h>
 #include <net/udp.h>
+#include <net/ping.h>
 #include <linux/skbuff.h>
 #include <net/sock.h>
 #include <net/raw.h>
@@ -920,6 +921,16 @@
                 .flags =      INET_PROTOSW_PERMANENT,
        },
         
+#ifdef CONFIG_IP_PING
+       {
+		.type =       SOCK_DGRAM,
+		.protocol =   IPPROTO_ICMP,
+		.prot =       &ping_prot,
+		.ops =        &inet_dgram_ops,
+		.no_check =   UDP_CSUM_DEFAULT,
+		.flags =      INET_PROTOSW_REUSE,
+       },
+#endif
 
        {
                .type =       SOCK_RAW,
@@ -1340,6 +1351,9 @@
 
 static struct net_protocol icmp_protocol = {
 	.handler =	icmp_rcv,
+#ifdef CONFIG_IP_PING
+	.err_handler =	ping_err,
+#endif
 	.no_policy =	1,
 };
 
@@ -1411,6 +1425,11 @@
 	rc = proto_register(&raw_prot, 1);
 	if (rc)
 		goto out_unregister_udp_proto;
+#ifdef CONFIG_IP_PING
+	rc = proto_register(&ping_prot, 1);
+	if (rc)
+		goto out_unregister_raw_proto;
+#endif
 
 	/*
 	 *	Tell SOCKET that we are alive... 
@@ -1462,12 +1481,17 @@
 	/* Setup UDP memory threshold */
 	udp_init();
 
+
 	/*
 	 *	Set the ICMP layer up
 	 */
 
 	icmp_init(&inet_family_ops);
 
+#ifdef CONFIG_IP_PING
+	ping_init();
+#endif
+
 	/*
 	 *	Initialise the multicast router
 	 */
@@ -1491,6 +1515,10 @@
 	rc = 0;
 out:
 	return rc;
+#ifdef CONFIG_IP_PING
+out_unregister_raw_proto:
+	proto_unregister(&raw_prot);
+#endif
 out_unregister_tcp_proto:
 	proto_unregister(&tcp_prot);
 out_unregister_udp_proto:
@@ -1515,11 +1543,19 @@
 		goto out_udp;
 	if (fib_proc_init())
 		goto out_fib;
+#ifdef CONFIG_IP_PING
+	if (ping_proc_init())
+		goto out_ping;
+#endif
 	if (ip_misc_proc_init())
 		goto out_misc;
 out:
 	return rc;
 out_misc:
+#ifdef CONFIG_IP_PING
+	ping_proc_exit();
+out_ping:
+#endif
 	fib_proc_exit();
 out_fib:
 	udp4_proc_exit();
Index: linux-2.6.18/net/ipv4/icmp.c
===================================================================
--- linux-2.6.18.orig/net/ipv4/icmp.c	2010-12-27 20:50:29.000000000 +0300
+++ linux-2.6.18/net/ipv4/icmp.c	2010-12-27 20:50:32.000000000 +0300
@@ -84,6 +84,7 @@
 #include <net/tcp.h>
 #include <net/udp.h>
 #include <net/raw.h>
+#include <net/ping.h>
 #include <linux/skbuff.h>
 #include <net/sock.h>
 #include <linux/errno.h>
@@ -820,6 +821,17 @@
 			       iph->saddr, skb->dev);
 		break;
   	}
+
+#ifdef CONFIG_IP_PING
+	/* Ping wants to see redirects.
+         * Let's pretend they are errors of sorts... */
+	if (iph->protocol == IPPROTO_ICMP &&
+	    iph->ihl >= 5 &&
+	    pskb_may_pull(skb, (iph->ihl<<2)+8)) {
+		ping_err(skb, icmp_hdr(skb)->un.gateway);
+	}
+#endif
+
 out:
 	return;
 out_err:
@@ -1062,7 +1074,7 @@
  */
 static const struct icmp_control icmp_pointers[NR_ICMP_TYPES + 1] = {
 	[ICMP_ECHOREPLY] = {
-		.handler = icmp_discard,
+		.handler = icmp_echoreply,
 	},
 	[1] = {
 		.handler = icmp_discard,
Index: linux-2.6.18/net/ipv4/ping.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.18/net/ipv4/ping.c	2011-01-02 20:11:17.000000000 +0300
@@ -0,0 +1,974 @@
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		"Ping" sockets
+ *
+ *		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.
+ *
+ * Based on ipv4/udp.c code.
+ *
+ * Authors:	Vasiliy Kulikov,
+ *		Pavel Kankovsky (for 2.4.32)
+ *
+ */
+
+#include <asm/system.h>
+#include <linux/uaccess.h>
+#include <asm/ioctls.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <linux/mm.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <net/snmp.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/icmp.h>
+#include <net/protocol.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <net/sock.h>
+#include <net/ping.h>
+#include <net/icmp.h>
+#include <net/udp.h>
+#include <net/route.h>
+#include <net/inet_common.h>
+#include <net/checksum.h>
+
+
+#ifdef CONFIG_SYSCTL
+DEFINE_SEQLOCK(ping_group_range_lock);
+static int ip_ping_group_range_min[] = { 0, 0 };
+static int ip_ping_group_range_max[] = { 65535, 65535 };
+#endif
+
+struct ping_table ping_table __read_mostly;
+
+u16 ping_port_rover;
+
+static inline int ping_hashfn(struct ve_struct *net, unsigned num, unsigned mask)
+{
+	unsigned veid = VEID(net);
+	int res = (num + (veid ^ (veid >> 16))) & mask;
+	ping_debug("hash(%d) = %d\n", num, res);
+	return res;
+}
+
+static inline struct hlist_head *ping_hashslot(struct ping_table *table,
+					     struct ve_struct *net, unsigned num)
+{
+	return &table->hash[ping_hashfn(net, num, PING_HTABLE_MASK)];
+}
+
+void __inet_get_ping_group_range(unsigned *data, unsigned int *low, unsigned int *high)
+{
+	unsigned seq;
+	do {
+		seq = read_seqbegin(&ping_group_range_lock);
+
+		*low = data[0];
+		*high = data[1];
+	} while (read_seqretry(&ping_group_range_lock, seq));
+}
+
+void inet_get_ping_group_range_net(struct ve_struct *net, unsigned int *low, unsigned int *high)
+{
+	unsigned int *data = net->ping_group_range;
+	__inet_get_ping_group_range(data, low, high);
+}
+
+/* Update system visible IP port range */
+void set_ping_group_range(struct ctl_table *table, int range[2])
+{
+	int *data = table->data;
+	write_seqlock(&ping_group_range_lock);
+	data[0] = range[0];
+	data[1] = range[1];
+	write_sequnlock(&ping_group_range_lock);
+}
+
+/* Validate changes from /proc interface. */
+int ipv4_ping_group_range(ctl_table *table, int write, struct file *filep,
+				 void __user *buffer,
+				 size_t *lenp, loff_t *ppos)
+{
+	int ret;
+	int range[2];
+	ctl_table tmp = {
+		.data = &range,
+		.maxlen = sizeof(range),
+		.mode = table->mode,
+		.extra1 = &ip_ping_group_range_min,
+		.extra2 = &ip_ping_group_range_max,
+	};
+	struct ve_struct *env = get_exec_env();
+
+	inet_get_ping_group_range_net(env, range, range + 1);
+	ret = proc_dointvec_minmax(&tmp, write, filep, buffer, lenp, ppos);
+
+	if (write && ret == 0)
+		set_ping_group_range(table, range);
+
+	return ret;
+}
+
+static int ping_v4_get_port(struct sock *sk, unsigned short ident)
+{
+	struct hlist_node *node;
+	struct hlist_head *hlist;
+	struct inet_sock *isk, *isk2;
+	struct sock *sk2 = NULL;
+	struct ve_struct *env;
+
+	env = sk->owner_env;
+	isk = inet_sk(sk);
+	write_lock_bh(&ping_table.lock);
+	if (ident == 0) {
+		u32 i;
+		u16 result = ping_port_rover + 1;
+
+		for (i = 0; i < (1L << 16); i++, result++) {
+			if (!result)
+				result++; /* avoid zero */
+			hlist = ping_hashslot(&ping_table, env,
+					    result);
+			sk_for_each(sk2, node, hlist) {
+				isk2 = inet_sk(sk2);
+
+				if (isk2->num == result)
+					goto next_port;
+			}
+
+			/* found */
+			ping_port_rover = ident = result;
+			break;
+next_port:
+			;
+		}
+		if (i >= (1L << 16))
+			goto fail;
+	} else {
+		hlist = ping_hashslot(&ping_table, env, ident);
+		sk_for_each(sk2, node, hlist) {
+			isk2 = inet_sk(sk2);
+
+			if ((isk2->num == ident) &&
+			    (sk2 != sk) &&
+			    (!sk2->sk_reuse || !sk->sk_reuse))
+				goto fail;
+		}
+	}
+
+	ping_debug("found port/ident = %d\n", ident);
+	isk->num = ident;
+	if (sk_unhashed(sk)) {
+		ping_debug("was not hashed\n");
+		sock_hold(sk);
+
+		sk_add_node(sk, hlist);
+		sock_prot_inc_use(sk->sk_prot);
+	}
+	write_unlock_bh(&ping_table.lock);
+	return 0;
+
+fail:
+	write_unlock_bh(&ping_table.lock);
+	return 1;
+}
+
+static void ping_v4_hash(struct sock *sk)
+{
+	ping_debug("ping_v4_hash(sk->port=%u)\n", inet_sk(sk)->num);
+	BUG(); /* "Please do not press this button again." */
+}
+
+static void ping_v4_unhash(struct sock *sk)
+{
+	struct inet_sock *isk = inet_sk(sk);
+	struct ve_struct *env = sk->owner_env;
+	ping_debug("ping_v4_unhash(isk=%p,isk->num=%u)\n", isk, isk->num);
+	if (sk_hashed(sk)) {
+		struct hlist_head *hslot;
+
+		hslot = ping_hashslot(&ping_table, env, isk->num);
+		write_lock_bh(&ping_table.lock);
+		sk_del_node_init(sk);
+		sock_put(sk);
+		isk->num = isk->sport = 0;
+		sock_prot_dec_use(sk->sk_prot);
+		write_unlock_bh(&ping_table.lock);
+	}
+}
+
+struct sock *ping_v4_lookup(u32 saddr, u32 daddr, u16 ident, int dif)
+{
+	struct ve_struct *net = get_exec_env();
+	struct hlist_head *hslot = ping_hashslot(&ping_table, net, ident);
+	struct sock *sk = NULL;
+	struct inet_sock *isk;
+	struct hlist_node *hnode;
+
+	ping_debug("try to find: num = %d, daddr = %ld, dif = %d\n",
+			 (int)ident, (unsigned long)daddr, dif);
+	read_lock_bh(&ping_table.lock);
+
+	sk_for_each(sk, hnode, hslot) {
+		isk = inet_sk(sk);
+
+		ping_debug("found: %p: num = %d, daddr = %ld, dif = %d\n", sk,
+			 (int)isk->num, (unsigned long)isk->rcv_saddr,
+			 sk->sk_bound_dev_if);
+
+		ping_debug("iterate\n");
+		if (isk->num != ident)
+			continue;
+		if (isk->rcv_saddr && isk->rcv_saddr != daddr)
+			continue;
+		if (sk->sk_bound_dev_if && sk->sk_bound_dev_if != dif)
+			continue;
+
+		sock_hold(sk);
+		goto exit;
+	}
+
+	sk = NULL;
+exit:
+	read_unlock_bh(&ping_table.lock);
+
+	return sk;
+}
+
+static void ping_close(struct sock *sk, long timeout)
+{
+	ping_debug("ping_close(sk=%p,sk->num=%u)\n",
+		inet_sk(sk), inet_sk(sk)->num);
+	ping_debug("isk->refcnt = %d\n", sk->sk_refcnt.counter);
+
+	sk_common_release(sk);
+}
+
+/*
+ * We need our own bind because there are no privileged id's == local ports.
+ * Moreover, we don't allow binding to multi- and broadcast addresses.
+ */
+
+static int ping_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
+{
+	struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
+	struct inet_sock *isk = inet_sk(sk);
+	unsigned short snum;
+	int chk_addr_ret;
+	int err;
+	//struct ve_struct *env = sk->owner_env;
+
+	if (addr_len < sizeof(struct sockaddr_in))
+		return -EINVAL;
+
+	ping_debug("ping_v4_bind(sk=%p,sa_addr=%08x,sa_port=%d)\n",
+		sk, addr->sin_addr.s_addr, ntohs(addr->sin_port));
+
+	chk_addr_ret = inet_addr_type(addr->sin_addr.s_addr);
+	if (addr->sin_addr.s_addr == INADDR_ANY)
+		chk_addr_ret = RTN_LOCAL;
+
+	if ((sysctl_ip_nonlocal_bind == 0 &&
+	    isk->freebind == 0 && // isk->transparent == 0 &&
+	     chk_addr_ret != RTN_LOCAL) ||
+	    chk_addr_ret == RTN_MULTICAST ||
+	    chk_addr_ret == RTN_BROADCAST)
+		return -EADDRNOTAVAIL;
+
+	lock_sock(sk);
+
+	err = -EINVAL;
+	if (isk->num != 0)
+		goto out;
+
+	err = -EADDRINUSE;
+	isk->rcv_saddr = isk->saddr = addr->sin_addr.s_addr;
+	snum = ntohs(addr->sin_port);
+	if (ping_v4_get_port(sk, snum) != 0) {
+		isk->saddr = isk->rcv_saddr = 0;
+		goto out;
+	}
+
+	ping_debug("after bind(): num = %d, daddr = %ld, dif = %d\n",
+		(int)isk->num,
+		(unsigned long) isk->rcv_saddr,
+		(int)sk->sk_bound_dev_if);
+
+	err = 0;
+	if (isk->rcv_saddr)
+		sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
+	if (snum)
+		sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
+	isk->sport = htons(isk->num);
+	isk->daddr = 0;
+	isk->dport = 0;
+	sk_dst_reset(sk);
+out:
+	release_sock(sk);
+	ping_debug("ping_v4_bind -> %d\n", err);
+	return err;
+}
+
+/*
+ * Is this a supported type of ICMP message?
+ */
+
+static inline int ping_supported(int type, int code)
+{
+	if (type == ICMP_ECHO && code == 0)
+		return 1;
+	return 0;
+}
+
+/*
+ * This routine is called by the ICMP module when it gets some
+ * sort of error condition.
+ */
+
+static int ping_queue_rcv_skb(struct sock *sk, struct sk_buff *skb);
+
+void ping_err(struct sk_buff *skb, u32 info)
+{
+	struct iphdr *iph = (struct iphdr *)skb->data;
+	struct icmphdr *icmph = (struct icmphdr *)(skb->data+(iph->ihl<<2));
+	struct inet_sock *inet_sock;
+	int type = icmph->type;
+	int code = icmph->code;
+	struct sock *sk;
+	int harderr;
+	int err;
+
+	/* We assume the packet has already been checked by icmp_unreach */
+
+	if (!ping_supported(icmph->type, icmph->code))
+		return;
+
+	ping_debug("ping_err(type=%04x,code=%04x,id=%04x,seq=%04x)\n", type,
+		code, ntohs(icmph->un.echo.id), ntohs(icmph->un.echo.sequence));
+
+	sk = ping_v4_lookup(iph->daddr, iph->saddr,
+			    ntohs(icmph->un.echo.id), skb->dev->ifindex);
+	if (sk == NULL) {
+		ICMP_INC_STATS_BH(ICMP_MIB_INERRORS);
+		ping_debug("no socket, dropping\n");
+		return;	/* No socket for error */
+	}
+	ping_debug("err on socket %p\n", sk);
+
+	err = 0;
+	harderr = 0;
+	inet_sock = inet_sk(sk);
+
+	switch (type) {
+	default:
+	case ICMP_TIME_EXCEEDED:
+		err = EHOSTUNREACH;
+		break;
+	case ICMP_SOURCE_QUENCH:
+		/* This is not a real error but ping wants to see it.
+		 * Report it with some fake errno. */
+		err = EREMOTEIO;
+		break;
+	case ICMP_PARAMETERPROB:
+		err = EPROTO;
+		harderr = 1;
+		break;
+	case ICMP_DEST_UNREACH:
+		if (code == ICMP_FRAG_NEEDED) { /* Path MTU discovery */
+			if (inet_sock->pmtudisc != IP_PMTUDISC_DONT) {
+				err = EMSGSIZE;
+				harderr = 1;
+				break;
+			}
+			goto out;
+		}
+		err = EHOSTUNREACH;
+		if (code <= NR_ICMP_UNREACH) {
+			harderr = icmp_err_convert[code].fatal;
+			err = icmp_err_convert[code].errno;
+		}
+		break;
+	case ICMP_REDIRECT:
+		/* See ICMP_SOURCE_QUENCH */
+		err = EREMOTEIO;
+		break;
+	}
+
+	/*
+	 *      RFC1122: OK.  Passes ICMP errors back to application, as per
+	 *	4.1.3.3.
+	 */
+	if (!inet_sock->recverr) {
+		if (!harderr || sk->sk_state != TCP_ESTABLISHED)
+			goto out;
+	} else {
+		ip_icmp_error(sk, skb, err, 0 /* no remote port */,
+			 info, (u8 *)icmph);
+	}
+	sk->sk_err = err;
+	sk->sk_error_report(sk);
+out:
+	sock_put(sk);
+}
+
+/*
+ *	Copy and checksum an ICMP Echo packet from user space into a buffer.
+ */
+
+struct pingfakehdr {
+	struct icmphdr icmph;
+	struct iovec *iov;
+	u32 wcheck;
+};
+
+static int ping_getfrag(void *from, char * to,
+			int offset, int fraglen, int odd, struct sk_buff *skb)
+{
+	struct pingfakehdr *pfh = (struct pingfakehdr *)from;
+
+	if (offset == 0) {
+		if (fraglen < sizeof(struct icmphdr))
+			BUG();
+		if (csum_partial_copy_fromiovecend(to + sizeof(struct icmphdr),
+			    pfh->iov, 0, fraglen - sizeof(struct icmphdr),
+			    &pfh->wcheck))
+			return -EFAULT;
+
+		pfh->wcheck = csum_partial((char *)&pfh->icmph,
+			sizeof(struct icmphdr), pfh->wcheck);
+		pfh->icmph.checksum = csum_fold(pfh->wcheck);
+		memcpy(to, &pfh->icmph, sizeof(struct icmphdr));
+		return 0;
+	}
+	if (offset < sizeof(struct icmphdr))
+		BUG();
+	if (csum_partial_copy_fromiovecend
+			(to, pfh->iov, offset - sizeof(struct icmphdr),
+			 fraglen, &pfh->wcheck))
+		return -EFAULT;
+	return 0;
+}
+
+int ping_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
+		 size_t len)
+{
+	struct inet_sock *isk = inet_sk(sk);
+	struct ipcm_cookie ipc;
+	struct icmphdr user_icmph;
+	struct pingfakehdr pfh;
+	struct rtable *rt = NULL;
+	int free = 0;
+	u32 saddr, daddr;
+	u8  tos;
+	int err;
+	//struct ve_struct *env = sk->owner_env;
+
+	ping_debug("ping_sendmsg(sk=%p,sk->num=%u)\n", isk, isk->num);
+
+
+	if (len > 0xFFFF)
+		return -EMSGSIZE;
+
+	/*
+	 *	Check the flags.
+	 */
+
+	/* Mirror BSD error message compatibility */
+	if (msg->msg_flags & MSG_OOB)
+		return -EOPNOTSUPP;
+
+	/*
+	 *	Fetch the ICMP header provided by the userland.
+	 *	iovec is modified!
+	 */
+
+	if (memcpy_fromiovec((u8 *)&user_icmph, msg->msg_iov,
+			     sizeof(struct icmphdr)))
+		return -EFAULT;
+	if (!ping_supported(user_icmph.type, user_icmph.code))
+		return -EINVAL;
+
+	/*
+	 *	Get and verify the address.
+	 */
+
+	if (msg->msg_name) {
+		struct sockaddr_in *usin = (struct sockaddr_in *)msg->msg_name;
+		if (msg->msg_namelen < sizeof(*usin))
+			return -EINVAL;
+		if (usin->sin_family != AF_INET)
+			return -EINVAL;
+		daddr = usin->sin_addr.s_addr;
+		/* no remote port */
+	} else {
+		if (sk->sk_state != TCP_ESTABLISHED)
+			return -EDESTADDRREQ;
+		daddr = isk->daddr;
+		/* no remote port */
+	}
+
+	ipc.addr = isk->saddr;
+	ipc.opt = NULL;
+	ipc.oif = sk->sk_bound_dev_if;
+
+	if (msg->msg_controllen) {
+		err = ip_cmsg_send(msg, &ipc);
+		if (err)
+			return err;
+		if (ipc.opt)
+			free = 1;
+	}
+	if (!ipc.opt)
+		ipc.opt = isk->opt;
+
+	saddr = ipc.addr;
+	ipc.addr = daddr;
+
+	if (ipc.opt && ipc.opt->srr) {
+		if (!daddr)
+			return -EINVAL;
+		daddr = ipc.opt->faddr;
+	}
+	tos = RT_TOS(isk->tos);
+	if (sock_flag(sk, SOCK_LOCALROUTE) ||
+	    (msg->msg_flags&MSG_DONTROUTE) ||
+	    (ipc.opt && ipc.opt->is_strictroute)) {
+		tos |= RTO_ONLINK;
+	}
+
+	if (MULTICAST(daddr)) {
+		if (!ipc.oif)
+			ipc.oif = isk->mc_index;
+		if (!saddr)
+			saddr = isk->mc_addr;
+	}
+
+	{
+		struct flowi fl = { .oif = ipc.oif,
+				    .nl_u = { .ip4_u = {
+						.daddr = daddr,
+						.saddr = saddr,
+						.tos = tos } },
+				    .proto = IPPROTO_ICMP,
+		};
+
+		//struct ve_struct *net = env;
+
+		security_sk_classify_flow(sk, &fl);
+		err = ip_route_output_flow(&rt, &fl, sk, 1);
+		if (err) {
+			if (err == -ENETUNREACH)
+				IP_INC_STATS_BH(IPSTATS_MIB_OUTNOROUTES);
+			goto out;
+		}
+
+		err = -EACCES;
+		if ((rt->rt_flags & RTCF_BROADCAST) &&
+		    !sock_flag(sk, SOCK_BROADCAST))
+			goto out;
+	}
+
+	if (msg->msg_flags & MSG_CONFIRM)
+		goto do_confirm;
+back_from_confirm:
+
+	if (!ipc.addr)
+		ipc.addr = rt->rt_dst;
+
+	lock_sock(sk);
+
+	pfh.icmph.type = user_icmph.type; /* already checked */
+	pfh.icmph.code = user_icmph.code; /* dtto */
+	pfh.icmph.checksum = 0;
+	pfh.icmph.un.echo.id = isk->sport;
+	pfh.icmph.un.echo.sequence = user_icmph.un.echo.sequence;
+	pfh.iov = msg->msg_iov;
+	pfh.wcheck = 0;
+
+	err = ip_append_data(sk, ping_getfrag, &pfh, len,
+			0, &ipc, rt,
+			msg->msg_flags);
+	if (err)
+		ip_flush_pending_frames(sk);
+	else
+		err = ip_push_pending_frames(sk);
+	release_sock(sk);
+
+out:
+	ip_rt_put(rt);
+	if (free)
+		kfree(ipc.opt);
+	if (!err) {
+		icmp_out_count(user_icmph.type);
+		return len;
+	}
+	return err;
+
+do_confirm:
+	dst_confirm(&rt->u.dst);
+	if (!(msg->msg_flags & MSG_PROBE) || len)
+		goto back_from_confirm;
+	err = 0;
+	goto out;
+}
+
+/*
+ *	IOCTL requests applicable to the UDP^H^H^HICMP protocol
+ */
+
+int ping_ioctl(struct sock *sk, int cmd, unsigned long arg)
+{
+	ping_debug("ping_ioctl(sk=%p,sk->num=%u,cmd=%d,arg=%lu)\n",
+		inet_sk(sk), inet_sk(sk)->num, cmd, arg);
+	switch (cmd) {
+	case SIOCOUTQ:
+	case SIOCINQ:
+		return udp_ioctl(sk, cmd, arg);
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+int ping_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
+		 size_t len, int noblock, int flags, int *addr_len)
+{
+	struct inet_sock *isk = inet_sk(sk);
+	struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name;
+	struct sk_buff *skb;
+	int copied, err;
+
+	ping_debug("ping_recvmsg(sk=%p,sk->num=%u)\n", isk, isk->num);
+
+	if (flags & MSG_OOB)
+		goto out;
+
+	if (addr_len)
+		*addr_len = sizeof(*sin);
+
+	if (flags & MSG_ERRQUEUE)
+		return ip_recv_error(sk, msg, len);
+
+	skb = skb_recv_datagram(sk, flags, noblock, &err);
+	if (!skb)
+		goto out;
+
+	copied = skb->len;
+	if (copied > len) {
+		msg->msg_flags |= MSG_TRUNC;
+		copied = len;
+	}
+
+	/* Don't bother checking the checksum */
+	err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+	if (err)
+		goto done;
+
+	sock_recv_timestamp(msg, sk, skb);
+
+	/* Copy the address. */
+	if (sin) {
+		sin->sin_family = AF_INET;
+		sin->sin_port = 0 /* skb->h.uh->source */;
+		sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
+		memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
+	}
+	if (isk->cmsg_flags)
+		ip_cmsg_recv(msg, skb);
+	err = copied;
+
+done:
+	skb_free_datagram(sk, skb);
+out:
+	ping_debug("ping_recvmsg -> %d\n", err);
+	return err;
+}
+
+static int ping_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
+{
+	ping_debug("ping_queue_rcv_skb(sk=%p,sk->num=%d,skb=%p)\n",
+		inet_sk(sk), inet_sk(sk)->num, skb);
+	if (sock_queue_rcv_skb(sk, skb) < 0) {
+		ICMP_INC_STATS_BH(ICMP_MIB_INERRORS);
+		kfree_skb(skb);
+		ping_debug("ping_queue_rcv_skb -> failed\n");
+		return -1;
+	}
+	return 0;
+}
+
+
+/*
+ *	All we need to do is get the socket.
+ */
+
+void ping_rcv(struct sk_buff *skb)
+{
+	struct sock *sk;
+	//struct ve_struct *net = dev_net(skb->dev);
+	struct iphdr *iph = ip_hdr(skb);
+	struct icmphdr *icmph = icmp_hdr(skb);
+	u32 saddr = iph->saddr;
+	u32 daddr = iph->daddr;
+
+	/* We assume the packet has already been checked by icmp_rcv */
+
+	ping_debug("ping_rcv(skb=%p,id=%04x,seq=%04x)\n",
+		skb, ntohs(icmph->un.echo.id), ntohs(icmph->un.echo.sequence));
+
+	/* Push ICMP header back */
+	skb_push(skb, skb->data - (u8 *)icmph);
+
+	sk = ping_v4_lookup(saddr, daddr, ntohs(icmph->un.echo.id),
+			    skb->dev->ifindex);
+	if (sk != NULL) {
+		ping_debug("rcv on socket %p\n", sk);
+		ping_queue_rcv_skb(sk, skb_get(skb));
+		sock_put(sk);
+		return;
+	}
+	ping_debug("no socket, dropping\n");
+
+	/* We're called from icmp_rcv(). kfree_skb() is done there. */
+}
+
+struct proto ping_prot = {
+	.name =		"PING",
+	.owner =	THIS_MODULE,
+	.close =	ping_close,
+	.connect =	ip4_datagram_connect,
+	.disconnect =	udp_disconnect,
+	.ioctl =	ping_ioctl,
+	.setsockopt =	ip_setsockopt,
+	.getsockopt =	ip_getsockopt,
+	.sendmsg =	ping_sendmsg,
+	.recvmsg =	ping_recvmsg,
+	.bind =		ping_bind,
+	.backlog_rcv =	ping_queue_rcv_skb,
+	.hash =		ping_v4_hash,
+	.unhash =	ping_v4_unhash,
+	.get_port =	ping_v4_get_port,
+	.obj_size =	sizeof(struct inet_sock),
+};
+EXPORT_SYMBOL(ping_prot);
+
+#ifdef CONFIG_PROC_FS
+
+static struct sock *ping_get_first(struct seq_file *seq, int start)
+{
+	struct sock *sk;
+	struct ping_iter_state *state = seq->private;
+	//struct ve_struct *net = seq_file_net(seq);
+	struct ve_struct *net = get_exec_env();
+
+	for (state->bucket = start; state->bucket < PING_HTABLE_SIZE;
+	     ++state->bucket) {
+		struct hlist_node *node;
+		struct hlist_head *hslot = &ping_table.hash[state->bucket];
+
+		sk_for_each(sk, node, hslot) {
+			if (ve_accessible(sk->owner_env, net))
+				goto found;
+		}
+	}
+	sk = NULL;
+found:
+	return sk;
+}
+
+static struct sock *ping_get_next(struct seq_file *seq, struct sock *sk)
+{
+	struct ping_iter_state *state = seq->private;
+
+	do {
+		sk = sk_next(sk);
+		if (!sk)
+			break;
+		if (ve_accessible(sk->owner_env, get_exec_env()))
+			break;
+	} while (1);
+
+	if (!sk)
+		return ping_get_first(seq, state->bucket + 1);
+	return sk;
+}
+
+static struct sock *ping_get_idx(struct seq_file *seq, loff_t pos)
+{
+	struct sock *sk = ping_get_first(seq, 0);
+
+	if (sk)
+		while (pos && (sk = ping_get_next(seq, sk)) != NULL)
+			--pos;
+	return pos ? NULL : sk;
+}
+
+static void *ping_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	struct ping_iter_state *state = seq->private;
+	state->bucket = 0;
+
+	read_lock_bh(&ping_table.lock);
+
+	return *pos ? ping_get_idx(seq, *pos-1) : SEQ_START_TOKEN;
+}
+
+static void *ping_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	struct sock *sk;
+
+	if (v == SEQ_START_TOKEN)
+		sk = ping_get_idx(seq, 0);
+	else
+		sk = ping_get_next(seq, v);
+
+	++*pos;
+	return sk;
+}
+
+static void ping_seq_stop(struct seq_file *seq, void *v)
+{
+	read_unlock_bh(&ping_table.lock);
+}
+
+static void ping_format_sock(struct sock *sp, struct seq_file *f,
+		int bucket, int *len)
+{
+	struct inet_sock *inet = inet_sk(sp);
+	__be32 dest = inet->daddr;
+	__be32 src = inet->rcv_saddr;
+	__u16 destp = ntohs(inet->dport);
+	__u16 srcp = ntohs(inet->sport);
+
+	seq_printf(f, "%5d: %08X:%04X %08X:%04X"
+		" %02X %08X:%08X %02X:%08lX %08X %5d %8d %lu %d %p %n",
+		bucket, src, srcp, dest, destp, sp->sk_state,
+		atomic_read(&sp->sk_wmem_alloc),
+		atomic_read(&sp->sk_rmem_alloc),
+		0, 0L, 0, sock_i_uid(sp), 0, sock_i_ino(sp),
+		atomic_read(&sp->sk_refcnt), sp,
+		len);
+}
+
+static int ping_seq_show(struct seq_file *seq, void *v)
+{
+	if (v == SEQ_START_TOKEN)
+		seq_printf(seq, "%-127s\n",
+			   "  sl  local_address rem_address   st tx_queue "
+			   "rx_queue tr tm->when retrnsmt   uid  timeout "
+			   "inode ref pointer drops");
+	else {
+		struct ping_iter_state *state = seq->private;
+		int len;
+
+		ping_format_sock(v, seq, state->bucket, &len);
+		seq_printf(seq, "%*s\n", 127 - len, "");
+	}
+	return 0;
+}
+
+static struct seq_operations ping_seq_ops = {
+	.show		= ping_seq_show,
+	.start		= ping_seq_start,
+	.next		= ping_seq_next,
+	.stop		= ping_seq_stop,
+};
+
+static int ping_seq_open(struct inode *inode, struct file *file)
+{
+	int rc = -ENOMEM;
+	struct seq_file *seq;
+	struct ping_iter_state *s = kzalloc(sizeof(*s), GFP_KERNEL);
+
+	if (!s)
+		goto out;
+
+	rc = seq_open(file, &ping_seq_ops);
+	if (rc)
+		goto out_kfree;
+
+	seq	     = file->private_data;
+	seq->private = s;
+out:
+	return rc;
+out_kfree:
+	kfree(s);
+	goto out;
+}
+
+static struct file_operations ping_seq_fops = {
+	.owner		= THIS_MODULE,
+	.open		= ping_seq_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release_private,
+};
+
+static const char ping_proc_name[] = "net/icmp";
+
+static int ping_proc_register(void)
+{
+	struct proc_dir_entry *p;
+	int rc = 0;
+
+	p = proc_glob_fops_create(ping_proc_name, S_IRUGO,
+			     &ping_seq_fops);
+	if (!p)
+		rc = -ENOMEM;
+	return rc;
+}
+
+static void ping_proc_unregister(void)
+{
+	remove_proc_glob_entry(ping_proc_name, NULL);
+}
+
+static int ping_proc_init_net(void)
+{
+	return ping_proc_register();
+}
+
+static void ping_proc_exit_net(void)
+{
+	ping_proc_unregister();
+}
+
+#if 0
+static struct pernet_operations ping_net_ops = {
+	.init = ping_proc_init_net,
+	.exit = ping_proc_exit_net,
+};
+#endif
+
+int __init ping_proc_init(void)
+{
+	return ping_proc_init_net();
+}
+
+void ping_proc_exit(void)
+{
+	ping_proc_exit_net();
+}
+
+#endif
+
+void __init ping_init(void)
+{
+	int i;
+
+	for (i = 0; i < PING_HTABLE_SIZE; i++)
+		INIT_HLIST_HEAD(&ping_table.hash[i]);
+	rwlock_init(&ping_table.lock);
+}
Index: linux-2.6.18/include/linux/sysctl.h
===================================================================
--- linux-2.6.18.orig/include/linux/sysctl.h	2011-01-02 19:59:54.000000000 +0300
+++ linux-2.6.18/include/linux/sysctl.h	2011-01-02 19:59:57.000000000 +0300
@@ -528,6 +528,7 @@
 	NET_IPV4_CONF_ARP_IGNORE=19,
 	NET_IPV4_CONF_PROMOTE_SECONDARIES=20,
 	NET_IPV4_CONF_ARP_ACCEPT=21,
+	NET_IPV4_CONF_PING_GROUP_RANGE=22,
 	__NET_IPV4_CONF_MAX
 };
 
Index: linux-2.6.18/include/linux/ve.h
===================================================================
--- linux-2.6.18.orig/include/linux/ve.h	2011-01-02 19:53:20.000000000 +0300
+++ linux-2.6.18/include/linux/ve.h	2011-01-02 20:05:04.000000000 +0300
@@ -215,6 +215,7 @@
 	struct ctl_table_header	*forward_header;
 	struct ctl_table	*forward_table;
  	unsigned long		rt_flush_required;
+	unsigned int 		ping_group_range[2];
 	struct neigh_table	*ve_arp_tbl;
 #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
 	struct ipv6_devconf	*_ipv6_devconf;
Index: linux-2.6.18/net/ipv4/devinet.c
===================================================================
--- linux-2.6.18.orig/net/ipv4/devinet.c	2011-01-02 20:00:04.000000000 +0300
+++ linux-2.6.18/net/ipv4/devinet.c	2011-01-02 20:04:12.000000000 +0300
@@ -1457,6 +1457,14 @@
 			.proc_handler	= &ipv4_doint_and_flush,
 			.strategy	= &ipv4_doint_and_flush_strategy,
 		},
+		{
+			.ctl_name	= NET_IPV4_CONF_PING_GROUP_RANGE,
+			.procname	= "ping_group_range",
+			//.data		= &ipv4_devconf.promote_secondaries,
+			.maxlen		= sizeof(int)*2,
+			.mode		= 0644,
+			.proc_handler	= &ipv4_doint_and_flush,
+		},
 	},
 	.devinet_dev = {
 		{
