[PATCH 1/2] Add optional rootless ping support

Radoslav Kolev radoslav.kolev at suse.com
Tue Apr 15 08:33:18 UTC 2025


---
 networking/ping.c | 212 +++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 180 insertions(+), 32 deletions(-)

diff --git a/networking/ping.c b/networking/ping.c
index b7e6955a9..b2ff92b82 100644
--- a/networking/ping.c
+++ b/networking/ping.c
@@ -46,6 +46,16 @@
 //config:	With this option off, ping will say "HOST is alive!"
 //config:	or terminate with SIGALRM in 5 seconds otherwise.
 //config:	No command-line options will be recognized.
+//config:
+//config:config FEATURE_PING_NONROOT
+//config:    bool "Enable ping without root privileges"
+//config:    default n
+//config:    depends on PING || PING6
+//config:    help
+//config:    When enabled, ping will attempt to use SOCK_DGRAM instead of SOCK_RAW
+//config:    if root privileges are unavailable. This allows ping to work for
+//config:    non-root users on systems configured to permit it (e.g., Linux with
+//config:    net.ipv4.ping_group_range set). When disabled, ping requires root.
 
 /* Needs socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), therefore BB_SUID_MAYBE: */
 //applet:IF_PING(APPLET(ping, BB_DIR_BIN, BB_SUID_MAYBE))
@@ -208,6 +218,10 @@ enum {
 	pingsock = 0,
 };
 
+#if ENABLE_FEATURE_PING_NONROOT
+static int using_dgram;
+#endif
+
 static void
 #if ENABLE_PING6
 create_icmp_socket(len_and_sockaddr *lsa)
@@ -224,14 +238,57 @@ create_icmp_socket(void)
 #endif
 		sock = socket(AF_INET, SOCK_RAW, 1); /* 1 == ICMP */
 	if (sock < 0) {
-		if (errno == EPERM)
-			bb_simple_error_msg_and_die(bb_msg_perm_denied_are_you_root);
+        if (errno != EPERM)
 		bb_simple_perror_msg_and_die(bb_msg_can_not_create_raw_socket);
+#if ENABLE_FEATURE_PING_NONROOT
+#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
+         */
+
+	using_dgram = 1;
+
+#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 /* defined(__linux__) || defined(__APPLE__) */
+#endif /* ENABLE_FEATURE_PING_NONROOT */
+            bb_simple_error_msg_and_die(bb_msg_perm_denied_are_you_root);
 	}
 
 	xmove_fd(sock, pingsock);
 }
 
+#if ENABLE_FEATURE_PING_NONROOT
+static int get_source_port(int fd) {
+	len_and_sockaddr *my_lsa;
+	int port;
+	/* we need the correct address family, so get it from the
+	* already created socket here */
+	my_lsa = get_sock_lsa(fd);
+	if (my_lsa == NULL)
+		bb_simple_perror_msg_and_die("getsockname");
+	if ( (port = get_nport(&my_lsa->u.sa) == -1 ) )
+		bb_simple_perror_msg_and_die("get port");
+	if ( !port ) {
+		/* socket is not yet bound (happens in simple version)
+		* bind it explicitly so we can get assigned a source port */
+		xbind(fd, &my_lsa->u.sa, my_lsa->len);
+		getsockname(fd, &my_lsa->u.sa, &my_lsa->len );
+	}
+	port = get_nport(&my_lsa->u.sa);
+	if (ENABLE_FEATURE_CLEAN_UP)
+		free(my_lsa);
+	return port;
+}
+#endif
+
 #if !ENABLE_FEATURE_FANCY_PING
 
 /* Simple version */
@@ -279,15 +336,25 @@ static void ping4(len_and_sockaddr *lsa)
 				bb_simple_perror_msg("recvfrom");
 			continue;
 		}
-		if (c >= 76) {			/* ip + icmp */
-			struct iphdr *iphdr = (struct iphdr *) G.packet;
 
-			pkt = (struct icmp *) (G.packet + (iphdr->ihl << 2));	/* skip ip hdr */
-			if (pkt->icmp_id != G.myid)
-				continue; /* not our ping */
-			if (pkt->icmp_type == ICMP_ECHOREPLY)
-				break;
+#if ENABLE_FEATURE_PING_NONROOT
+		/* no IP header, just 8 bytes ICMP header + 56 bytes of payload */
+		if (using_dgram && c == DEFDATALEN + ICMP_MINLEN ) {
+			pkt = (struct icmp *) G.packet;
+		} else
+#endif
+		if (c >= 76) {	/* ip + icmp */
+			struct iphdr *iphdr = (struct iphdr *) G.packet;
+			/* skip ip hdr */
+			pkt = (struct icmp *) (G.packet + (iphdr->ihl << 2));
+		} else {
+			continue; /* packet is too short */
 		}
+
+		if (pkt->icmp_id != G.myid)
+			continue; /* not our ping */
+		if (pkt->icmp_type == ICMP_ECHOREPLY)
+			break;
 	}
 }
 
