master xplshn/aruu / cmd / net / ping.c
  1/* See LICENSE file for copyright and license details. */
  2
  3
  4#include "util.h"
  5#include "arg.h"
  6
  7#include <arpa/inet.h>
  8#include <errno.h>
  9#include <netdb.h>
 10#include <netinet/in.h>
 11#include <netinet/ip.h>
 12#include <netinet/ip_icmp.h>
 13#include <poll.h>
 14#include <signal.h>
 15#include <stdio.h>
 16#include <stdlib.h>
 17#include <string.h>
 18#include <sys/socket.h>
 19#include <sys/time.h>
 20#include <sys/types.h>
 21#include <unistd.h>
 22
 23static int keep_running = 1;
 24
 25static void
 26usage(void)
 27{
 28	eprintf("usage: %s [-c count] [-i interval] [-s size] [-t ttl] [-w deadline] [-q] host\n", argv0);
 29}
 30
 31static void
 32sigint_handler(int sig)
 33{
 34	(void)sig;
 35	keep_running = 0;
 36}
 37
 38static unsigned short
 39checksum(unsigned short *addr, int len)
 40{
 41	unsigned long sum = 0;
 42
 43	while (len > 1) {
 44		sum += *addr++;
 45		len -= 2;
 46	}
 47	if (len > 0)
 48		sum += *(unsigned char *)addr;
 49	while (sum >> 16)
 50		sum = (sum & 0xffff) + (sum >> 16);
 51	return ~sum;
 52}
 53
 54static void
 55send_ping(int sock, struct sockaddr_in *dst, int seq, int size)
 56{
 57	struct icmphdr *icmp;
 58	char *packet;
 59	struct timeval tv;
 60	int packlen = sizeof(*icmp) + size;
 61
 62	packet = emalloc(packlen);
 63	memset(packet, 0, packlen);
 64
 65	icmp = (struct icmphdr *)packet;
 66	icmp->type = ICMP_ECHO;
 67	icmp->code = 0;
 68	icmp->un.echo.id = htons(getpid() & 0xffff);
 69	icmp->un.echo.sequence = htons(seq);
 70
 71	/* store timestamp in payload if size is large enough */
 72	if (size >= (int)sizeof(struct timeval)) {
 73		gettimeofday(&tv, NULL);
 74		memcpy(packet + sizeof(*icmp), &tv, sizeof(tv));
 75	}
 76
 77	icmp->checksum = checksum((unsigned short *)packet, packlen);
 78
 79	if (sendto(sock, packet, packlen, 0, (struct sockaddr *)dst, sizeof(*dst)) < 0)
 80		eprintf("sendto:");
 81
 82	free(packet);
 83}
 84
 85static double
 86get_time_ms(void)
 87{
 88	struct timeval tv;
 89	gettimeofday(&tv, NULL);
 90	return (double)tv.tv_sec * 1000.0 + (double)tv.tv_usec / 1000.0;
 91}
 92
 93static void
 94print_stats(const char *host, int sent, int received, double min_rtt, double max_rtt, double sum_rtt)
 95{
 96	printf("\n--- %s ping statistics ---\n", host);
 97	printf("%d packets transmitted, %d received, %d%% packet loss\n",
 98	       sent, received, sent > 0 ? (sent - received) * 100 / sent : 0);
 99	if (received > 0) {
100		printf("rtt min/avg/max = %.3f/%.3f/%.3f ms\n",
101		       min_rtt, sum_rtt / received, max_rtt);
102	}
103}
104
105// ?man ping: send icmp echo requests
106// ?man arguments: host
107// ?man send icmp echo requests to verify network connectivity
108int
109main(int argc, char *argv[])
110{
111	struct addrinfo hints, *res;
112	struct sockaddr_in dst;
113	struct sockaddr_in from;
114	struct pollfd pfd;
115	struct timeval sent_tv;
116	struct icmphdr *icmp;
117	char *cflag = NULL;
118	char *iflag = NULL;
119	char *sflag = NULL;
120	char *tflag = NULL;
121	char *wflag = NULL;
122	char *host;
123	char *rxbuf;
124	int qflag = 0;
125	int count = 0;
126	int size = 56;
127	int ttl = 0;
128	int deadline = 0;
129	int sock = -1;
130	int is_raw = 1;
131	int seq = 1;
132	int sent = 0;
133	int received = 0;
134	int r, optval, p_res, hlen;
135	double interval = 1.0;
136	double min_rtt = 999999.0;
137	double max_rtt = 0.0;
138	double sum_rtt = 0.0;
139	double start_time, deadline_ms, next_send_ms, now, timeout_ms, time_to_deadline, rtt, sent_ms;
140	ssize_t n;
141	socklen_t fromlen;
142
143	ARGBEGIN {
144	// ?man -c:str: print count or perform stdout action
145	case 'c':
146		cflag = EARGF(usage());
147		break;
148	// ?man -i:str: interactive mode or prompt for confirmation
149	case 'i':
150		iflag = EARGF(usage());
151		break;
152	// ?man -s:str: silent mode or print summary
153	case 's':
154		sflag = EARGF(usage());
155		break;
156	// ?man -t:str: sort or specify timestamp
157	case 't':
158		tflag = EARGF(usage());
159		break;
160	// ?man -w:str: wait for completion
161	case 'w':
162		wflag = EARGF(usage());
163		break;
164	// ?man -q: quiet mode; suppress output
165	case 'q':
166		qflag = 1;
167		break;
168	default:
169		usage();
170	} ARGEND
171
172	if (argc < 1)
173		usage();
174
175	host = argv[0];
176
177	if (cflag)
178		count = estrtonum(cflag, 1, 1000000);
179	if (iflag)
180		interval = estrtod(iflag);
181	if (sflag)
182		size = estrtonum(sflag, 0, 65507);
183	if (tflag)
184		ttl = estrtonum(tflag, 1, 255);
185	if (wflag)
186		deadline = estrtonum(wflag, 1, 1000000);
187
188	memset(&hints, 0, sizeof(hints));
189	hints.ai_family = AF_INET;
190	hints.ai_socktype = SOCK_RAW;
191
192	r = getaddrinfo(host, NULL, &hints, &res);
193	if (r != 0)
194		eprintf("getaddrinfo %s: %s\n", host, gai_strerror(r));
195
196	memcpy(&dst, res->ai_addr, sizeof(dst));
197	freeaddrinfo(res);
198
199	/* try opening raw icmp socket first, fallback to dgram */
200	sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
201	if (sock < 0) {
202		sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
203		if (sock < 0)
204			eprintf("socket:");
205		is_raw = 0;
206	}
207
208	if (ttl > 0) {
209		if (setsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0)
210			eprintf("setsockopt IP_TTL:");
211	}
212
213	/* request that recvfrom returns the ttl value */
214	optval = 1;
215	if (is_raw) {
216		setsockopt(sock, IPPROTO_IP, IP_RECVTTL, &optval, sizeof(optval));
217	}
218
219	if (!qflag) {
220		printf("PING %s (%s) %d bytes of data.\n",
221		       host, inet_ntoa(dst.sin_addr), size);
222	}
223
224	signal(SIGINT, sigint_handler);
225
226	pfd.fd = sock;
227	pfd.events = POLLIN;
228
229	start_time = get_time_ms();
230	deadline_ms = deadline > 0 ? start_time + deadline * 1000.0 : 0.0;
231	next_send_ms = start_time;
232
233	rxbuf = emalloc(size + 1024);
234
235	while (keep_running) {
236		now = get_time_ms();
237
238		if (deadline > 0 && now >= deadline_ms)
239			break;
240
241		if (count > 0 && sent >= count && received >= sent)
242			break;
243
244		if (now >= next_send_ms && (count == 0 || sent < count)) {
245			send_ping(sock, &dst, seq++, size);
246			sent++;
247			next_send_ms = now + interval * 1000.0;
248		}
249
250		timeout_ms = next_send_ms - now;
251		if (deadline > 0) {
252			time_to_deadline = deadline_ms - now;
253			if (time_to_deadline < timeout_ms)
254				timeout_ms = time_to_deadline;
255		}
256		if (timeout_ms < 0)
257			timeout_ms = 0;
258
259		if (count > 0 && sent >= count)
260			timeout_ms = 2000.0;
261
262		p_res = poll(&pfd, 1, (int)timeout_ms);
263		if (p_res < 0) {
264			if (errno == EINTR)
265				continue;
266			eprintf("poll:\n");
267		}
268
269		if (p_res > 0 && (pfd.revents & POLLIN)) {
270			fromlen = sizeof(from);
271			n = recvfrom(sock, rxbuf, size + 1024, 0, (struct sockaddr *)&from, &fromlen);
272			if (n < 0) {
273				if (errno == EINTR || errno == EAGAIN)
274					continue;
275				eprintf("recvfrom:\n");
276			}
277
278			hlen = 0;
279			if (is_raw) {
280				struct ip *ip = (struct ip *)rxbuf;
281				hlen = ip->ip_hl << 2;
282				if (n < hlen + (int)sizeof(*icmp))
283					continue;
284			}
285			icmp = (struct icmphdr *)(rxbuf + hlen);
286
287			if (icmp->type == ICMP_ECHOREPLY &&
288			    (!is_raw || ntohs(icmp->un.echo.id) == (getpid() & 0xffff))) {
289				received++;
290				rtt = get_time_ms();
291				if (n - hlen - (int)sizeof(*icmp) >= (int)sizeof(struct timeval)) {
292					memcpy(&sent_tv, rxbuf + hlen + sizeof(*icmp), sizeof(sent_tv));
293					sent_ms = (double)sent_tv.tv_sec * 1000.0 + (double)sent_tv.tv_usec / 1000.0;
294					rtt -= sent_ms;
295					if (rtt < min_rtt)
296						min_rtt = rtt;
297					if (rtt > max_rtt)
298						max_rtt = rtt;
299					sum_rtt += rtt;
300					if (!qflag) {
301						printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%.3f ms\n",
302						       (int)n, inet_ntoa(from.sin_addr), ntohs(icmp->un.echo.sequence),
303						       is_raw ? ((struct ip *)rxbuf)->ip_ttl : 64, rtt);
304					}
305				} else {
306					if (!qflag) {
307						printf("%d bytes from %s: icmp_seq=%d ttl=%d\n",
308						       (int)n, inet_ntoa(from.sin_addr), ntohs(icmp->un.echo.sequence),
309						       is_raw ? ((struct ip *)rxbuf)->ip_ttl : 64);
310					}
311				}
312			}
313		}
314	}
315
316	free(rxbuf);
317	close(sock);
318
319	print_stats(host, sent, received, min_rtt, max_rtt, sum_rtt);
320
321	return received > 0 ? 0 : 1;
322}