#include <asio.hpp>
#include <istream>
#include <iostream>
#include <ostream>
#include "icmp_header.hpp"
#include "ipv4_header.hpp"
using asio::ip::icmp;
using asio::steady_timer;
namespace chrono = asio::chrono;
class pinger
{
public:
pinger(asio::io_context& io_context, const char* destination)
: resolver_(io_context), socket_(io_context, icmp::v4()),
timer_(io_context), sequence_number_(0), num_replies_(0)
{
destination_ = *resolver_.resolve(icmp::v4(), destination, "").begin();
start_send();
start_receive();
}
private:
void start_send()
{
std::string body("\"Hello!\" from Asio ping.");
icmp_header echo_request;
echo_request.type(icmp_header::echo_request);
echo_request.code(0);
echo_request.identifier(get_identifier());
echo_request.sequence_number(++sequence_number_);
compute_checksum(echo_request, body.begin(), body.end());
asio::streambuf request_buffer;
std::ostream os(&request_buffer);
os << echo_request << body;
time_sent_ = steady_timer::clock_type::now();
socket_.send_to(request_buffer.data(), destination_);
num_replies_ = 0;
timer_.expires_at(time_sent_ + chrono::seconds(5));
timer_.async_wait(std::bind(&pinger::handle_timeout, this));
}
void handle_timeout()
{
if (num_replies_ == 0)
std::cout << "Request timed out" << std::endl;
timer_.expires_at(time_sent_ + chrono::seconds(1));
timer_.async_wait(std::bind(&pinger::start_send, this));
}
void start_receive()
{
reply_buffer_.consume(reply_buffer_.size());
socket_.async_receive(reply_buffer_.prepare(65536),
std::bind(&pinger::handle_receive, this, std::placeholders::_2));
}
void handle_receive(std::size_t length)
{
reply_buffer_.commit(length);
std::istream is(&reply_buffer_);
ipv4_header ipv4_hdr;
icmp_header icmp_hdr;
is >> ipv4_hdr >> icmp_hdr;
if (is && icmp_hdr.type() == icmp_header::echo_reply
&& icmp_hdr.identifier() == get_identifier()
&& icmp_hdr.sequence_number() == sequence_number_)
{
if (num_replies_++ == 0)
timer_.cancel();
chrono::steady_clock::time_point now = chrono::steady_clock::now();
chrono::steady_clock::duration elapsed = now - time_sent_;
std::cout << length - ipv4_hdr.header_length()
<< " bytes from " << ipv4_hdr.source_address()
<< ": icmp_seq=" << icmp_hdr.sequence_number()
<< ", ttl=" << ipv4_hdr.time_to_live()
<< ", time="
<< chrono::duration_cast<chrono::milliseconds>(elapsed).count()
<< std::endl;
}
start_receive();
}
static unsigned short get_identifier()
{
#if defined(ASIO_WINDOWS)
return static_cast<unsigned short>(::GetCurrentProcessId());
#else
return static_cast<unsigned short>(::getpid());
#endif
}
icmp::resolver resolver_;
icmp::endpoint destination_;
icmp::socket socket_;
steady_timer timer_;
unsigned short sequence_number_;
chrono::steady_clock::time_point time_sent_;
asio::streambuf reply_buffer_;
std::size_t num_replies_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: ping <host>" << std::endl;
#if !defined(ASIO_WINDOWS)
std::cerr << "(You may need to run this program as root.)" << std::endl;
#endif
return 1;
}
asio::io_context io_context;
pinger p(io_context, argv[1]);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
}
}