From: Guilherme Maciel Ferreira Date: Mon, 14 Feb 2011 08:09:40 +0000 (+0100) Subject: Bring aboard first version of ping check project X-Git-Tag: v1.0~205 X-Git-Url: http://developer.intra2net.com/git/?a=commitdiff_plain;h=4ea9706c6f34bc29e527443a4de455ec2e8b83ed;p=pingcheck Bring aboard first version of ping check project - cmake project files - UML diagram for Visual Paradigm - network management using boost::asio --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f2ed52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# cmake out-of-source build directory +build + +# eclipse IDE project files and directories +Debug +.cproject +.project +.directory diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e08a828 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required( VERSION 2.6 ) + +project( pinger CXX ) + +# set the directory where the executable will be placed +set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) + +# set the directory where the make install places the executable +#set( CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR} ) + +add_subdirectory( src ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..9298f6b --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,22 @@ +set( SOURCES + icmp_header.cpp + ipv4_header.cpp + pinger.cpp + main.cpp +) + +set( TARGET pinger ) + +set( Boost_USE_STATIC_LIBS ON ) +set( Boost_USE_MULTITHREADED ON ) +set( Boost_USE_STATIC_RUNTIME OFF ) +find_package( Boost 1.36.0 REQUIRED COMPONENTS filesystem ) + +include_directories( ${Boost_INCLUDE_DIRS} ) +add_executable( ${TARGET} ${SOURCES} ) +target_link_libraries( ${TARGET} ${Boost_LIBRARIES} ) + +install( + TARGETS ${TARGET} + DESTINATION . +) \ No newline at end of file diff --git a/src/icmp_header.cpp b/src/icmp_header.cpp new file mode 100644 index 0000000..4908ba7 --- /dev/null +++ b/src/icmp_header.cpp @@ -0,0 +1,77 @@ +#include "icmp_header.hpp" + +IcmpHeader::IcmpHeader() +{ + std::fill( m_rep, m_rep + sizeof(m_rep), 0 ); +} + +unsigned char IcmpHeader::type() const +{ + return m_rep[ 0 ]; +} + +unsigned char IcmpHeader::code() const +{ + return m_rep[ 1 ]; +} + +unsigned short IcmpHeader::checksum() const +{ + return decode( 2, 3 ); +} + +unsigned short IcmpHeader::identifier() const +{ + return decode( 4, 5 ); +} + +unsigned short IcmpHeader::sequence_number() const +{ + return decode( 6, 7 ); +} + +void IcmpHeader::type( unsigned char n ) +{ + m_rep[ 0 ] = n; +} + +void IcmpHeader::code( unsigned char n ) +{ + m_rep[ 1 ] = n; +} + +void IcmpHeader::checksum( unsigned short n ) +{ + encode( 2, 3, n ); +} + +void IcmpHeader::identifier( unsigned short n ) +{ + encode( 4, 5, n ); +} + +void IcmpHeader::sequence_number( unsigned short n ) +{ + encode( 6, 7, n ); +} + +std::istream& operator>>( std::istream& is, IcmpHeader& header ) +{ + return is.read( reinterpret_cast ( header.m_rep ), 8 ); +} + +std::ostream& operator<<( std::ostream& os, const IcmpHeader& header ) +{ + return os.write( reinterpret_cast ( header.m_rep ), 8 ); +} + +unsigned short IcmpHeader::decode( int a, int b ) const +{ + return (m_rep[ a ] << 8) + m_rep[ b ]; +} + +void IcmpHeader::encode( int a, int b, unsigned short n ) +{ + m_rep[ a ] = static_cast ( n >> 8 ); + m_rep[ b ] = static_cast ( n & 0xFF ); +} diff --git a/src/icmp_header.hpp b/src/icmp_header.hpp new file mode 100644 index 0000000..06adc4a --- /dev/null +++ b/src/icmp_header.hpp @@ -0,0 +1,91 @@ +#ifndef ICMP_HEADER_HPP +#define ICMP_HEADER_HPP + +#include +#include +#include + +// ICMP header format: +// +// 0 8 16 31 +// +---------------+---------------+------------------------------+ --- +// | | | | ^ +// | type | code | checksum | | +// | | | | | +// +---------------+---------------+------------------------------+ 8 bytes +// | | | | +// | identifier | sequence number | | +// | | | v +// +-------------------------------+------------------------------+ --- + +class IcmpHeader +{ +public: + enum IcmpType + { + EchoReply = 0, + DestinationUnreachable = 3, + SourceQuench = 4, + Redirect = 5, + EchoRequest = 8, + TimeExceeded = 11, + ParameterProblem = 12, + TimestampRequest = 13, + TimestampReply = 14, + InfoRequest = 15, + InfoReply = 16, + AddressRequest = 17, + AddressReply = 18 + }; + + IcmpHeader(); + + unsigned char type() const; + unsigned char code() const; + unsigned short checksum() const; + unsigned short identifier() const; + unsigned short sequence_number() const; + + void type( const unsigned char n ); + void code( const unsigned char n ); + void checksum( const unsigned short n ); + void identifier( const unsigned short n ); + void sequence_number( const unsigned short n ); + + friend std::istream& operator>>( + std::istream& is, + IcmpHeader& header ); + friend std::ostream& operator<<( + std::ostream& os, + const IcmpHeader& header ); + +private: + unsigned short decode( int a, int b ) const; + void encode( int a, int b, unsigned short n ); + + unsigned char m_rep[ 8 ]; +}; + +template + void compute_checksum( + IcmpHeader& 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 ( *body_iter++ ) << 8); + if (body_iter != body_end) + sum += static_cast ( *body_iter++ ); + } + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + header.checksum( static_cast ( ~sum ) ); + } + +#endif // ICMP_HEADER_HPP diff --git a/src/ipv4_header.cpp b/src/ipv4_header.cpp new file mode 100644 index 0000000..c426505 --- /dev/null +++ b/src/ipv4_header.cpp @@ -0,0 +1,99 @@ +#include "ipv4_header.hpp" + +Ipv4Header::Ipv4Header() +{ + std::fill( m_rep, m_rep + sizeof(m_rep), 0 ); +} + +unsigned char Ipv4Header::version() const +{ + return (m_rep[ 0 ] >> 4) & 0xF; +} + +unsigned short Ipv4Header::header_length() const +{ + return (m_rep[ 0 ] & 0xF) * 4; +} + +unsigned char Ipv4Header::type_of_service() const +{ + return m_rep[ 1 ]; +} + +unsigned short Ipv4Header::total_length() const +{ + return decode( 2, 3 ); +} + +unsigned short Ipv4Header::identification() const +{ + return decode( 4, 5 ); +} + +bool Ipv4Header::dont_fragment() const +{ + return (m_rep[ 6 ] & 0x40) != 0; +} + +bool Ipv4Header::more_fragments() const +{ + return (m_rep[ 6 ] & 0x20) != 0; +} + +unsigned short Ipv4Header::fragment_offset() const +{ + return decode( 6, 7 ) & 0x1FFF; +} + +unsigned int Ipv4Header::time_to_live() const +{ + return m_rep[ 8 ]; +} + +unsigned char Ipv4Header::protocol() const +{ + return m_rep[ 9 ]; +} + +unsigned short Ipv4Header::header_checksum() const +{ + return decode( 10, 11 ); +} + +boost::asio::ip::address_v4 Ipv4Header::source_address() const +{ + boost::asio::ip::address_v4::bytes_type bytes = { { + m_rep[ 12 ], + m_rep[ 13 ], + m_rep[ 14 ], + m_rep[ 15 ] } }; + return boost::asio::ip::address_v4( bytes ); +} + +boost::asio::ip::address_v4 Ipv4Header::destination_address() const +{ + boost::asio::ip::address_v4::bytes_type bytes = { { + m_rep[ 16 ], + m_rep[ 17 ], + m_rep[ 18 ], + m_rep[ 19 ] } }; + return boost::asio::ip::address_v4( bytes ); +} + +std::istream& operator>>( std::istream& is, Ipv4Header& header ) +{ + is.read( reinterpret_cast ( header.m_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 ( header.m_rep ) + 20, options_length ); + return is; +} + +unsigned short Ipv4Header::decode( int a, int b ) const +{ + return (m_rep[ a ] << 8) + m_rep[ b ]; +} diff --git a/src/ipv4_header.hpp b/src/ipv4_header.hpp new file mode 100644 index 0000000..dd9fc6d --- /dev/null +++ b/src/ipv4_header.hpp @@ -0,0 +1,68 @@ +#ifndef IPV4_HEADER_HPP +#define IPV4_HEADER_HPP + +#include +#include + +// IPv4 header format: +// +// 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 Ipv4Header +{ +public: + Ipv4Header(); + + unsigned char version() const; + unsigned short header_length() const; + unsigned char type_of_service() const; + unsigned short total_length() const; + unsigned short identification() const; + + bool dont_fragment() const; + bool more_fragments() const; + unsigned short fragment_offset() const; + unsigned int time_to_live() const; + unsigned char protocol() const; + unsigned short header_checksum() const; + + boost::asio::ip::address_v4 source_address() const; + boost::asio::ip::address_v4 destination_address() const; + + friend std::istream& operator>>( std::istream& is, Ipv4Header& header ); + +private: + unsigned short decode( int a, int b ) const; + + unsigned char m_rep[ 60 ]; +}; + +#endif // IPV4_HEADER_HPP diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a5bbcd0 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,25 @@ +#include + +#include "pinger.hpp" + +int main( int argc, char* argv[] ) +{ + try + { + if (argc != 2) + { + std::cerr << "Usage: ping " << std::endl; + std::cerr << "(You may need to run this program as root.)" + << std::endl; + return 1; + } + + boost::asio::io_service io_service; + Pinger p( io_service, argv[ 1 ] ); + io_service.run(); + } + catch (std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + } +} diff --git a/src/pinger.cpp b/src/pinger.cpp new file mode 100644 index 0000000..242cbd7 --- /dev/null +++ b/src/pinger.cpp @@ -0,0 +1,133 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "icmp_header.hpp" +#include "ipv4_header.hpp" +#include "pinger.hpp" + +//----------------------------------------------------------------------------- +// Pinger +//----------------------------------------------------------------------------- + +Pinger::Pinger( boost::asio::io_service& io_service, const char* destination ) : + m_resolver( io_service ), m_timer( io_service ), m_sequence_number( 0 ), + m_num_replies( 0 ), m_socket( io_service, icmp::v4() ) +{ + try + { + std::string ip = "172.16.1.149"; + uint32_t port = 21; + icmp::endpoint ep( address::from_string( ip ), port ); + m_socket.bind( ep ); + } + catch (boost::system::system_error &e) + { + std::cerr << "exception" << std::endl; + } + + icmp::resolver::query query( icmp::v4(), destination, "" ); + m_destination = *m_resolver.resolve( query ); + + start_send(); + start_receive(); +} + +Pinger::~Pinger() +{ +} + +void Pinger::start_send() +{ + std::string body( "ping message" ); + + // Create an ICMP header for an echo request. + IcmpHeader echo_request; + echo_request.type( IcmpHeader::EchoRequest ); + echo_request.code( 0 ); + echo_request.identifier( get_identifier() ); + m_sequence_number++; + echo_request.sequence_number( m_sequence_number ); + compute_checksum( echo_request, body.begin(), body.end() ); + + // Encode the request packet. + boost::asio::streambuf request_buffer; + std::ostream os( &request_buffer ); + os << echo_request << body; + + // Send the request. + m_time_sent = posix_time::microsec_clock::universal_time(); + m_socket.send_to( request_buffer.data(), m_destination ); + + // Wait up to five seconds for a reply. + m_num_replies = 0; + m_timer.expires_at( m_time_sent + posix_time::seconds( 5 ) ); + m_timer.async_wait( boost::bind( &Pinger::handle_timeout, this ) ); +} + +void Pinger::handle_timeout() +{ + if (m_num_replies == 0) + std::cout << "Request timed out" << std::endl; + + // Requests must be sent no less than one second apart. + m_timer.expires_at( m_time_sent + posix_time::seconds( 1 ) ); + m_timer.async_wait( boost::bind( &Pinger::start_send, this ) ); +} + +void Pinger::start_receive() +{ + // Discard any data already in the buffer. + m_reply_buffer.consume( m_reply_buffer.size() ); + + // Wait for a reply. We prepare the buffer to receive up to 64KB. + m_socket.async_receive( + m_reply_buffer.prepare( 65536 ), + boost::bind( &Pinger::handle_receive, this, _2 ) ); +} + +void Pinger::handle_receive( std::size_t length ) +{ + // The actual number of bytes received is committed to the buffer so that we + // can extract it using a std::istream object. + m_reply_buffer.commit( length ); + + // Decode the reply packet. + std::istream is( &m_reply_buffer ); + Ipv4Header ipv4_hdr; + IcmpHeader icmp_hdr; + is >> ipv4_hdr >> icmp_hdr; + + // We can receive all ICMP packets received by the host, so we need to + // filter out only the echo replies that match the our identifier and + // expected sequence number. + if (is && icmp_hdr.type() == IcmpHeader::EchoReply + && icmp_hdr.identifier() == get_identifier() + && icmp_hdr.sequence_number() == m_sequence_number) + { + // If this is the first reply, interrupt the five second timeout. + if (m_num_replies++ == 0) + m_timer.cancel(); + + // Print out some information about the reply packet. + posix_time::ptime now = posix_time::microsec_clock::universal_time(); + 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=" + << (now - m_time_sent).total_milliseconds() << " ms" + << std::endl; + } + + start_receive(); +} + +uint16_t Pinger::get_identifier() +{ + return static_cast ( ::getpid() ); +} diff --git a/src/pinger.hpp b/src/pinger.hpp new file mode 100644 index 0000000..bf1d8a4 --- /dev/null +++ b/src/pinger.hpp @@ -0,0 +1,41 @@ +#ifndef PINGER_HPP +#define PINGER_HPP + +#include + +using boost::asio::ip::address; +using boost::asio::ip::icmp; +using boost::asio::deadline_timer; + +namespace posix_time = boost::posix_time; + +//----------------------------------------------------------------------------- +// Pinger +//----------------------------------------------------------------------------- + +class Pinger +{ +public: + Pinger( boost::asio::io_service& io_service, const char* destination ); + virtual ~Pinger(); + +private: + void start_send(); + + void handle_timeout(); + void start_receive(); + void handle_receive( std::size_t length ); + + static uint16_t get_identifier(); + + icmp::resolver m_resolver; + icmp::endpoint m_destination; + icmp::socket m_socket; + deadline_timer m_timer; + uint16_t m_sequence_number; + posix_time::ptime m_time_sent; + boost::asio::streambuf m_reply_buffer; + std::size_t m_num_replies; +}; + +#endif /* PINGER_HPP */ diff --git a/uml/libpingcheck.vpp b/uml/libpingcheck.vpp new file mode 100644 index 0000000..1cb081d Binary files /dev/null and b/uml/libpingcheck.vpp differ