init repo
This commit is contained in:
commit
4772a7dd2a
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.vscode
|
||||
|
||||
sping
|
||||
10
Makefile
Normal file
10
Makefile
Normal file
@ -0,0 +1,10 @@
|
||||
HOST := google.com
|
||||
|
||||
format:
|
||||
@astyle --style=java --recursive *.c,*.h -n || true
|
||||
|
||||
build: format
|
||||
@gcc -o sping sping.c -Wall -lm
|
||||
|
||||
run: build
|
||||
@sudo ./sping $(HOST) -c 6
|
||||
68
README.md
Normal file
68
README.md
Normal file
@ -0,0 +1,68 @@
|
||||
# sping
|
||||
|
||||
A simple **ping** implementation to briefly details how it works under the hood.
|
||||
|
||||
## Overview
|
||||
|
||||
The **Internet Control Message Protocol (ICMP)** is a network layer protocol in the IP suite used for diagnostics, error reporting, and network management. It supports tools like `ping` by sending messages to test connectivity and measure round-trip time.
|
||||
|
||||
How ICMP Works ?
|
||||
* **Purpose**: Facilitates error reporting (e.g., "destination unreachable") and diagnostic queries (e.g., Echo Request/Reply for ping).
|
||||
* **Operation**: ICMP packets are encapsulated in IP packets (IPv4/IPv6) and sent between hosts or routers. No TCP/UDP is used.
|
||||
* **Key Messages**:
|
||||
* Echo Request (Type 8): Sent by ping to test if a host is reachable.
|
||||
* Echo Reply (Type 0): Response from the target confirming reachability.
|
||||
* Other types: Destination Unreachable (Type 3), Time Exceeded (Type 11).
|
||||
|
||||
Below, the ICMP packet structure:
|
||||
```ascii
|
||||
+-------------------------------+
|
||||
| Ethernet Frame (optional) |
|
||||
|-------------------------------|
|
||||
| Destination MAC (6 bytes) |
|
||||
| Source MAC (6 bytes) |
|
||||
| EtherType (2 bytes) = 0x0800 |
|
||||
+-------------------------------+
|
||||
| IPv4 Header (20 bytes) |
|
||||
|-------------------------------|
|
||||
| Version (4b) = 4 |
|
||||
| Header Len (4b) = 5 |
|
||||
| Type of Service (8b) = 0 |
|
||||
| Total Length (16b) = 84 |
|
||||
| Identification (16b) |
|
||||
| Flags/Offset (16b) = 0 |
|
||||
| TTL (8b) = e.g., 64 |
|
||||
| Protocol (8b) = 1 (ICMP) |
|
||||
| Checksum (16b) |
|
||||
| Source IP (32b) |
|
||||
| Destination IP (32b) |
|
||||
+-------------------------------+
|
||||
| ICMP Packet (64 bytes def) |
|
||||
|-------------------------------|
|
||||
| Type (8b) = 8 / 0 |
|
||||
| Code (8b) = 0 |
|
||||
| Checksum (16b) |
|
||||
| Identifier (16b) |
|
||||
| Sequence Number (16b) |
|
||||
| Payload (56 bytes, e.g., 0s) |
|
||||
+-------------------------------+
|
||||
| Ethernet FCS (4 bytes) |
|
||||
+-------------------------------+
|
||||
```
|
||||
|
||||
|
||||
## Build
|
||||
Of course a C compiler must be installed.
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
## Run
|
||||
Run the build and launch the program, you must have the superuser rights to run it (raw socket access).
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
By default, packets are sent to [google.com](google.com), if you want to change the destination update **HOST** variable:
|
||||
```bash
|
||||
make run HOST=anotherhost.com
|
||||
```
|
||||
186
sping.c
Normal file
186
sping.c
Normal file
@ -0,0 +1,186 @@
|
||||
#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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user