参考了网上几个 traceroute的实现版本,存在一些缺陷,比如没有做超时处理,或者只能在window下使用。自己用boost实现了一个traceroute小工具,在window下正常运行。
先来看下面实现的原理。这些说明来自维基百科。traceroute,现代Linux系统称为tracepath,Windows系统称为tracert,是一种电脑网络工具。它可显示数据包在IP网络经过的路由器的IP地址。 程序利用增加存活时间(TTL)值来实现其功能的。每当数据包经过一个路由器,其存活时间就会减1。当其存活时间是0时,主机便取消数据包,并传送一个ICMP TTL数据包给原数据包的发出者。
下面是具体实现的源代码:
1 // 2 // traceroute.cpp 3 // ~~~~~~~~ 4 // 5 // BoSheng Lu 2013-04-25 6 // 7 // Version 1.0. 8 // 9 10 #include <boost/asio.hpp> 11 #include <boost/bind.hpp> 12 #include<boost/thread.hpp> 13 #include <istream> 14 #include <iostream> 15 #include<iomanip> 16 #include<string> 17 #include <ostream> 18 19 #include "icmp_header.hpp" 20 #include "ipv4_header.hpp" 21 22 using boost::asio::ip::icmp; 23 using boost::asio::deadline_timer; 24 namespace posix_time = boost::posix_time; 25 26 class traceroute 27 { 28 public: 29 traceroute(boost::asio::io_service& io_service, const char* destination) 30 : resolver_(io_service), socket_(io_service, icmp::v4()), 31 sequence_number_(0), max_hop_(30) 32 { 33 icmp::resolver::query query(icmp::v4(), destination, ""); 34 destination_ = *resolver_.resolve(query); 35 36 std::cout << "\nTracing route to " << destination 37 << " [" << destination_.address().to_string() << "]" 38 << " with a maximum of " << max_hop_ << " hops.\n" << std::endl; 39 40 #if defined(BOOST_WINDOWS) 41 int timeout = 5000; 42 if( setsockopt(socket_.native(), SOL_SOCKET, SO_RCVTIMEO, 43 (const char*)&timeout, sizeof(timeout))) 44 { 45 std::cout << "RCVTIMEO not set properly." << std::endl; 46 throw std::runtime_error("RCVTIMEO not set properly"); 47 } 48 #else 49 struct timeval tv; 50 tv.tv_sec = 5; 51 tv.tv_usec = 0; 52 if( setsockopt(socket_.native(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) 53 { 54 std::cout << "RCVTIMEO not set properly." << std::endl; 55 throw std::runtime_error("RCVTIMEO not set properly"); 56 } 57 #endif 58 59 reach_dest_host_ = false; 60 int ttl = 1; 61 while (!reach_dest_host_ && max_hop_--) 62 { 63 const boost::asio::ip::unicast::hops option(ttl); 64 socket_.set_option(option); 65 66 boost::asio::ip::unicast::hops op; 67 socket_.get_option(op); 68 if( ttl != op.value() ) 69 { 70 std::cout << "TTL not set properly. Should be " 71 << ttl << " but was set to " 72 << op.value() << '.' << std::endl; 73 throw std::runtime_error("TTL not set properly"); 74 } 75 76 std::cout << std::setw(3) << ttl << std::flush; 77 start_send(); 78 start_receive(); 79 80 ttl++; 81 }//while 82 } 83 84 private: 85 void start_send() 86 { 87 std::string body("\"Hello!\" from Asio ping."); 88 89 // Create an ICMP header for an echo request. 90 icmp_header echo_request; 91 echo_request.type(icmp_header::echo_request); 92 echo_request.code(0); 93 echo_request.identifier(get_identifier()); 94 echo_request.sequence_number(++sequence_number_); 95 compute_checksum(echo_request, body.begin(), body.end()); 96 97 // Encode the request packet. 98 boost::asio::streambuf request_buffer; 99 std::ostream os(&request_buffer); 100 os << echo_request << body; 101 102 // Send the request. 103 time_sent_ = posix_time::microsec_clock::universal_time(); 104 socket_.send_to(request_buffer.data(), destination_); 105 } 106 107 108 void start_receive() 109 { 110 // Discard any data already in the buffer. 111 reply_buffer_.consume(reply_buffer_.size()); 112 113 // Wait for a reply. We prepare the buffer to receive up to 64KB. 114 boost::system::error_code ec; 115 boost::asio::socket_base::message_flags flags = 0; 116 size_t len = socket_.receive(reply_buffer_.prepare(65536), flags, ec); 117 118 handle_receive(len, ec); 119 } 120 121 void handle_receive(std::size_t length, boost::system::error_code& ec) 122 { 123 // The actual number of bytes received is committed to the buffer so that we 124 // can extract it using a std::istream object. 125 reply_buffer_.commit(length); 126 127 // Decode the reply packet. 128 std::istream is(&reply_buffer_); 129 ipv4_header ipv4_hdr; 130 is >> ipv4_hdr; 131 132 if(ec) 133 { 134 std::cout << std::setw(9) << '*' << '\t' << "Request timed out." << std::endl; 135 return; 136 } 137 138 posix_time::ptime now = posix_time::microsec_clock::universal_time(); 139 long long dwRoundTripTime = (now - time_sent_).total_milliseconds(); 140 141 if(dwRoundTripTime) 142 { 143 std::cout << std::setw(6) << dwRoundTripTime << " ms" << std::flush; 144 } 145 else 146 { 147 std::cout << std::setw(6) << "<1" << " ms" << std::flush; 148 } 149 150 boost::this_thread::sleep(boost::posix_time::seconds(2)); 151 152 std::cout << '\t' << ipv4_hdr.source_address() << std::endl; 153 154 // Print out some information about the reply packet. 155 if( ipv4_hdr.source_address().to_string() == destination_.address().to_string() ) 156 { 157 reach_dest_host_ = true; 158 std::cout << std::endl; 159 std::cout << std::endl; 160 std::cout << "traceroute sucess!" << std::endl; 161 162 icmp_header icmp_hdr; 163 is >> icmp_hdr; 164 165 if (is && icmp_hdr.type() == icmp_header::echo_reply 166 && icmp_hdr.identifier() == get_identifier() 167 && icmp_hdr.sequence_number() == sequence_number_) 168 { 169 // Print out some information about the reply packet. 170 std::cout << length - ipv4_hdr.header_length() 171 << " bytes from " << ipv4_hdr.source_address() 172 << ": icmp_seq=" << icmp_hdr.sequence_number() 173 << ", ttl=" << ipv4_hdr.time_to_live() 174 << ", time=" << dwRoundTripTime << " ms" 175 << std::endl; 176 } 177 } 178 } 179 180 static unsigned short get_identifier() 181 { 182 #if defined(BOOST_WINDOWS) 183 return static_cast<unsigned short>(::GetCurrentProcessId()); 184 #else 185 return static_cast<unsigned short>(::getpid()); 186 #endif 187 } 188 189 icmp::resolver resolver_; 190 icmp::endpoint destination_; 191 icmp::socket socket_; 192 unsigned short sequence_number_; 193 posix_time::ptime time_sent_; 194 boost::asio::streambuf reply_buffer_; 195 bool reach_dest_host_; 196 std::size_t max_hop_; 197 }; 198 199 int main(int argc, char* argv[]) 200 { 201 try 202 { 203 if(argc != 2) 204 { 205 std::cerr << "Usage: ping <host>" << std::endl; 206 #if !defined(BOOST_WINDOWS) 207 std::cerr << "(You may need to run this program as root.)" << std::endl; 208 #endif 209 return 1; 210 } 211 212 boost::asio::io_service io_service; 213 traceroute p(io_service, argv[1]); 214 io_service.run(); 215 } 216 catch (std::exception& e) 217 { 218 std::cerr << "Exception: " << e.what() << std::endl; 219 } 220 }
这个源码实现使用了boost库附带的boost asio使用示例中的代码,下面也将这个例子中使用到的代码附上,方便使用。至于boost库的编译使用另请参考其他的文章。
下面是ipv4_header.hpp:
// // ipv4_header.hpp // ~~~~~~~~~~~~~~~ // // Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef IPV4_HEADER_HPP #define IPV4_HEADER_HPP #include <algorithm> #include <boost/asio/ip/address_v4.hpp> // Packet header for IPv4. // // The wire format of an IPv4 header is: // // 0 8 16 31 // +-------+-------+---------------+------------------------------+ --- // | | | | | ^ // |version|header | type of | total length in bytes | | // | (4) | length| service | | | // +-------+-------+---------------+-+-+-+------------------------+ | // | | | | | | | // | identification |0|D|M| fragment offset | | // | | |F|F| | | // +---------------+---------------+-+-+-+------------------------+ | // | | | | | // | time to live | protocol | header checksum | 20 bytes // | | | | | // +---------------+---------------+------------------------------+ | // | | | // | source IPv4 address | | // | | | // +--------------------------------------------------------------+ | // | | | // | destination IPv4 address | | // | | v // +--------------------------------------------------------------+ --- // | | ^ // | | | // / options (if any) / 0 - 40 // / / bytes // | | | // | | v // +--------------------------------------------------------------+ --- class ipv4_header { public: ipv4_header() { std::fill(rep_, rep_ + sizeof(rep_), 0); } unsigned char version() const { return (rep_[0] >> 4) & 0xF; } unsigned short header_length() const { return (rep_[0] & 0xF) * 4; } unsigned char type_of_service() const { return rep_[1]; } unsigned short total_length() const { return decode(2, 3); } unsigned short identification() const { return decode(4, 5); } bool dont_fragment() const { return (rep_[6] & 0x40) != 0; } bool more_fragments() const { return (rep_[6] & 0x20) != 0; } unsigned short fragment_offset() const { return decode(6, 7) & 0x1FFF; } unsigned int time_to_live() const { return rep_[8]; } unsigned char protocol() const { return rep_[9]; } unsigned short header_checksum() const { return decode(10, 11); } boost::asio::ip::address_v4 source_address() const { boost::asio::ip::address_v4::bytes_type bytes = { { rep_[12], rep_[13], rep_[14], rep_[15] } }; return boost::asio::ip::address_v4(bytes); } boost::asio::ip::address_v4 destination_address() const { boost::asio::ip::address_v4::bytes_type bytes = { { rep_[16], rep_[17], rep_[18], rep_[19] } }; return boost::asio::ip::address_v4(bytes); } friend std::istream& operator>>(std::istream& is, ipv4_header& header) { is.read(reinterpret_cast<char*>(header.rep_), 20); if (header.version() != 4) is.setstate(std::ios::failbit); std::streamsize options_length = header.header_length() - 20; if (options_length < 0 || options_length > 40) is.setstate(std::ios::failbit); else is.read(reinterpret_cast<char*>(header.rep_) + 20, options_length); return is; } private: unsigned short decode(int a, int b) const { return (rep_[a] << 8) + rep_[b]; } unsigned char rep_[60]; }; #endif // IPV4_HEADER_HPP
下面是icmp_header.hpp:
// // icmp_header.hpp // ~~~~~~~~~~~~~~~ // // Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef ICMP_HEADER_HPP #define ICMP_HEADER_HPP #include <istream> #include <ostream> #include <algorithm> // ICMP header for both IPv4 and IPv6. // // The wire format of an ICMP header is: // // 0 8 16 31 // +---------------+---------------+------------------------------+ --- // | | | | ^ // | type | code | checksum | | // | | | | | // +---------------+---------------+------------------------------+ 8 bytes // | | | | // | identifier | sequence number | | // | | | v // +-------------------------------+------------------------------+ --- class icmp_header { public: enum { echo_reply = 0, destination_unreachable = 3, source_quench = 4, redirect = 5, echo_request = 8, time_exceeded = 11, parameter_problem = 12, timestamp_request = 13, timestamp_reply = 14, info_request = 15, info_reply = 16, address_request = 17, address_reply = 18 }; icmp_header() { std::fill(rep_, rep_ + sizeof(rep_), 0); } unsigned char type() const { return rep_[0]; } unsigned char code() const { return rep_[1]; } unsigned short checksum() const { return decode(2, 3); } unsigned short identifier() const { return decode(4, 5); } unsigned short sequence_number() const { return decode(6, 7); } void type(unsigned char n) { rep_[0] = n; } void code(unsigned char n) { rep_[1] = n; } void checksum(unsigned short n) { encode(2, 3, n); } void identifier(unsigned short n) { encode(4, 5, n); } void sequence_number(unsigned short n) { encode(6, 7, n); } friend std::istream& operator>>(std::istream& is, icmp_header& header) { return is.read(reinterpret_cast<char*>(header.rep_), 8); } friend std::ostream& operator<<(std::ostream& os, const icmp_header& header) { return os.write(reinterpret_cast<const char*>(header.rep_), 8); } private: unsigned short decode(int a, int b) const { return (rep_[a] << 8) + rep_[b]; } void encode(int a, int b, unsigned short n) { rep_[a] = static_cast<unsigned char>(n >> 8); rep_[b] = static_cast<unsigned char>(n & 0xFF); } unsigned char rep_[8]; }; template <typename Iterator> void compute_checksum(icmp_header& header, Iterator body_begin, Iterator body_end) { unsigned int sum = (header.type() << 8) + header.code() + header.identifier() + header.sequence_number(); Iterator body_iter = body_begin; while (body_iter != body_end) { sum += (static_cast<unsigned char>(*body_iter++) << 8); if (body_iter != body_end) sum += static_cast<unsigned char>(*body_iter++); } sum = (sum >> 16) + (sum & 0xFFFF); sum += (sum >> 16); header.checksum(static_cast<unsigned short>(~sum)); } #endif // ICMP_HEADER_HPP