ICMP Ping Program in C | Generated by AI
ICMP (Internet Control Message Protocol) is a network layer protocol used for diagnostic and error-reporting purposes in IP networks. It’s most famously used by the ping
utility to test connectivity between hosts. ICMP messages are encapsulated directly in IP packets (no TCP or UDP layer) and consist of a type, code, checksum, and payload. For ping
, the “Echo Request” (Type 8) and “Echo Reply” (Type 0) messages are key.
Here’s how ICMP works at a high level:
- A sender (e.g., your machine) sends an ICMP Echo Request to a target IP.
- The target, if reachable and configured to respond, sends back an ICMP Echo Reply.
- The sender measures the round-trip time (RTT) based on when the reply arrives.
To implement a ping
-like program in C without libraries like libpcap
or libnet
, we’ll use raw sockets (SOCK_RAW
) to craft and send ICMP packets directly. This requires root privileges (e.g., sudo
) because raw sockets bypass higher-level abstractions.
Below is a simplified C program that sends ICMP Echo Requests and listens for Echo Replies, mimicking basic ping
functionality.
ICMP Ping Program in C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <time.h>
#include <errno.h>
// Compute ICMP checksum
unsigned short checksum(void *b, int len) {
unsigned short *buf = b;
unsigned int sum = 0;
unsigned short result;
for (sum = 0; len > 1; len -= 2) {
sum += *buf++;
}
if (len == 1) {
sum += *(unsigned char *)buf;
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
result = ~sum;
return result;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s <destination_ip>\n", argv[0]);
exit(EXIT_FAILURE);
}
int sock_fd;
struct sockaddr_in dest_addr;
char packet[64]; // ICMP header + some data
struct icmphdr *icmp;
char recv_buffer[1024];
struct timespec start, end;
// Create raw socket for ICMP
sock_fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sock_fd < 0) {
perror("Socket creation failed (run as root?)");
exit(EXIT_FAILURE);
}
// Set up destination address
dest_addr.sin_family = AF_INET;
if (inet_pton(AF_INET, argv[1], &dest_addr.sin_addr) <= 0) {
perror("Invalid IP address");
close(sock_fd);
exit(EXIT_FAILURE);
}
// Prepare ICMP Echo Request packet
memset(packet, 0, sizeof(packet));
icmp = (struct icmphdr *)packet;
icmp->type = ICMP_ECHO; // Type 8: Echo Request
icmp->code = 0;
icmp->un.echo.id = getpid(); // Use process ID as identifier
icmp->un.echo.sequence = 1; // Sequence number
for (int i = sizeof(*icmp); i < 64; i++) {
packet[i] = i; // Fill payload with dummy data
}
icmp->checksum = 0; // Compute checksum after filling packet
icmp->checksum = checksum(packet, 64);
printf("Pinging %s...\n", argv[1]);
for (int i = 0; i < 4; i++) { // Send 4 pings
// Record send time
clock_gettime(CLOCK_MONOTONIC, &start);
// Send ICMP Echo Request
if (sendto(sock_fd, packet, 64, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
perror("Send failed");
close(sock_fd);
exit(EXIT_FAILURE);
}
// Receive ICMP Echo Reply
struct sockaddr_in from_addr;
socklen_t from_len = sizeof(from_addr);
int bytes_received = recvfrom(sock_fd, recv_buffer, sizeof(recv_buffer), 0,
(struct sockaddr *)&from_addr, &from_len);
if (bytes_received < 0) {
perror("Receive failed");
continue;
}
// Record receive time
clock_gettime(CLOCK_MONOTONIC, &end);
// Parse received IP and ICMP headers
struct iphdr *ip = (struct iphdr *)recv_buffer;
struct icmphdr *icmp_reply = (struct icmphdr *)(recv_buffer + (ip->ihl * 4));
if (icmp_reply->type == ICMP_ECHOREPLY && icmp_reply->un.echo.id == getpid()) {
double rtt = (end.tv_sec - start.tv_sec) * 1000.0 +
(end.tv_nsec - start.tv_nsec) / 1000000.0;
printf("%d bytes from %s: seq=%d time=%.2f ms\n",
bytes_received - (ip->ihl * 4), argv[1], icmp_reply->un.echo.sequence, rtt);
} else {
printf("Unexpected ICMP response: type=%d\n", icmp_reply->type);
}
sleep(1); // Wait 1 second between pings
icmp->un.echo.sequence++; // Increment sequence number
icmp->checksum = 0;
icmp->checksum = checksum(packet, 64); // Recalculate checksum
}
close(sock_fd);
return 0;
}
How It Works
- Socket Creation:
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
creates a raw socket for ICMP. This lets us craft ICMP packets manually.
- ICMP Packet Construction:
- The ICMP header (
struct icmphdr
) is filled with:type = ICMP_ECHO
(8) for Echo Request.code = 0
.id
set to the process ID to identify our pings.sequence
to track individual requests.
- A payload (dummy data) is added, and the checksum is calculated over the entire packet.
- The ICMP header (
- Sending:
sendto()
sends the packet to the destination IP. No port is needed since ICMP operates below the transport layer.
- Receiving:
recvfrom()
captures the raw IP packet containing the ICMP Echo Reply.- We skip the IP header (
ihl * 4
bytes) to reach the ICMP header. - Check if it’s an Echo Reply (Type 0) and matches our
id
.
- Timing:
clock_gettime()
measures the RTT in milliseconds.
- Checksum:
- The
checksum()
function computes the 16-bit one’s complement sum, as required by ICMP.
- The
Compilation and Usage
- Compile:
gcc ping.c -o ping
- Run (as root):
sudo ./ping 8.8.8.8
- Example output:
Pinging 8.8.8.8... 64 bytes from 8.8.8.8: seq=1 time=12.34 ms 64 bytes from 8.8.8.8: seq=2 time=11.89 ms ...
Notes
- Root Privileges: Raw sockets require
sudo
because they manipulate network packets directly. - Firewall: The target must respond to ICMP Echo Requests (some hosts block them).
- Simplifications: This version doesn’t handle all edge cases (e.g., timeouts, packet loss fully, or IP options).
This is as low-level as you can get in user space with ICMP. Below this, you’d need kernel-level code to interact with the IP stack directly. Let me know if you want to tweak it further!