1 //
2 // ping.cpp
3 // ~~~~~~~~
4 //
5 // Copyright (c) 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
6 //
7 // Distributed under the Boost Software License, Version 1.0. (See accompanying
8 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
9 //
10 
11 #include <asio.hpp>
12 #include <boost/bind.hpp>
13 #include <istream>
14 #include <iostream>
15 #include <ostream>
16 
17 #include "icmp_header.hpp"
18 #include "ipv4_header.hpp"
19 
20 using asio::ip::icmp;
21 using asio::steady_timer;
22 namespace chrono = asio::chrono;
23 
24 class pinger
25 {
26 public:
pinger(asio::io_context & io_context,const char * destination)27   pinger(asio::io_context& io_context, const char* destination)
28     : resolver_(io_context), socket_(io_context, icmp::v4()),
29       timer_(io_context), sequence_number_(0), num_replies_(0)
30   {
31     destination_ = *resolver_.resolve(icmp::v4(), destination, "").begin();
32 
33     start_send();
34     start_receive();
35   }
36 
37 private:
start_send()38   void start_send()
39   {
40     std::string body("\"Hello!\" from Asio ping.");
41 
42     // Create an ICMP header for an echo request.
43     icmp_header echo_request;
44     echo_request.type(icmp_header::echo_request);
45     echo_request.code(0);
46     echo_request.identifier(get_identifier());
47     echo_request.sequence_number(++sequence_number_);
48     compute_checksum(echo_request, body.begin(), body.end());
49 
50     // Encode the request packet.
51     asio::streambuf request_buffer;
52     std::ostream os(&request_buffer);
53     os << echo_request << body;
54 
55     // Send the request.
56     time_sent_ = steady_timer::clock_type::now();
57     socket_.send_to(request_buffer.data(), destination_);
58 
59     // Wait up to five seconds for a reply.
60     num_replies_ = 0;
61     timer_.expires_at(time_sent_ + chrono::seconds(5));
62     timer_.async_wait(boost::bind(&pinger::handle_timeout, this));
63   }
64 
handle_timeout()65   void handle_timeout()
66   {
67     if (num_replies_ == 0)
68       std::cout << "Request timed out" << std::endl;
69 
70     // Requests must be sent no less than one second apart.
71     timer_.expires_at(time_sent_ + chrono::seconds(1));
72     timer_.async_wait(boost::bind(&pinger::start_send, this));
73   }
74 
start_receive()75   void start_receive()
76   {
77     // Discard any data already in the buffer.
78     reply_buffer_.consume(reply_buffer_.size());
79 
80     // Wait for a reply. We prepare the buffer to receive up to 64KB.
81     socket_.async_receive(reply_buffer_.prepare(65536),
82         boost::bind(&pinger::handle_receive, this, _2));
83   }
84 
handle_receive(std::size_t length)85   void handle_receive(std::size_t length)
86   {
87     // The actual number of bytes received is committed to the buffer so that we
88     // can extract it using a std::istream object.
89     reply_buffer_.commit(length);
90 
91     // Decode the reply packet.
92     std::istream is(&reply_buffer_);
93     ipv4_header ipv4_hdr;
94     icmp_header icmp_hdr;
95     is >> ipv4_hdr >> icmp_hdr;
96 
97     // We can receive all ICMP packets received by the host, so we need to
98     // filter out only the echo replies that match the our identifier and
99     // expected sequence number.
100     if (is && icmp_hdr.type() == icmp_header::echo_reply
101           && icmp_hdr.identifier() == get_identifier()
102           && icmp_hdr.sequence_number() == sequence_number_)
103     {
104       // If this is the first reply, interrupt the five second timeout.
105       if (num_replies_++ == 0)
106         timer_.cancel();
107 
108       // Print out some information about the reply packet.
109       chrono::steady_clock::time_point now = chrono::steady_clock::now();
110       chrono::steady_clock::duration elapsed = now - time_sent_;
111       std::cout << length - ipv4_hdr.header_length()
112         << " bytes from " << ipv4_hdr.source_address()
113         << ": icmp_seq=" << icmp_hdr.sequence_number()
114         << ", ttl=" << ipv4_hdr.time_to_live()
115         << ", time="
116         << chrono::duration_cast<chrono::milliseconds>(elapsed).count()
117         << std::endl;
118     }
119 
120     start_receive();
121   }
122 
get_identifier()123   static unsigned short get_identifier()
124   {
125 #if defined(ASIO_WINDOWS)
126     return static_cast<unsigned short>(::GetCurrentProcessId());
127 #else
128     return static_cast<unsigned short>(::getpid());
129 #endif
130   }
131 
132   icmp::resolver resolver_;
133   icmp::endpoint destination_;
134   icmp::socket socket_;
135   steady_timer timer_;
136   unsigned short sequence_number_;
137   chrono::steady_clock::time_point time_sent_;
138   asio::streambuf reply_buffer_;
139   std::size_t num_replies_;
140 };
141 
main(int argc,char * argv[])142 int main(int argc, char* argv[])
143 {
144   try
145   {
146     if (argc != 2)
147     {
148       std::cerr << "Usage: ping <host>" << std::endl;
149 #if !defined(ASIO_WINDOWS)
150       std::cerr << "(You may need to run this program as root.)" << std::endl;
151 #endif
152       return 1;
153     }
154 
155     asio::io_context io_context;
156     pinger p(io_context, argv[1]);
157     io_context.run();
158   }
159   catch (std::exception& e)
160   {
161     std::cerr << "Exception: " << e.what() << std::endl;
162   }
163 }
164