This project is about recoding the ping command.
subject v.5.1
Project Structure π
.
βββ Dockerfile
βββ LICENSE
βββ README.md
βββ _subject
β βββ en.subject.pdf
βββ project
βββ Makefile
βββ ft_ping
βββ include
β βββ ft_ping.h
βββ src
βββ libft
β βββ ft_free.c
β βββ ft_int_len.c
β βββ ft_str_realloc.c
β βββ ft_strf.c
βββ logger.c
βββ main.c
βββ packets.c
βββ parser.c
βββ ping.c
βββ socket.c
βββ verbose.c
After cloning the repository, you can opt to install it on a Debian-based system, or via Docker for greater portability.
In this case you will need to make sure you have all the dependencies, or
install the meta-packege build-essential
, which already contains all the tools
needed to compile the program:
apt-get install -y --no-install-recommends build-essential
and then:
cd ft_ping/project
make
./ft_ping [COMMAND] [OPTIONS]
If you choose installation via Docker, you don't need anything other than Docker itself.
cd ft_ping/
docker build -t ft_ping .
docker run --rm ft_ping [COMMAND] [OPTIONS]
This reimplementation of ping
is inspired by the version of
GNU inetutils v2.0
, below is a brief introduction and installation guide for
testing purposes.
The ping
command sends ICMP ECHO_REQUEST packets to a host to check
connectivity.
It is commonly used to:
- Verify network status.
- Diagnose connectivity issues.
- Measure response times and packet loss.
Each request includes IP and ICMP headers plus timing data.
By default, ping
sends packets once per second until interrupted (Ctrl-C).
It reports round-trip times, statistics, and a summary upon completion.
The target can be a hostname or IP address.
Installing GNU inetutils 2.0 on Debian-based systems:
-
Remove the default ping:
apt remove iputils-ping
-
Install build tools:
apt update apt install build-essential wget
-
Download version 2.0 sources:
wget https://ftp.gnu.org/gnu/inetutils/inetutils-2.0.tar.gz
-
Extract and compile:
tar -xvf inetutils-2.0.tar.gz cd inetutils-2.0 ./configure --prefix=/usr/local/inetutils-2.0 make make install
-
Use the specific binary:
/usr/local/inetutils-2.0/bin/ping --version
or create an alias:
echo "alias ping='/usr/local/inetutils-2.0/bin/ping'" >> ~/.bashrc source ~/.bashrc
This implementation uses raw sockets to craft and send fully custom ICMP packets, including the IPv4 header. Two separate sockets are created:
-
A raw IP socket
socket(AF_INET, SOCK_RAW, IPPROTO_RAW)
for sending packets with manually constructed IPv4 headers. -
A raw ICMP socket
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
for receiving ICMP Echo Replies and other ICMP messages.
With raw sockets and the IP_HDRINCL
socket option enabled, the application takes full control over the entire packet structure:
-
The kernel does not generate the IPv4 header automatically. Instead, your program must manually construct the IPv4 header, specifying all fields (Version, Header Length, Total Length, Identification, TTL, Protocol, etc.).
-
You must also build the ICMP header yourself, setting the Type, Code, Identifier, Sequence Number, and computing the correct checksum.
-
A custom payload is appended to the ICMP packet, it includes a timestamp and arbitrary data for testing round-trip times and verifying packet integrity.
-
On reception, the program receives the full raw IP packet (including the IPv4 header), so it must parse the header manually to extract and validate the embedded ICMP Echo Reply.
Note:
-
Without
IP_HDRINCL
, the kernel would automatically fill in the IPv4 header based on your destination address and other defaults, and you would only need to provide the transport-layer payload (ICMP). WithIP_HDRINCL
, you get full control but also full responsibility. -
Because raw sockets allow generating arbitrary network traffic, they require elevated privileges (root or
CAP_NET_RAW
capability).
Each packet sent by this implementation contains a manually constructed IPv4 header.
Below is an overview of its fields:
Field | Size (bits) | Description |
---|---|---|
Version | 4 | IP protocol version. Always set to 4 for IPv4. |
IHL | 4 | Internet Header Length in 32-bit words. Usually 5 (20 bytes). |
Type of Service | 8 | Specifies priority and QoS. |
Total Length | 16 | Full length of the packet (header + payload). |
Identification | 16 | Unique identifier for fragmentation. |
Flags | 3 | Control flags (e.g., Don't Fragment). |
Fragment Offset | 13 | Offset of a fragment in the original datagram. |
TTL | 8 | Time To Live, decremented by each router hop. |
Protocol | 8 | Encapsulated protocol number (1 for ICMP). |
Header Checksum | 16 | Checksum of the header. Calculated manually. |
Source Address | 32 | IPv4 address of the sender. |
Destination Address | 32 | IPv4 address of the recipient. |
Note:
- The
IP_HDRINCL
socket option requires the application to populate all fields and compute the header checksum. - The "Don't Fragment" flag is set to avoid fragmentation.
- The source address can be set to
INADDR_ANY
, in which case the kernel fill in the actual address.
The Internet Control Message Protocol (ICMP) is part of the Internet protocol suite (RFC 792). It is used by network devices to send error messages and operational information, such as when a host is unreachable or a service is unavailable. Unlike TCP and UDP, ICMP is mainly for diagnostics (e.g., ping, traceroute) and error reporting (RFC 1122).
ICMP operates at Layer 3 (the Network Layer) of the OSI model, the same layer as IP.
Each ICMP message starts with a fixed header:
Field | Size (bits) | Description |
---|---|---|
Type | 8 | Specifies the ICMP message type (e.g., 0=Echo Reply, 8=Echo Request). |
Code | 8 | Further refines the message type. |
Checksum | 16 | Verifies the integrity of the ICMP header and payload. |
Rest of Header | 32 | Depends on Type and Code. For Echo messages, contains Identifier and Sequence Number. |
Echo Request/Reply additional fields:
Subfield | Size (bits) | Description |
---|---|---|
Identifier | 16 | Helps match requests to replies. |
Sequence | 16 | Incremented with each request. |
These message types define ICMP control and diagnostic functions as specified in
RFC 792 and related updates.
Each Type identifies the high-level purpose of the message, while the Code field
provides additional context.
Below are common ICMP types:
Type | Name | Description |
---|---|---|
0 | Echo Reply | Reply to an Echo Request. |
3 | Destination Unreachable | Packet could not be delivered. |
5 | Redirect | Redirect routing (e.g., better gateway). |
8 | Echo Request | Ping request. |
9 | Router Advertisement | Router discovery messages. |
10 | Router Solicitation | Request for router advertisement. |
11 | Time Exceeded | TTL expired in transit. |
12 | Parameter Problem | Invalid header field. |
13 | Timestamp Request | Request timestamp. |
14 | Timestamp Reply | Reply to timestamp request. |
The meaning of the Code field depends on the Type.
Below are common codes for a few types:
Type | Code | Meaning |
---|---|---|
3 | 0 | Network Unreachable |
3 | 1 | Host Unreachable |
3 | 2 | Protocol Unreachable |
3 | 3 | Port Unreachable |
3 | 4 | Fragmentation needed and DF set |
3 | 5 | Source Route Failed |
11 | 0 | TTL expired in transit |
11 | 1 | Fragment reassembly time exceeded |
The ICMP payload immediately follows the header.
For Echo Request and Echo Reply packets, it typically contains:
- A timestamp (e.g.,
struct timeval
) used to measure round-trip time (RTT). - Arbitrary data bytes (padding) to reach the desired packet size.
The checksum field covers the entire ICMP message, including both the header and the payload. It is calculated as the one's complement of the one's complement sum of all 16-bit words in the message.
If the total length of the message is odd, the final byte is padded with a zero byte before computing the checksum.
Formula: checksum = ~ (sum of all 16-bit words)
Note:
A valid checksum is required for the receiver to accept and process the ICMP packet.
If the checksum is incorrect, the packet will be discarded.
You can inspect the data both live during execution, using GDB and passively using Wireshark.
When you receive a packet via recvfrom()
, the buffer contains the entire raw IP packet, starting from the first byte of the IPv4 header (offset 0).
This means:
- Offset 0β19: IPv4 header (fixed part, 20 bytes)
- Offset 20βN: ICMP header and payload
Each field occupies specific offsets in the buffer, matching the protocol specification (RFC 791 for IPv4, RFC 792 for ICMP).
In the example below, we display the full packet ([IPv4 header] + [ICMP header] + [ICMP payload]), which by default is 84 bytes long:
(gdb) x/84bx rbuf
0x55555555db00: 0x45 0x00 0x00 0x54 0x34 0x48 0x00 0x00
0x55555555db08: 0xfc 0x01 0x78 0x30 0x0a 0x00 0x00 0x01
0x55555555db10: 0xac 0x1e 0x5c 0x11 0x00 0x00 0x90 0x88
0x55555555db18: 0xe7 0xc3 0x00 0x00 0xee 0xea 0x66 0x68
0x55555555db20: 0x00 0x00 0x00 0x00 0xae 0xce 0x06 0x00
0x55555555db28: 0x00 0x00 0x00 0x00 0x00 0x01 0x02 0x03
0x55555555db30: 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b
0x55555555db38: 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13
0x55555555db40: 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b
0x55555555db48: 0x1c 0x1d 0x1e 0x1f 0x20 0x21 0x22 0x23
0x55555555db50: 0x24 0x25 0x26 0x27
Knowing the offset of each field inside the IPv4 header, you can extract and print readable values.
For example the source IP address is located between byte 12 and byte 15 of the IPv4 header and destination between byte 16 and byte 19, you can print it as a human-readable string like this:
(gdb) printf "%d.%d.%d.%d\n", rbuf[12], rbuf[13], rbuf[14], rbuf[15]
10.0.0.1
(gdb) printf "%d.%d.%d.%d\n", rbuf[16], rbuf[17], rbuf[18], rbuf[19]
172.30.92.17
Wireshark can capture and decode ICMP packets in real time.
It provides a detailed, human-readable view of each field in the IPv4 and ICMP headers, making it an essential tool to verify that raw packets are crafted correctly.
You can filter the traffic with some useful filters:
icmp
β Show all ICMP packets (requests and replies).icmp.type == 8
β Show only Echo Requests.icmp.type == 0
β Show only Echo Replies.ip.src == <your_ip>
β Filter packets sent by your host.ip.dst == <target_ip>
β Filter packets sent to the target.
Below is an example of an ICMP Echo Request packet as dissected by Wireshark:
Internet Protocol Version 4, Src: 172.30.92.17, Dst: 10.0.0.1
0100 .... = Version: 4
.... 0101 = Header Length: 20 bytes (5)
Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
Total Length: 84
Identification: 0xcecd (52941)
010. .... = Flags: 0x2, Don't fragment
...0 0000 0000 0000 = Fragment Offset: 0
Time to Live: 64
Protocol: ICMP (1)
Header Checksum: 0x59ab [validation disabled]
[Header checksum status: Unverified]
Source Address: 172.30.92.17
Destination Address: 10.0.0.1
[Stream index: 1]
Internet Control Message Protocol
Type: 8 (Echo (ping) request)
Code: 0
Checksum: 0xb471 [correct]
[Checksum Status: Good]
Identifier (BE): 52941 (0xcecd)
Identifier (LE): 52686 (0xcdce)
Sequence Number (BE): 0 (0x0000)
Sequence Number (LE): 0 (0x0000)
[Response frame: 8]
Timestamp from icmp data: Jul 3, 2025 22:23:51.516017000 W. Europe Daylight Time
[Timestamp from icmp data (relative): -0.303915000 seconds]
Data (40 bytes)
Data: 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627
[Length: 40]
Here is the same packet shown as a raw hex dump:
# IPv4 Header (20 bytes)
45 00 00 54 ce cd 40 00 40 01 59 ab ac 1e 5c 11
0a 00 00 01
# ICMP Header + Payload
08 00 b4 71 ce cd 00 00 d7 e6 66 68 00 00 00 00
b1 df 07 00 00 00 00 00 00 01 02 03 04 05 06 07
08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27
-
RFC
-
Man Pages
-
Other
See LICENSE