]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // ping.cpp | |
3 | // ~~~~~~~~ | |
4 | // | |
1e59de90 | 5 | // Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
7c673cae FG |
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 <boost/asio.hpp> | |
f67539c2 | 12 | #include <boost/bind/bind.hpp> |
7c673cae FG |
13 | #include <istream> |
14 | #include <iostream> | |
15 | #include <ostream> | |
16 | ||
17 | #include "icmp_header.hpp" | |
18 | #include "ipv4_header.hpp" | |
19 | ||
20 | using boost::asio::ip::icmp; | |
11fdf7f2 TL |
21 | using boost::asio::steady_timer; |
22 | namespace chrono = boost::asio::chrono; | |
7c673cae FG |
23 | |
24 | class pinger | |
25 | { | |
26 | public: | |
b32b8144 FG |
27 | pinger(boost::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) | |
7c673cae | 30 | { |
b32b8144 | 31 | destination_ = *resolver_.resolve(icmp::v4(), destination, "").begin(); |
7c673cae FG |
32 | |
33 | start_send(); | |
34 | start_receive(); | |
35 | } | |
36 | ||
37 | private: | |
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 | boost::asio::streambuf request_buffer; | |
52 | std::ostream os(&request_buffer); | |
53 | os << echo_request << body; | |
54 | ||
55 | // Send the request. | |
11fdf7f2 | 56 | time_sent_ = steady_timer::clock_type::now(); |
7c673cae FG |
57 | socket_.send_to(request_buffer.data(), destination_); |
58 | ||
59 | // Wait up to five seconds for a reply. | |
60 | num_replies_ = 0; | |
11fdf7f2 | 61 | timer_.expires_at(time_sent_ + chrono::seconds(5)); |
7c673cae FG |
62 | timer_.async_wait(boost::bind(&pinger::handle_timeout, this)); |
63 | } | |
64 | ||
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. | |
11fdf7f2 | 71 | timer_.expires_at(time_sent_ + chrono::seconds(1)); |
7c673cae FG |
72 | timer_.async_wait(boost::bind(&pinger::start_send, this)); |
73 | } | |
74 | ||
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), | |
f67539c2 | 82 | boost::bind(&pinger::handle_receive, this, boost::placeholders::_2)); |
7c673cae FG |
83 | } |
84 | ||
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. | |
11fdf7f2 TL |
109 | chrono::steady_clock::time_point now = chrono::steady_clock::now(); |
110 | chrono::steady_clock::duration elapsed = now - time_sent_; | |
7c673cae FG |
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() | |
11fdf7f2 TL |
115 | << ", time=" |
116 | << chrono::duration_cast<chrono::milliseconds>(elapsed).count() | |
7c673cae FG |
117 | << std::endl; |
118 | } | |
119 | ||
120 | start_receive(); | |
121 | } | |
122 | ||
123 | static unsigned short get_identifier() | |
124 | { | |
125 | #if defined(BOOST_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_; | |
11fdf7f2 | 135 | steady_timer timer_; |
7c673cae | 136 | unsigned short sequence_number_; |
11fdf7f2 | 137 | chrono::steady_clock::time_point time_sent_; |
7c673cae FG |
138 | boost::asio::streambuf reply_buffer_; |
139 | std::size_t num_replies_; | |
140 | }; | |
141 | ||
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(BOOST_ASIO_WINDOWS) | |
150 | std::cerr << "(You may need to run this program as root.)" << std::endl; | |
151 | #endif | |
152 | return 1; | |
153 | } | |
154 | ||
b32b8144 FG |
155 | boost::asio::io_context io_context; |
156 | pinger p(io_context, argv[1]); | |
157 | io_context.run(); | |
7c673cae FG |
158 | } |
159 | catch (std::exception& e) | |
160 | { | |
161 | std::cerr << "Exception: " << e.what() << std::endl; | |
162 | } | |
163 | } |