User Tools

Site Tools


Differences

This shows you the differences between two versions of the page.

Link to this comparison view

cpp:trace [2019-05-28 15:26]
cpp:trace [2020-02-15 00:57] (current)
Line 1: Line 1:
 +
 +=====Ping & traceroute in one tube=====
 +
 +My raw socket usage example with Boost::​Asio.
 +
 +<code c++ ping.cpp> ​
 +/*
 + * ping.cpp
 + *
 + * Copyright 2004-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;
 +}
 +</​code>​
 +
 +===Out===
 +<​file>​
 +$ 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
 +</​file>​
 +
 +----
 +[<>]