Skip to content

Instantly share code, notes, and snippets.

@marcomagdy
Last active October 18, 2023 22:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcomagdy/439725dd9fa404c1ca57a2688c6062af to your computer and use it in GitHub Desktop.
Save marcomagdy/439725dd9fa404c1ca57a2688c6062af to your computer and use it in GitHub Desktop.
Ping.cpp
// Build with > clang++ -std=c++17 pinger.cpp -o pinger -Wall -Werror -fsanitize=address,undefined
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/ip_icmp.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <string>
#include <thread>
#include <vector>
// Define the Ping Loop
int pingloop = 1;
// Interrupt handler
void intHandler(int dummy) { pingloop = 0; }
struct IcmpPacket {
struct icmp header;
static constexpr int ping_packet_size = 64;
uint8_t msg[ping_packet_size - sizeof(struct icmp)];
IcmpPacket()
: header()
, msg()
{
header.icmp_type = ICMP_ECHO;
header.icmp_hun.ih_idseq.icd_id = getpid();
header.icmp_hun.ih_idseq.icd_seq = 1;
}
void increment_seq() { header.icmp_hun.ih_idseq.icd_seq++; }
void sequence(uint16_t value) { header.icmp_hun.ih_idseq.icd_seq = value; }
};
class StopWatch {
std::chrono::time_point<std::chrono::high_resolution_clock> m_start;
public:
StopWatch()
: m_start(std::chrono::high_resolution_clock::now())
{
}
double elapsed_seconds() const
{
auto now = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::seconds>(now - m_start).count();
}
double elapsed_ms() const
{
auto now = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(now - m_start).count();
}
};
static uint16_t checksum(IcmpPacket const& packet)
{
auto len = sizeof(packet);
unsigned char buffer[sizeof(packet)];
memcpy(buffer, &packet, len);
auto* buf = (uint16_t*)buffer;
uint64_t sum = 0;
for (sum = 0; len > 1; len -= 2) {
sum += *buf++;
}
if (len == 1) {
sum += *buf;
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
uint16_t result = ~sum; // one's complement and truncate to 16 bits.
return result;
}
struct PingPacketResult {
double rtt_ms;
bool is_error;
};
struct PingOverallResult {
size_t packet_size = IcmpPacket::ping_packet_size;
size_t packets_sent;
size_t packets_received;
double packet_loss_percent;
double total_time_ms;
std::string ip;
std::vector<PingPacketResult> packet_results;
};
struct SocketHandle {
int m_sockfd;
SocketHandle(int sockfd)
: m_sockfd(sockfd)
{
}
SocketHandle(SocketHandle&& other)
: m_sockfd(other.m_sockfd)
{
other.m_sockfd = -1;
}
~SocketHandle()
{
if (m_sockfd >= 0) {
close(m_sockfd);
}
}
int handle() const { return m_sockfd; }
};
class SocketAddress {
struct sockaddr_in m_addr_con;
std::string m_ip;
public:
SocketAddress(struct sockaddr_in addr_con, std::string ip)
: m_addr_con(addr_con)
, m_ip(ip)
{
}
struct sockaddr* address() const { return (sockaddr*)&m_addr_con; }
std::string const& ip() const { return m_ip; }
};
class IcmpSocket {
SocketHandle m_socket;
IcmpSocket(SocketHandle socket)
: m_socket(std::move(socket))
{
}
public:
int fd() const { return m_socket.handle(); }
static std::optional<IcmpSocket> create(std::string& error)
{
SocketHandle sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (sockfd.handle() < 0) {
error = "Failed to create a socket";
return {};
}
// set the TTL on the socket
const int ttl = 64;
if (setsockopt(sockfd.handle(), IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) != 0) {
error = "Failed to set TTL option on socket";
return {};
}
// set the receive timeout to 1 second
constexpr int receive_timeout = 1;
struct timeval tv_out;
tv_out.tv_sec = receive_timeout;
tv_out.tv_usec = 0;
if (setsockopt(sockfd.handle(), SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof tv_out) != 0) {
error = "Failed to set receive timeout on socket";
return {};
}
return IcmpSocket(std::move(sockfd));
}
std::optional<SocketAddress> dns_resolve(std::string domain)
{
struct addrinfo hints, *res;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
if (getaddrinfo(domain.c_str(), nullptr, &hints, &res) != 0) {
return std::nullopt;
}
sockaddr_in const* ipv4 = (sockaddr_in*)res->ai_addr;
sockaddr_in addr_con;
addr_con.sin_family = res->ai_family;
constexpr int port_number = 0;
addr_con.sin_port = htons(port_number);
addr_con.sin_addr = ipv4->sin_addr;
std::string ping_ip = inet_ntoa(ipv4->sin_addr);
return SocketAddress(addr_con, ping_ip);
}
// make a ping request
PingOverallResult send_ping(std::string const& domain, size_t iterations)
{
std::optional<SocketAddress> address_opt = dns_resolve(domain);
if (!address_opt.has_value()) {
printf("Failed to resolve domain name %s\n", domain.c_str());
return {};
}
int ttl_val = 64, msg_sent_count = 0, flag = 1, msg_received_count = 0;
struct sockaddr_in r_addr;
struct timeval tv_out;
constexpr int receive_timeout_seconds = 1;
tv_out.tv_sec = receive_timeout_seconds;
tv_out.tv_usec = 0;
PingOverallResult ping_result;
StopWatch total_time;
// send icmp packet in an infinite loop
while (pingloop && iterations--) {
// flag is whether packet was sent or not
flag = 1;
IcmpPacket pckt;
pckt.sequence(msg_sent_count++);
pckt.header.icmp_cksum = checksum(pckt);
std::this_thread::sleep_for(std::chrono::seconds(1));
// send packet
StopWatch ping_time;
if (sendto(fd(), &pckt, sizeof(pckt), 0, address_opt->address(), sizeof(*address_opt->address())) <= 0) {
printf("\nPacket Sending Failed!\n");
flag = 0;
}
// receive packet
uint32_t addr_len = sizeof(r_addr);
if (recvfrom(fd(), &pckt, sizeof(pckt), 0, (struct sockaddr*)&r_addr, &addr_len) <= 0
&& msg_sent_count > 1) {
printf("\nPacket receive failed!\n");
}
else {
const double rtt_ms = ping_time.elapsed_ms();
if (flag) {
if (pckt.header.icmp_type != 69 || pckt.header.icmp_code != 0) {
printf("Error..Packet received with ICMP type %d code %d\n", pckt.header.icmp_type,
pckt.header.icmp_code);
ping_result.packet_results.push_back({ rtt_ms, true });
} else {
printf("%d bytes from %s (%s) msg_seq=%d ttl=%d rtt = %f ms.\n", IcmpPacket::ping_packet_size,
domain.c_str(), address_opt->ip().c_str(), msg_sent_count, ttl_val, rtt_ms);
msg_received_count++;
ping_result.packet_results.push_back({ rtt_ms, false });
}
}
}
}
ping_result.packets_sent = msg_sent_count;
ping_result.packets_received = msg_received_count;
ping_result.packet_loss_percent = ((msg_sent_count - msg_received_count) / 1.0 * msg_sent_count) * 100.0;
ping_result.total_time_ms = total_time.elapsed_ms();
ping_result.ip = address_opt->ip();
return ping_result;
}
};
// Driver Code
int main(int argc, char* argv[])
{
if (argc != 2) {
printf("\nFormat %s <address>\n", argv[0]);
return 0;
}
std::string error_msg;
const auto* domain = argv[1];
std::optional<IcmpSocket> socket_opt = IcmpSocket::create(error_msg);
if (!socket_opt.has_value()) {
printf("Failed: %s\n", error_msg.c_str());
return 1;
}
signal(SIGINT, intHandler); // catching interrupt
// send pings continuously
auto result = socket_opt->send_ping(domain, 5);
printf("\n===%s ping statistics===\n", result.ip.c_str());
printf("\n%lu packets sent, %ld packets received, %f percent packet loss. Total time: %f ms.\n\n",
result.packets_sent, result.packets_received, result.packet_loss_percent, result.total_time_ms);
return 0;
}
@richcole
Copy link

line: 22 : without being packed I'm worried about alignment here. Perhaps a static assert that the struct is of the expected length would help.

line 61: It isn't clear to me why you're copying the packet to compute the checksum. I didn't you this function modifying the packet. I wonder if it should be a const method on the packet struct.

line 115: You change here to the m_ for member variables. Seems like you change style within the same file on whether or not to prefix fields.

167: no const& on the string being passed in?

187: this function feels like a bit of a monster. It must be decomposable into smaller functions.

I don't see you use the timeout.

Seems like you should check that the ping response you get has an id that matches the one you sent out.

flag doesn't seem like a good name for what the flag is. was_sent maybe?

You can reduce the indent of the else case by putting a continue in the if statement preceeding it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment