[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