From 21d74f2989d0046e5b7c586f5a052643d5da8dcc Mon Sep 17 00:00:00 2001
From: Natanael Copa <ncopa@alpinelinux.org>
Date: Tue, 29 Mar 2016 18:59:22 +0200
Subject: [PATCH 06/16] ping: make ping work without root privileges

---
 networking/ping.c | 103 +++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 87 insertions(+), 16 deletions(-)

diff --git a/networking/ping.c b/networking/ping.c
index d1d59d545..c0ebc0f9a 100644
--- a/networking/ping.c
+++ b/networking/ping.c
@@ -163,6 +163,7 @@ enum {
 	pingsock = 0,
 };
 
+static int using_dgram;
 static void
 #if ENABLE_PING6
 create_icmp_socket(len_and_sockaddr *lsa)
@@ -179,9 +180,23 @@ create_icmp_socket(void)
 #endif
 		sock = socket(AF_INET, SOCK_RAW, 1); /* 1 == ICMP */
 	if (sock < 0) {
-		if (errno == EPERM)
-			bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
-		bb_perror_msg_and_die(bb_msg_can_not_create_raw_socket);
+		if (errno != EPERM)
+			bb_perror_msg_and_die(bb_msg_can_not_create_raw_socket);
+#if defined(__linux__) || defined(__APPLE__)
+		/* We don't have root privileges.  Try SOCK_DGRAM instead.
+		 * Linux needs net.ipv4.ping_group_range for this to work.
+		 * MacOSX allows ICMP_ECHO, ICMP_TSTAMP or ICMP_MASKREQ
+		 */
+#if ENABLE_PING6
+		if (lsa->u.sa.sa_family == AF_INET6)
+			sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
+		else
+#endif
+			sock = socket(AF_INET, SOCK_DGRAM, 1); /* 1 == ICMP */
+		if (sock < 0)
+#endif
+		bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
+		using_dgram = 1;
 	}
 
 	xmove_fd(sock, pingsock);
@@ -234,10 +249,12 @@ static void ping4(len_and_sockaddr *lsa)
 				bb_perror_msg("recvfrom");
 			continue;
 		}
