// Simple ICMP ping for macOS (also works on Linux where unprivileged ICMP is allowed).
// Build: clang++ -std=c++17 -Wall -o myping ping.cpp
// Run: ./myping localhost [count]
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
struct IcmpHeader {
uint8_t type;
uint8_t code;
uint16_t checksum;
uint16_t id;
uint16_t seq;
};
static const uint8_t ICMP_ECHO_REQUEST = 8;
static const uint8_t ICMP_ECHO_REPLY = 0;
uint16_t checksum(const void* data, size_t len) {
const uint16_t* p = static_cast<const uint16_t*>(data);
uint32_t sum = 0;
while (len > 1) { sum += *p++; len -= 2; }
if (len == 1) sum += *reinterpret_cast<const uint8_t*>(p);
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
return static_cast<uint16_t>(~sum);
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <host> [count]\n";
return 1;
}
const char* host = argv[1];
int count = (argc >= 3) ? std::atoi(argv[2]) : 4;
// Resolve hostname to IPv4 address
addrinfo hints{}, *res = nullptr;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
if (int rc = getaddrinfo(host, nullptr, &hints, &res); rc != 0) {
std::cerr << "Cannot resolve " << host << ": " << gai_strerror(rc) << "\n";
return 1;
}
sockaddr_in dest = *reinterpret_cast<sockaddr_in*>(res->ai_addr);
freeaddrinfo(res);
char ipstr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &dest.sin_addr, ipstr, sizeof(ipstr));
// SOCK_DGRAM + IPPROTO_ICMP lets a normal user send pings on macOS (no sudo needed)
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (sock < 0) {
perror("socket");
return 1;
}
timeval tv{2, 0}; // 2 second receive timeout
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
std::printf("PING %s (%s)\n", host, ipstr);
const size_t payloadSize = 56;
int sent = 0, received = 0;
double minRtt = 1e9, maxRtt = 0, totalRtt = 0;
uint16_t id = static_cast<uint16_t>(getpid() & 0xFFFF);
for (int seq = 0; seq < count; ++seq) {
char packet[sizeof(IcmpHeader) + payloadSize];
std::memset(packet, 0, sizeof(packet));
auto* hdr = reinterpret_cast<IcmpHeader*>(packet);
hdr->type = ICMP_ECHO_REQUEST;
hdr->code = 0;
hdr->id = htons(id);
hdr->seq = htons(static_cast<uint16_t>(seq));
std::memset(packet + sizeof(IcmpHeader), 0xA5, payloadSize);
hdr->checksum = 0;
hdr->checksum = checksum(packet, sizeof(packet));
auto start = std::chrono::steady_clock::now();
ssize_t n = sendto(sock, packet, sizeof(packet), 0,
reinterpret_cast<sockaddr*>(&dest), sizeof(dest));
if (n < 0) {
perror("sendto");
} else {
++sent;
bool gotReply = false;
while (!gotReply) {
char buf[1024];
sockaddr_in from{};
socklen_t fromLen = sizeof(from);
ssize_t r = recvfrom(sock, buf, sizeof(buf), 0,
reinterpret_cast<sockaddr*>(&from), &fromLen);
if (r < 0) {
std::printf("Request timeout for icmp_seq %d\n", seq);
break;
}
auto end = std::chrono::steady_clock::now();
// macOS includes the IP header in received data; Linux does not.
size_t offset = 0;
if ((static_cast<uint8_t>(buf[0]) >> 4) == 4)
offset = (static_cast<uint8_t>(buf[0]) & 0x0F) * 4;
if (static_cast<size_t>(r) < offset + sizeof(IcmpHeader)) continue;
auto* reply = reinterpret_cast<IcmpHeader*>(buf + offset);
if (reply->type == ICMP_ECHO_REPLY && ntohs(reply->seq) == seq) {
double ms = std::chrono::duration<double, std::milli>(end - start).count();
char fromStr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &from.sin_addr, fromStr, sizeof(fromStr));
std::printf("%zd bytes from %s: icmp_seq=%d time=%.3f ms\n",
r - static_cast<ssize_t>(offset), fromStr, seq, ms);
++received;
totalRtt += ms;
if (ms < minRtt) minRtt = ms;
if (ms > maxRtt) maxRtt = ms;
gotReply = true;
}
}
}
if (seq < count - 1)
std::this_thread::sleep_for(std::chrono::seconds(1));
}
close(sock);
std::printf("\n--- %s ping statistics ---\n", host);
std::printf("%d packets transmitted, %d received, %.1f%% packet loss\n",
sent, received, sent ? 100.0 * (sent - received) / sent : 0.0);
if (received > 0)
std::printf("round-trip min/avg/max = %.3f/%.3f/%.3f ms\n",
minRtt, totalRtt / received, maxRtt);
return received > 0 ? 0 : 1;
}
No comments:
Post a Comment
no thanks
Note: Only a member of this blog may post a comment.