User Tools

Site Tools


Ping & traceroute in one tube

My raw socket usage example with Boost::Asio.

ping.cpp
/*
 * ping.cpp
 *
 * Copyright 2019 Oleg Borodin <borodin@unix7.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 *
 */
 
 
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/array.hpp>
 
 
#include <istream>
#include <iostream>
#include <ostream>
 
namespace ping {
    namespace ip {
        const std::size_t min_header_size = 20;
        const std::size_t option_max_size = 40;
        const std::size_t reply_buffer_size = 65536;
    }
 
    namespace icmp {
        uint16_t id = 1234;
        uint16_t seq = 4567;
        namespace type {
            const uint16_t echo_reply = 0;
            //const uint16_t destination_unreachable = 3;
            //const uint16_t source_quench = 4;
            //const uint16_t redirect = 5;
            const uint16_t echo_request = 8;
            const uint16_t time_exceeded = 11;
            //const uint16_t parameter_problem = 12;
            //const uint16_t timestamp_request = 13;
            //const uint16_t timestamp_reply = 14;
            //const uint16_t info_request = 15;
            //const uint16_t info_reply = 16;
            //const uint16_t address_request = 17;
            //const uint16_t address_reply = 18;
        }
    }
}
 
using namespace boost::asio;
 
class ip_packet {
  public:
 
    struct {
        std::uint8_t  ip_hl:4;               /* header length */
        std::uint8_t  ip_v:4;                /* version */
        std::uint8_t  ip_tos;                /* type of service */
        std::uint16_t ip_len;                /* total length */
        std::uint16_t ip_id;                 /* identification */
        std::uint16_t ip_off;                /* fragment offset field */
        std::uint8_t  ip_ttl;                /* time to live */
        std::uint8_t  ip_p;                  /* protocol */
        std::uint16_t ip_sum;                /* checksum */
 
        ip::address_v4::bytes_type ip_src; /* source address */
        ip::address_v4::bytes_type ip_dst; /* dest address */
 
        std::uint8_t options[ping::ip::option_max_size];
                                        /* options */
 
    } __packed __aligned(2) header;
 
    ip_packet() {
        std::memset(&header, 0, sizeof(header));
    }
 
    uint16_t ip_ttl() {
        return header.ip_ttl;
    }
    uint16_t ip_v() {
        return header.ip_v;
    }
    uint16_t ip_hl() {
        return (header.ip_hl * sizeof(uint32_t));
    }
    friend std::istream& operator >> (std::istream& istream, ip_packet& packet) {
        istream.read((char *)(&packet.header), ping::ip::min_header_size);
        std::size_t packet_options_size = packet.ip_hl() - ping::ip::min_header_size;
        istream.read((char *)(&packet.header.options),  packet_options_size);
        return istream;
    }
};
 
 
class icmp_packet {
  public:
    struct {
        uint8_t icmp_type;
        uint8_t icmp_code;
        uint16_t icmp_cksum;
 
        uint16_t icmp_id;
        uint16_t icmp_seq;
    } header;
 
    icmp_packet() {
        std::memset(&header, 0, sizeof(header));
    }
 
    friend std::ostream& operator << (std::ostream& ostream, icmp_packet& packet) {
        return ostream.write((char *)(&packet.header), sizeof(packet.header));
    }
 
    friend std::istream& operator >> (std::istream& istream, icmp_packet& packet) {
        return istream.read((char *)(&packet.header), sizeof(packet.header));
    }
 
    void icmp_type(const uint16_t type) {
        header.icmp_type = type;
    }
    uint16_t icmp_type() {
        return header.icmp_type;
    }
 
    uint16_t icmp_id() {
        return ntohs(header.icmp_id);
    }
 
    void icmp_id(uint16_t id) {
        header.icmp_id = htons(id);
    }
 
    uint16_t icmp_seq() {
        return ntohs(header.icmp_seq);
    }
 
    void icmp_seq(uint16_t seq) {
        header.icmp_seq = htons(seq);
    }
 
    void calc_checksum() {
        header.icmp_cksum = 0;
        const uint16_t * const data = (uint16_t *)&header;
        std::size_t header_size = sizeof(header);
 
        uint32_t accu = 0;
        for (std::size_t i = 0; i < (header_size >> 1); ++i) {
            accu = accu + data[i];
        }
        while (accu >> 16) {
            accu = (accu & 0xffff) + (accu >> 16);
        }
        header.icmp_cksum = ~accu;
    }
};
 
class pinger {
  public:
 
    std::string hostname = "localhost";
 
    boost::asio::streambuf request_buffer;
    boost::asio::streambuf reply_buffer;
 
    ip::icmp::endpoint destination;
    ip::icmp::endpoint source;
 
    pinger(std::string host) : hostname(host) {
    }
 
    void start() {
        std::uint8_t max_ttl = 12;
        for (std::uint8_t ttl = 1; ttl < max_ttl; ttl++) {
            ping(ttl);
        }
    }
 