@@ -373,12 +440,21 @@ static int common_ping_main(sa_family_t af, char **argv)
 	alarm(5); /* give the host 5000ms to respond */
 
 	create_icmp_socket(lsa);
-	G.myid = (uint16_t) getpid();
-	/* we can use native-endian ident, but other Unix ping/traceroute
-	 * utils use *big-endian pid*, and e.g. traceroute on our machine may be
-	 * *not* from busybox, idents may collide. Follow the convention:
-	 */
-	G.myid = htons(G.myid);
+
+#if ENABLE_FEATURE_PING_NONROOT
+	if (using_dgram)
+		G.myid = get_source_port(pingsock);
+	else
+#endif
+	{
+		G.myid = (uint16_t) getpid();
+		/* we can use native-endian ident, but other Unix ping/traceroute
+		 * utils use *big-endian pid*, and e.g. traceroute on our machine may be
+		 * *not* from busybox, idents may collide. Follow the convention:
+		 */
+		G.myid = htons(G.myid);
+	}
+
 #if ENABLE_PING6
 	if (lsa->u.sa.sa_family == AF_INET6)
 		ping6(lsa);
@@ -689,7 +765,11 @@ static void unpack_tail(int sz, uint32_t *tp,
 	puts(dupmsg);
 	fflush_all();
 }
+#if ENABLE_FEATURE_PING_NONROOT
+static int unpack4(char *buf, int sz, struct sockaddr_in *from, int ttl)
+#else
 static int unpack4(char *buf, int sz, struct sockaddr_in *from)