-		if (c >= 76) {			/* ip + icmp */
-			struct iphdr *iphdr = (struct iphdr *) G.packet;
+		if (c >= 76 || using_dgram && (c == 64)) {			/* ip + icmp */
+			if(!using_dgram) {
+				struct iphdr *iphdr = (struct iphdr *) G.packet;
 
-			pkt = (struct icmp *) (G.packet + (iphdr->ihl << 2));	/* skip ip hdr */
+				pkt = (struct icmp *) (G.packet + (iphdr->ihl << 2));	/* skip ip hdr */
+			} else pkt = (struct icmp *) G.packet;
 			if (pkt->icmp_id != G.myid)
 				continue; /* not our ping */
 			if (pkt->icmp_type == ICMP_ECHOREPLY)
@@ -634,19 +651,21 @@ static void unpack_tail(int sz, uint32_t *tp,
 }
 static void unpack4(char *buf, int sz, struct sockaddr_in *from)
 {
-	struct icmp *icmppkt;
 	struct iphdr *iphdr;
+	struct icmp *icmppkt;
 	int hlen;
 
 	/* discard if too short */
 	if (sz < (datalen + ICMP_MINLEN))
 		return;
+	if(!using_dgram) {
+		/* check IP header */
+		iphdr = (struct iphdr *) buf;
+		hlen = iphdr->ihl << 2;
+		sz -= hlen;
+		icmppkt = (struct icmp *) (buf + hlen);
+	} else icmppkt = (struct icmp *) buf;
 
-	/* check IP header */
-	iphdr = (struct iphdr *) buf;
-	hlen = iphdr->ihl << 2;
-	sz -= hlen;
-	icmppkt = (struct icmp *) (buf + hlen);
 	if (icmppkt->icmp_id != myid)
 		return;				/* not our ping */
 
@@ -658,7 +677,7 @@ static void unpack4(char *buf, int sz, struct sockaddr_in *from)
 			tp = (uint32_t *) icmppkt->icmp_data;
 		unpack_tail(sz, tp,
 			inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr),
-			recv_seq, iphdr->ttl);
+			recv_seq, using_dgram ? 42 : iphdr->ttl);
 	} else if (icmppkt->icmp_type != ICMP_ECHO) {
 		bb_error_msg("warning: got ICMP %d (%s)",
 				icmppkt->icmp_type,
@@ -702,11 +721,31 @@ static void ping4(len_and_sockaddr *lsa)
 	int sockopt;
 
 	pingaddr.sin = lsa->u.sin;
-	if (source_lsa) {
+	if (source_lsa && !using_dgram) {
 		if (setsockopt(pingsock, IPPROTO_IP, IP_MULTICAST_IF,
 				&source_lsa->u.sa, source_lsa->len))
 			bb_error_msg_and_die("can't set multicast source interface");
 		xbind(pingsock, &source_lsa->u.sa, source_lsa->len);
+	} else if(using_dgram) {
+		struct sockaddr_in sa;
+		socklen_t sl;
+
+		sa.sin_family = AF_INET;
+		sa.sin_port = 0;
+		sa.sin_addr.s_addr = source_lsa ?
+			source_lsa->u.sin.sin_addr.s_addr : 0;
+		sl = sizeof(sa);
+
+		if (bind(pingsock, (struct sockaddr *) &sa, sl) == -1) {
+			perror("bind");
+			exit(2);
+		}
+
+		if (getsockname(pingsock, (struct sockaddr *) &sa, &sl) == -1) {
+			perror("getsockname");
+			exit(2);
+		}
+		myid = sa.sin_port;
 	}
 
 	/* enable broadcast pings */
@@ -723,6 +762,15 @@ static void ping4(len_and_sockaddr *lsa)
 		setsockopt_int(pingsock, IPPROTO_IP, IP_MULTICAST_TTL, opt_ttl);
 	}
 
+	if(using_dgram) {
+		int hold = 65536;
+		if (setsockopt(pingsock, SOL_IP, IP_RECVTTL, (char *)&hold, sizeof(hold)))
+			perror("WARNING: setsockopt(IP_RECVTTL)");
+		if (setsockopt(pingsock, SOL_IP, IP_RETOPTS, (char *)&hold, sizeof(hold)))
+			perror("WARNING: setsockopt(IP_RETOPTS)");
+
+	}
+
 	signal(SIGINT, print_stats_and_exit);
 
 	/* start the ping's going ... */
@@ -756,10 +804,33 @@ static void ping6(len_and_sockaddr *lsa)
 	char control_buf[CMSG_SPACE(36)];
 
 	pingaddr.sin6 = lsa->u.sin6;
-	if (source_lsa)
+	if (source_lsa && !using_dgram)
 		xbind(pingsock, &source_lsa->u.sa, source_lsa->len);
+	else if(using_dgram) {
+		struct sockaddr_in6 sa = {0};
+		socklen_t sl;
+
+		sa.sin6_family = AF_INET6;
+		sa.sin6_port = 0;
+		if(source_lsa) {
+			memcpy(&sa.sin6_addr, &source_lsa->u.sin6.sin6_addr, sizeof(struct in6_addr));
+		}
+		sl = sizeof(sa);
+
+		if (bind(pingsock, (struct sockaddr *) &sa, sl) == -1) {
+			perror("bind");
+			exit(2);
+		}
+
+		if (getsockname(pingsock, (struct sockaddr *) &sa, &sl) == -1) {
+			perror("getsockname");
+			exit(2);
+		}
+		myid = sa.sin6_port;
+	}
 
 #ifdef ICMP6_FILTER
+	if(!using_dgram)
 	{
 		struct icmp6_filter filt;
 		if (!(option_mask32 & OPT_VERBOSE)) {
@@ -890,7 +961,7 @@ static int common_ping_main(int opt, char **argv)
 	if (opt & OPT_p)
 		G.pattern = xstrtou_range(str_p, 16, 0, 255);
 
-	myid = (uint16_t) getpid();
+	if (!using_dgram) myid = (uint16_t) getpid();
 	hostname = argv[optind];
 #if ENABLE_PING6
 	{
-- 
2.16.2