ping

an implementation of the ping utility written in C using ICMP


ping / ping.c

ping.c (6.5K) raw | download
1#define _DEFAULT_SOURCE
2#define _POSIX_C_SOURCE 200809L
3#include <sys/socket.h>
4#include <sys/select.h>
5#include <netinet/ip.h>
6#include <netinet/ip_icmp.h>
7#include <netdb.h>
8#include <arpa/inet.h>
9#include <signal.h>
10#include <unistd.h>
11#include <errno.h>
12#include <stdint.h>
13#include <stdio.h>
14#include <string.h>
15#include <time.h>
16
17static volatile sig_atomic_t stopped;
18
19static void
20stop_ping(int sig)
21{
22 (void)sig;
23 stopped = 1;
24}
25
26static uint16_t
27checksum(const unsigned char *data, size_t len)
28{
29 uint32_t sum = 0;
30 while (len >= 2) {
31 sum += (uint32_t)data[0] * 256 + data[1];
32 data += 2;
33 len -= 2;
34 }
35 if (len) sum += (uint32_t)data[0] * 256;
36 while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
37 return (uint16_t)~sum;
38}
39
40static double
41now_ms(void)
42{
43 struct timespec ts;
44 if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) return -1;
45 return (double)ts.tv_sec * 1000 + (double)ts.tv_nsec / 1000000;
46}
47
48static int
49match_reply(const unsigned char *packet, size_t size, struct in_addr target,
50 uint16_t id, uint16_t seq, int *ttl)
51{
52 struct iphdr ip;
53 struct icmphdr icmp;
54 size_t header, total;
55 if (size < sizeof(ip)) return 0;
56 memcpy(&ip, packet, sizeof(ip));
57 if (ip.version != 4 || ip.ihl < 5 || ip.protocol != IPPROTO_ICMP ||
58 ip.saddr != target.s_addr || (ntohs(ip.frag_off) & (IP_MF | IP_OFFMASK)))
59 return 0;
60 header = (size_t)ip.ihl * 4;
61 total = ntohs(ip.tot_len);
62 if (total < header || total > size || total - header != 64) return 0;
63 memcpy(&icmp, packet + header, sizeof(icmp));
64 if (icmp.type != ICMP_ECHOREPLY || icmp.code != 0 ||
65 ntohs(icmp.un.echo.id) != id || ntohs(icmp.un.echo.sequence) != seq ||
66 checksum(packet + header, total - header) != 0)
67 return 0;
68 *ttl = ip.ttl;
69 return (int)(total - header);
70}
71
72int
73main(int argc, char **argv)
74{
75 struct addrinfo hints = {0}, *addresses;
76 struct sockaddr_in target;
77 struct sigaction action = {0};
78 unsigned char request[64] = {0}, reply[65536];
79 char address[INET_ADDRSTRLEN];
80 uint16_t id = (uint16_t)getpid(), seq = 0;
81 unsigned long transmitted = 0, received = 0;
82 double minimum = 0, maximum = 0, sum = 0;
83 int fd, result, status = 0;
84
85 if (argc != 2) {
86 fprintf(stderr, "usage: %s host\n", argv[0]);
87 return 1;
88 }
89 hints.ai_family = AF_INET;
90 hints.ai_socktype = SOCK_RAW;
91 hints.ai_protocol = IPPROTO_ICMP;
92 result = getaddrinfo(argv[1], NULL, &hints, &addresses);
93 if (result != 0) {
94 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result));
95 return 1;
96 }
97 target = *(const struct sockaddr_in *)addresses->ai_addr;
98 freeaddrinfo(addresses);
99 if (inet_ntop(AF_INET, &target.sin_addr, address, sizeof(address)) == NULL) {
100 perror("inet_ntop");
101 return 1;
102 }
103 fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
104 if (fd < 0) { perror("socket"); return 1; }
105 if (fd >= FD_SETSIZE) {
106 fprintf(stderr, "socket descriptor exceeds select limit\n");
107 close(fd);
108 return 1;
109 }
110 action.sa_handler = stop_ping;
111 sigemptyset(&action.sa_mask);
112 if (sigaction(SIGINT, &action, NULL) < 0) {
113 perror("sigaction");
114 close(fd);
115 return 1;
116 }
117 if (printf("PING %s (%s)\n", argv[1], address) < 0 || fflush(stdout) == EOF) {
118 perror("stdout");
119 close(fd);
120 return 1;
121 }
122 while (!stopped) {
123 struct icmphdr echo = {0};
124 double sent, deadline;
125 ssize_t n;
126 int answered = 0;
127 ++seq;
128 echo.type = ICMP_ECHO;
129 echo.un.echo.id = htons(id);
130 echo.un.echo.sequence = htons(seq);
131 memcpy(request, &echo, sizeof(echo));
132 echo.checksum = htons(checksum(request, sizeof(request)));
133 memcpy(request, &echo, sizeof(echo));
134 sent = now_ms();
135 if (sent < 0) { perror("clock_gettime"); status = 1; break; }
136 do {
137 n = sendto(fd, request, sizeof(request), 0,
138 (struct sockaddr *)&target, sizeof(target));
139 } while (n < 0 && errno == EINTR && !stopped);
140 if (n == (ssize_t)sizeof(request)) ++transmitted;
141 if (stopped) break;
142 if (n != (ssize_t)sizeof(request)) {
143 if (n >= 0) errno = EIO;
144 perror("sendto");
145 status = 1;
146 break;
147 }
148 deadline = sent + 1000;
149 while (!stopped) {
150 fd_set readable;
151 struct timeval timeout;
152 double now = now_ms(), elapsed;
153 long remaining;
154 int ttl, bytes;
155 if (now < 0) { perror("clock_gettime"); status = 1; goto done; }
156 if (now >= deadline) break;
157 remaining = (long)((deadline - now) * 1000) + 1;
158 timeout.tv_sec = remaining / 1000000;
159 timeout.tv_usec = remaining % 1000000;
160 FD_ZERO(&readable);
161 FD_SET(fd, &readable);
162 result = select(fd + 1, &readable, NULL, NULL, &timeout);
163 if (result < 0 && errno == EINTR) continue;
164 if (result < 0) { perror("select"); status = 1; goto done; }
165 if (result == 0) break;
166 n = recvfrom(fd, reply, sizeof(reply), 0, NULL, NULL);
167 if (n < 0 && errno == EINTR) continue;
168 if (n < 0) { perror("recvfrom"); status = 1; goto done; }
169 bytes = match_reply(reply, (size_t)n, target.sin_addr, id, seq, &ttl);
170 if (!bytes || answered) continue;
171 now = now_ms();
172 if (now < 0) { perror("clock_gettime"); status = 1; goto done; }
173 elapsed = now - sent;
174 if (!received || elapsed < minimum) minimum = elapsed;
175 if (!received || elapsed > maximum) maximum = elapsed;
176 sum += elapsed;
177 ++received;
178 answered = 1;
179 printf("%d bytes from %s: icmp_seq=%u ttl=%d time=%.1f ms\n",
180 bytes, address, (unsigned int)seq, ttl, elapsed);
181 if (fflush(stdout) == EOF) { perror("stdout"); status = 1; goto done; }
182 }
183 }
184done:
185 close(fd);
186 printf("\n--- %s ping statistics ---\n", argv[1]);
187 printf("%lu packets transmitted, %lu received, %.0f%% packet loss\n",
188 transmitted, received, transmitted ?
189 100.0 * (double)(transmitted - received) / (double)transmitted : 0.0);
190 if (received)
191 printf("rtt min/avg/max = %.1f/%.1f/%.1f ms\n", minimum,
192 sum / (double)received, maximum);
193 if (fflush(stdout) == EOF) { perror("stdout"); status = 1; }
194 return status;
195}