+#endif
 {
 	struct icmp *icmppkt;
 	struct iphdr *iphdr;
@@ -699,23 +779,35 @@ static int unpack4(char *buf, int sz, struct sockaddr_in *from)
 	if (sz < (datalen + ICMP_MINLEN))
 		return 0;
 
-	/* check IP header */
-	iphdr = (struct iphdr *) buf;
-	hlen = iphdr->ihl << 2;
-	sz -= hlen;
-	icmppkt = (struct icmp *) (buf + hlen);
+#if ENABLE_FEATURE_PING_NONROOT
+	if (using_dgram) {
+		icmppkt = (struct icmp *) buf;
+	} else
+#endif
+	{
+		/* check IP header */
+		iphdr = (struct iphdr *) buf;
+		hlen = iphdr->ihl << 2;
+		sz -= hlen;
+		icmppkt = (struct icmp *) (buf + hlen);
+	}
+
 	if (icmppkt->icmp_id != myid)
 		return 0;				/* not our ping */
 
 	if (icmppkt->icmp_type == ICMP_ECHOREPLY) {
 		uint16_t recv_seq = ntohs(icmppkt->icmp_seq);
 		uint32_t *tp = NULL;
-
 		if (sz >= ICMP_MINLEN + sizeof(uint32_t))
 			tp = (uint32_t *) icmppkt->icmp_data;
 		unpack_tail(sz, tp,
-			inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr),
-			recv_seq, iphdr->ttl);
+			inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr), recv_seq,
+#if ENABLE_FEATURE_PING_NONROOT
+			using_dgram ? ttl : iphdr->ttl
+#else
+			iphdr->ttl
+#endif
+			);
 		return 1;
 	}
 	if (icmppkt->icmp_type != ICMP_ECHO) {
@@ -763,6 +855,12 @@ static int unpack6(char *packet, int sz, struct sockaddr_in6 *from, int hoplimit
 static void ping4(len_and_sockaddr *lsa)
 {
 	int sockopt;
+	struct sockaddr_in from;
+#if ENABLE_FEATURE_PING_NONROOT
+	struct msghdr msg;
+	struct iovec iov;
+	char control_buf[CMSG_SPACE(36)];
+#endif
 
 	pingaddr.sin = lsa->u.sin;
 	if (source_lsa) {
@@ -772,6 +870,14 @@ static void ping4(len_and_sockaddr *lsa)
 		xbind(pingsock, &source_lsa->u.sa, source_lsa->len);
 	}
 
+#if ENABLE_FEATURE_PING_NONROOT
+	if ( using_dgram ) {
+		myid =  get_source_port(pingsock);
+		if (setsockopt_1(pingsock, IPPROTO_IP, IP_RECVTTL))
+			bb_simple_error_msg_and_die("setsockopt(IP_RECVTTL)");
+	}
+#endif
+
 	/* enable broadcast pings */
 	setsockopt_broadcast(pingsock);
 
@@ -789,23 +895,53 @@ static void ping4(len_and_sockaddr *lsa)
 	signal(SIGINT, print_stats_and_exit);
 
 	/* start the ping's going ... */
+#if ENABLE_FEATURE_PING_NONROOT
+	msg.msg_name = &from;
+	msg.msg_namelen = sizeof(from);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = control_buf;
+	iov.iov_base = G.rcv_packet;
+	iov.iov_len = G.sizeof_rcv_packet;
+	msg.msg_controllen = sizeof(control_buf);
+#endif
  send_ping:
 	sendping4(0);
 
 	/* listen for replies */
 	while (1) {
-		struct sockaddr_in from;
-		socklen_t fromlen = (socklen_t) sizeof(from);
 		int c;
+#if ENABLE_FEATURE_PING_NONROOT
+		struct cmsghdr *mp;
+		int ttl = -1;
+#else
+		socklen_t fromlen = (socklen_t) sizeof(from);
+#endif
 
+#if ENABLE_FEATURE_PING_NONROOT
+		c = recvmsg(pingsock, &msg, 0);
+#else
 		c = recvfrom(pingsock, G.rcv_packet, G.sizeof_rcv_packet, 0,
 				(struct sockaddr *) &from, &fromlen);
+#endif
+
 		if (c < 0) {
 			if (errno != EINTR)
 				bb_simple_perror_msg("recvfrom");
 			continue;
 		}
+#if ENABLE_FEATURE_PING_NONROOT
+		for (mp = CMSG_FIRSTHDR(&msg); mp; mp = CMSG_NXTHDR(&msg, mp)) {
+			if (mp->cmsg_level == SOL_IP
+			 && mp->cmsg_type == IP_TTL
+			) {
+				move_from_unaligned_int(ttl, CMSG_DATA(mp));
+			}
+		}
+		c = unpack4(G.rcv_packet, c, &from, ttl);
+#else
 		c = unpack4(G.rcv_packet, c, &from);
+#endif
 		if (pingcount && G.nreceived >= pingcount)
 			break;
 		if (c && (option_mask32 & OPT_A)) {
@@ -826,7 +962,15 @@ static void ping6(len_and_sockaddr *lsa)
 	if (source_lsa)
 		xbind(pingsock, &source_lsa->u.sa, source_lsa->len);
 
+#if ENABLE_FEATURE_PING_NONROOT
+	if ( using_dgram )
+		myid = get_source_port(pingsock);
+#endif
+
 #ifdef ICMP6_FILTER
+#if ENABLE_FEATURE_PING_NONROOT
+	if (!using_dgram)
+#endif
 	{
 		struct icmp6_filter filt;
 		if (!(option_mask32 & OPT_VERBOSE)) {
@@ -971,13 +1115,17 @@ static int common_ping_main(int opt, char **argv)
 	if (interval > INT_MAX/1000000)
 		interval = INT_MAX/1000000;
 	G.interval_us = interval * 1000000;
-
-	myid = (uint16_t) getpid();
-	/* we can use native-endian ident, but other Unix ping/traceroute
-	 * utils use *big-endian pid*, and e.g. traceroute on our machine may be
-	 * *not* from busybox, idents may collide. Follow the convention:
-	 */
-	myid = htons(myid);
+#if ENABLE_FEATURE_PING_NONROOT
+	if (!using_dgram)
+#endif
+	{
+		myid = (uint16_t) getpid();
+		/* we can use native-endian ident, but other Unix ping/traceroute
+		 * utils use *big-endian pid*, and e.g. traceroute on our machine may be
+		 * *not* from busybox, idents may collide. Follow the convention:
+		 */
+		myid = htons(myid);
+	}
 	hostname = argv[optind];
 #if ENABLE_PING6
 	{
-- 
2.47.1



More information about the busybox mailing list