ping.cpp 4.6 KB

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