init repo

This commit is contained in:
rmanach 2025-08-29 12:07:27 +02:00
commit 4772a7dd2a
4 changed files with 267 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.vscode
sping

10
Makefile Normal file
View 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
View 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
View 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;
}