186 lines
5.6 KiB
C
186 lines
5.6 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/ip_icmp.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/time.h>
|
|
#include <netdb.h>
|
|
#include <math.h>
|
|
#include <errno.h>
|
|
|
|
// Parse a string into an integer.
|
|
// Returns 0 if it fails.
|
|
int parse_int(char *s, int *result) {
|
|
if (s == NULL || result == NULL)
|
|
return 0;
|
|
return sscanf(s, "%d", result) == 1;
|
|
}
|
|
|
|
// Compute ICMP checksum.
|
|
// It uses a standard 16-bit one's complement sum algorithm.
|
|
unsigned short checksum(void *b, int len) {
|
|
// points to the first 2-byte length of b
|
|
unsigned short *buf = b;
|
|
|
|
// set sum to zero
|
|
unsigned int sum = 0;
|
|
|
|
// split the packet into 2-byte chunks and sum it
|
|
for (sum = 0; len > 1; len -= 2)
|
|
sum += *buf++;
|
|
|
|
// if length is odd, last byte is treated as a 16-bit word by padding it with a zero
|
|
if (len > 0)
|
|
sum += *(unsigned char *)buf;
|
|
|
|
// any bits beyond 16 bits are shift and added to the lower 16 bits
|
|
sum = (sum >> 16) + (sum & 0xFFFF);
|
|
sum += (sum >> 16);
|
|
|
|
return (unsigned short) ~sum;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
if (argc < 2) {
|
|
printf("usage: %s <hostname or IP>\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
// parse packets number optional arg
|
|
char **args = argv;
|
|
int count = 4; // default packet number to send
|
|
for(int argpos = 1; argpos < argc; argpos++) {
|
|
if (!strcmp(*args++, "-c")) {
|
|
if (!parse_int(*args, &count)) {
|
|
printf("unable to parse ping count number\n");
|
|
printf("usage: <hostname or IP> -c <number>\n");
|
|
return 1;
|
|
}
|
|
continue;
|
|
}
|
|
args++;
|
|
}
|
|
|
|
// DNS resolution
|
|
struct addrinfo hints, *res;
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = AF_INET;
|
|
hints.ai_socktype = SOCK_RAW;
|
|
hints.ai_protocol = IPPROTO_ICMP;
|
|
|
|
int status = getaddrinfo(argv[1], NULL, &hints, &res);
|
|
if (status != 0) {
|
|
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
|
|
return 1;
|
|
}
|
|
|
|
// extract IP address
|
|
char ip_str[INET_ADDRSTRLEN];
|
|
struct sockaddr_in *addr = (struct sockaddr_in *)res->ai_addr;
|
|
inet_ntop(AF_INET, &(addr->sin_addr), ip_str, INET_ADDRSTRLEN);
|
|
printf("PING %s (%s): 56 data bytes\n", argv[1], ip_str);
|
|
|
|
// create raw socket for ICMP
|
|
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
|
|
if (sock < 0) {
|
|
perror("socket");
|
|
freeaddrinfo(res);
|
|
return 1;
|
|
}
|
|
|
|
// set destination address
|
|
struct sockaddr_in dest = *addr;
|
|
|
|
// ICMP packet (header + 56-byte payload)
|
|
char packet[64];
|
|
struct icmp *icmp = (struct icmp *)packet;
|
|
icmp->icmp_type = ICMP_ECHO; // type 8 (Echo Request)
|
|
icmp->icmp_code = 0;
|
|
icmp->icmp_id = getpid() & 0xFFFF; // use process ID as identifier fitting into 16 bits
|
|
icmp->icmp_seq = 0;
|
|
icmp->icmp_cksum = 0;
|
|
memset(packet + 8, 0, 56); // zero payload
|
|
|
|
struct timeval timeout;
|
|
timeout.tv_sec = 2;
|
|
timeout.tv_usec = 0;
|
|
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
|
|
|
int sent = 0, received = 0;
|
|
double min_rtt = 1e9, max_rtt = 0, sum_rtt = 0, sum_sq_rtt = 0;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
icmp->icmp_seq = i + 1;
|
|
icmp->icmp_cksum = 0;
|
|
icmp->icmp_cksum = checksum(packet, 64);
|
|
|
|
// record send time
|
|
struct timeval send_time, recv_time;
|
|
gettimeofday(&send_time, NULL);
|
|
sent++;
|
|
|
|
// send ICMP packet
|
|
if (sendto(sock, packet, 64, 0, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
|
|
perror("sendto");
|
|
continue;
|
|
}
|
|
|
|
// receive reply
|
|
char recv_buf[128];
|
|
struct sockaddr_in from;
|
|
socklen_t from_len = sizeof(from);
|
|
int n = recvfrom(sock, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&from, &from_len);
|
|
gettimeofday(&recv_time, NULL);
|
|
|
|
if (n < 0) {
|
|
printf("Request timed out\n");
|
|
continue;
|
|
}
|
|
|
|
// extract IP and ICMP headers
|
|
struct iphdr *ip = (struct iphdr *)recv_buf;
|
|
struct icmp *recv_icmp = (struct icmp *)(recv_buf + (ip->ihl << 2));
|
|
|
|
// verify reply
|
|
if (n >= (int)(ip->ihl << 2) + 8 && recv_icmp->icmp_type == ICMP_ECHOREPLY &&
|
|
recv_icmp->icmp_id == icmp->icmp_id && recv_icmp->icmp_seq == icmp->icmp_seq) {
|
|
|
|
double rtt = (recv_time.tv_sec - send_time.tv_sec) * 1000.0 +
|
|
(recv_time.tv_usec - send_time.tv_usec) / 1000.0;
|
|
received++;
|
|
|
|
sum_rtt += rtt;
|
|
sum_sq_rtt += rtt * rtt;
|
|
|
|
if (rtt < min_rtt) min_rtt = rtt;
|
|
if (rtt > max_rtt) max_rtt = rtt;
|
|
|
|
printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%.1f ms\n",
|
|
n - (ip->ihl << 2), ip_str, recv_icmp->icmp_seq, ip->ttl, rtt);
|
|
} else {
|
|
printf("Invalid reply\n");
|
|
}
|
|
|
|
sleep(1);
|
|
}
|
|
|
|
printf("\n--- %s ping statistics ---\n", argv[1]);
|
|
printf("%d packets transmitted, %d received, %.0f%% packet loss\n",
|
|
sent, received, (sent - received) * 100.0 / sent);
|
|
|
|
// see for stddev computation: https://en.wikipedia.org/wiki/Standard_deviation
|
|
if (received > 0) {
|
|
double avg_rtt = sum_rtt / received;
|
|
double stddev = received > 1 ? sqrt((sum_sq_rtt - sum_rtt * sum_rtt / received) / (received - 1)) : 0;
|
|
|
|
printf("rtt min/avg/max/stddev = %.3f/%.3f/%.3f/%.3f ms\n",
|
|
min_rtt, avg_rtt, max_rtt, stddev);
|
|
}
|
|
|
|
close(sock);
|
|
freeaddrinfo(res);
|
|
return 0;
|
|
} |