Bring aboard first version of ping check project
authorGuilherme Maciel Ferreira <guilherme.maciel.ferreira@intra2net.com>
Mon, 14 Feb 2011 08:09:40 +0000 (09:09 +0100)
committerGuilherme Maciel Ferreira <guilherme.maciel.ferreira@intra2net.com>
Mon, 21 Feb 2011 09:53:23 +0000 (10:53 +0100)
- cmake project files
- UML diagram for Visual Paradigm
- network management using boost::asio

.gitignore [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
src/CMakeLists.txt [new file with mode: 0644]
src/icmp_header.cpp [new file with mode: 0644]
src/icmp_header.hpp [new file with mode: 0644]
src/ipv4_header.cpp [new file with mode: 0644]
src/ipv4_header.hpp [new file with mode: 0644]
src/main.cpp [new file with mode: 0644]
src/pinger.cpp [new file with mode: 0644]
src/pinger.hpp [new file with mode: 0644]
uml/libpingcheck.vpp [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..1f2ed52
--- /dev/null
@@ -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 (file)
index 0000000..e08a828
--- /dev/null
@@ -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 (file)
index 0000000..9298f6b
--- /dev/null
@@ -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 (file)
index 0000000..4908ba7
--- /dev/null
@@ -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<char*> ( header.m_rep ), 8 );
+}
+
+std::ostream& operator<<( std::ostream& os, const IcmpHeader& header )
+{
+    return os.write( reinterpret_cast<const char*> ( 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<unsigned char> ( n >> 8 );
+    m_rep[ b ] = static_cast<unsigned char> ( n & 0xFF );
+}
diff --git a/src/icmp_header.hpp b/src/icmp_header.hpp
new file mode 100644 (file)
index 0000000..06adc4a
--- /dev/null
@@ -0,0 +1,91 @@
+#ifndef ICMP_HEADER_HPP
+#define ICMP_HEADER_HPP
+
+#include <istream>
+#include <ostream>
+#include <algorithm>
+
+// 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<typename Iterator>
+    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<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
diff --git a/src/ipv4_header.cpp b/src/ipv4_header.cpp
new file mode 100644 (file)
index 0000000..c426505
--- /dev/null
@@ -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<char*> ( 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<char*> ( 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 (file)
index 0000000..dd9fc6d
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef IPV4_HEADER_HPP
+#define IPV4_HEADER_HPP
+
+#include <algorithm>
+#include <boost/asio/ip/address_v4.hpp>
+
+// 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 (file)
index 0000000..a5bbcd0
--- /dev/null
@@ -0,0 +1,25 @@
+#include <iostream>
+
+#include "pinger.hpp"
+
+int main( int argc, char* argv[] )
+{
+    try
+    {
+        if (argc != 2)
+        {
+            std::cerr << "Usage: ping <host>" << 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 (file)
index 0000000..242cbd7
--- /dev/null
@@ -0,0 +1,133 @@
+#include <boost/bind.hpp>
+#include <istream>
+#include <iostream>
+#include <ostream>
+
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#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<uint16_t> ( ::getpid() );
+}
diff --git a/src/pinger.hpp b/src/pinger.hpp
new file mode 100644 (file)
index 0000000..bf1d8a4
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef PINGER_HPP
+#define PINGER_HPP
+
+#include <boost/asio.hpp>
+
+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 (file)
index 0000000..1cb081d
Binary files /dev/null and b/uml/libpingcheck.vpp differ