Last active
October 18, 2023 22:30
-
-
Save marcomagdy/439725dd9fa404c1ca57a2688c6062af to your computer and use it in GitHub Desktop.
Ping.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.