    void ping(uint8_t ttl) {
        io_service io_service;
 
        ip::icmp::resolver resolver(io_service);
        ip::icmp::resolver::query query(ip::icmp::v4(), hostname, "");
        ip::icmp::resolver::iterator iter = resolver.resolve(query);
        destination = *iter;
 
        source = ip::icmp::endpoint(ip::icmp::v4(), 0);
 
        try {
            ip::icmp::socket socket(io_service, source);
 
            icmp_packet packet;
            packet.icmp_type(ping::icmp::type::echo_request);
            packet.icmp_id(ping::icmp::id);
            packet.icmp_seq(ping::icmp::seq);
            packet.calc_checksum();
 
            std::ostream ostream(&request_buffer);
            ostream << packet;
 
            const ip::unicast::hops option(ttl);
            socket.set_option(option);
 
            boost::posix_time::ptime time_sent = boost::posix_time::microsec_clock::universal_time();
            //socket.non_blocking(true);
 
            int timeout = 3000;
            deadline_timer timer(socket.get_io_service());
            timer.expires_from_now(boost::posix_time::milliseconds(timeout));
            timer.async_wait(boost::bind(&pinger::handle_timeout, this, ttl, &socket));
 
 
            //std::cout << "# send icmp echo request to " << destination.address().to_string() << " with ttl " << (uint16_t)ttl <<"\n";
            socket.send_to(request_buffer.data(), destination);
 
            reply_buffer.consume(reply_buffer.size());
            socket.async_receive(
                reply_buffer.prepare(ping::ip::reply_buffer_size),
                boost::bind(&pinger::handle_receive, this, _1, _2, ttl, time_sent, &timer)
            );
            io_service.run();
 
        } catch (std::exception& e) {
            std::cout << "# exception: " << e.what() << "\n";
            return;
        }
    }
 
    void handle_timeout(uint8_t ttl, ip::icmp::socket *socket) {
        std::cout << "# timeout ttl " << (uint16_t)ttl <<"\n";
        socket->cancel();
        socket->close();
    }
 
    void handle_receive(
        boost::system::error_code error,
        std::size_t length,
        uint8_t ttl,
        boost::posix_time::ptime time_sent,
        deadline_timer *timer
    ) {
        reply_buffer.commit(length);
 
        timer->cancel();
 
        if (error != boost::system::errc::success) {
           std::cout << "# error " << error << "\n";
           return;
        }
 
        boost::posix_time::ptime time_receive = boost::posix_time::microsec_clock::universal_time();
 
        //std::cout << "# receive " << length << " bytes\n";
        std::istream istream(&reply_buffer);
        //std::cout << "# reply buffer size " << reply_buffer.size() << "\n";
 
        ip_packet ip;
        icmp_packet icmp;
 
        istream >> ip >> icmp;
 
        ip::address_v4 source_address(ip.header.ip_src);
        std::string source_address_str = source_address.to_string();
 
        //std::cout << "# receive ip packet from " << source_address_str << "\n";
        //std::cout << "#   ip ver " << ip.ip_v() << "\n";
        //std::cout << "#   ip hdr len " << ip.ip_hl() << "\n";
        //std::cout << "#   ip ttl " << ip.ip_ttl() << "\n";
 
        auto time_diff = time_receive - time_sent;
 
        uint16_t icmp_type = icmp.icmp_type();
        switch(icmp_type) {
            case ping::icmp::type::echo_reply:
                std::cout << "# ttl " << uint16_t(ttl) << " receive icmp echo reply packet from " << source_address_str << "\n";
                std::cout << "#   icmp id " << icmp.icmp_id() << "\n";
                std::cout << "#   icmp seq " << icmp.icmp_seq() << "\n";
                std::cout << "#   time " << time_diff.total_milliseconds() << " ms\n";
 
                break;
            case ping::icmp::type::time_exceeded:
                std::cout
                    << "# ttl " << uint16_t(ttl)
                    << " receive icmp time exceeded packet from " << source_address_str
                    << " after " << time_diff.total_milliseconds() << " ms\n";
                break;
            default:
                std::cout
                    << "# ttl " << uint16_t(ttl)
                    << " receive icmp packet type " << icmp_type
                    << " from " << source_address_str << "\n";
                break;
        }
    }
};
 
int main(int argc, char **argv) {
 
    pinger p("www.gnu.org");
    p.start();
    return 0;
}

Out

$ sudo ./ping
# ttl 1 receive icmp time exceeded packet from 192.168.56.1 after 2 ms
# timeout ttl 1
# ttl 2 receive icmp time exceeded packet from 212.48.195.247 after 12 ms
# timeout ttl 2
# ttl 3 receive icmp time exceeded packet from 212.48.198.234 after 66 ms
# timeout ttl 3
# ttl 4 receive icmp time exceeded packet from 87.226.133.254 after 76 ms
# timeout ttl 4
# ttl 5 receive icmp time exceeded packet from 194.68.123.187 after 126 ms
# timeout ttl 5
# ttl 6 receive icmp time exceeded packet from 184.105.65.125 after 96 ms
# timeout ttl 6
# ttl 7 receive icmp time exceeded packet from 72.52.92.213 after 80 ms
# timeout ttl 7
# ttl 8 receive icmp time exceeded packet from 72.52.92.166 after 154 ms
# timeout ttl 8
# ttl 9 receive icmp time exceeded packet from 184.105.64.54 after 166 ms
# timeout ttl 9
# timeout ttl 10
# error system:85
# ttl 11 receive icmp echo reply packet from 209.51.188.148
#   icmp id 1234
#   icmp seq 4567
#   time 148 ms
# timeout ttl 11

First PagePrevious PageBack to overviewNext PageLast Page