CLX C++ Libraries
Home >> icmp::socket

Declaration

#define ICMP_ECHO_REQUEST 8
#define ICMP_ECHO_REPLY 0

namespace icmp {
    template <int Family>
    class basic_socket : public basic_rawsocket<SOCK_RAW, Family, IPPROTO_ICMP>
    
    typedef basic_rawsocket<SOCK_RAW, AF_INET, IPPROTO_ICMP> rawsocket;
    typedef basic_socket<AF_INET> socket;
    typedef basic_sockaddress<AF_INET, IPPROTO_ICMP> sockaddress;
    typedef basic_sockmanager<SOCK_RAW, AF_INET, IPPROTO_ICMP> sockmanager;
};

Overview

icmp::socket は,ICMP 用ソケットのラッパクラスです.現在は,ICMP ECHO REQUEST の送信,および ICMP ECHO REPLY の受信しか想定していません(値を define していない). IPヘッダおよびICMPヘッダは,それぞれ ip.h,icmp.h のグローバル名前空間において, 独自に定義しています.

struct iphdr {
    u_int8_t  ihl:4;
    u_int8_t  version:4;
    u_int8_t  tos;
    
    u_int16_t tot_len;
    u_int16_t id;
    u_int16_t frag_off;
    
    u_int8_t  ttl;
    u_int8_t  protocol;
    u_int16_t check;
    
    unsigned long saddr;
    unsigned long daddr;
};

struct icmphdr {
    u_int8_t  type;
    u_int8_t  code;
    u_int16_t checksum;
    u_int16_t id;
    u_int16_t sequence;
};

また,データに付与されているIPヘッダ,ICMPヘッダを扱うための packet_header も定義しています.packet_header は,(IP ヘッダ + ICMP ヘッダが付与されている) 受信データを引数として指定されると,受信データからヘッダ情報をコピーします.

namespace icmp {
    class packet_header {
    public:
        typedef struct iphdr ip_type;
        typedef struct icmphdr icmp_type;
        typedef size_t size_type;
        
        packet_header();
        explicit packet_header(const char* packet);
        virtual ~packet_header();
        
        packet_header& operator=(const char* packet);
        packet_header& assign(const char* packet);
        void reset();
        
        size_type size() const;
        size_type ip_size() const;
        size_type icmp_size() const;
        
        ip_type* ip();
        icmp_type* icmp();
        
        const ip_type* ip() const;
        const icmp_type* icmp() const;
    };
};

ICMP ソケットを用いて通信を行う場合,送信時にはデータの先頭に ICMP ヘッダを付与して送信する必要があります.また,受信されるデータには, IP ヘッダ,および ICMP ヘッダが付与されています.

Example

example_icmp.cpp

#include <iostream>
#include <string>
#include <cstring>
#include <memory>
#include "clx/icmp.h"
#include "clx/timer.h"
#include "clx/argument.h"
#include "clx/format.h"

int main(int argc, char* argv[]) {
    clx::argument arg(argc, argv);
    if (arg.head().empty()) {
        std::cerr << "usage " << argv[0]
            << " hostname [-l data_bytes]" << std::endl;
        return -1;
    }
    
    /*
     * bufは送受信兼用バッファ.
     *   -send()メソッド: payload + ICMPヘッダ長
     *   -recv()メソッド: payload + IPヘッダ長 + ICMPヘッダ長
     * 以上を考慮して大きめに領域を確保する.
     */
    clx::icmp::packet_header hdr;
    int payload = 56;
    arg("l,length", payload);
    int packetsize = payload + hdr.icmp_size();
    int buffsize = packetsize + 1024;
    std::auto_ptr<char> buf(new char[buffsize]);
    
    try {
        clx::icmp::socket s(arg.head().at(0));
        std::cout << clx::format("ICMP ECHO %s (%s): %d data bytes")
            % arg.head().at(0) % s.to().ipaddr() % payload
        << std::endl;
        
        int seq = 0;
        while (1) {
            std::memset(buf.get(), 'a', packetsize);
            
            // sending ICMP echo request
            hdr.reset();
            hdr.icmp()->type = ICMP_ECHO_REQUEST;
            hdr.icmp()->sequence = seq;
            std::memcpy(buf.get(), (char*)hdr.icmp(), hdr.icmp_size());
            s.send(buf.get(), packetsize);
            clx::timer t;
            
            // receiving ICMP echo packet
            std::memset(buf.get(), 0, buffsize);
            int len = s.recv(buf.get(), buffsize);
            if (len < 0) return -1;
            double recv = t.total_elapsed();
            
            // print information
            hdr = buf.get();
            if (hdr.icmp()->type == ICMP_ECHO_REPLY) {
                std::cout <<
                    clx::format("%d bytes from %s: icmp_seq=%d ttl=%d time=%d ms")
                    % len % s.from().ipaddr()
                    % static_cast<int>(hdr.icmp()->sequence)
                    % static_cast<int>(hdr.ip()->ttl)
                    % static_cast<int>(recv * 1000)
                << std::endl;
                seq = hdr.icmp()->sequence + 1;
            } else {
                std::cerr << clx::format("received ICMP packet (type: %d) from %s")
                    % static_cast<int>(hdr.icmp()->type) % s.from().ipaddr()
                << std::endl;
            }
            
            clx::sleep(1.0);
        }
    }
    catch (clx::socket_error& e) {
        std::cerr << e.what() << std::endl;
        return -1;
    }
    catch (clx::sockaddress_error& e) {
        std::cerr << e.what() << std::endl;
        return -1;
    }
    
    return 0;
}
Result
$ ./test yahoo.com -l 1000
ICMP ECHO yahoo.com (68.180.206.184): 1000 data bytes
1028 bytes from 68.180.206.184: icmp_seq=0 ttl=46 time=156 ms
1028 bytes from 68.180.206.184: icmp_seq=1 ttl=46 time=159 ms
1028 bytes from 68.180.206.184: icmp_seq=2 ttl=46 time=168 ms
1028 bytes from 68.180.206.184: icmp_seq=3 ttl=46 time=171 ms
1028 bytes from 68.180.206.184: icmp_seq=4 ttl=46 time=162 ms
1028 bytes from 68.180.206.184: icmp_seq=5 ttl=46 time=158 ms

ping のような動きをします.指定されたホスト名に対して,

  1. ICMP ECHO REQUEST を送信する.
  2. ICMP ECHO REPLY を受信する.
  3. 1 秒スリープする.

と言う動きを繰り返します.RTT の計測にはタイムスタンプは使わずに,send() メソッドが成功した直後から recv() メソッドが成功した直後までの時間を表示しています.

Template Parameters

Family
プロトコルファミリーを指定します.

Related Types

typedef basic_sockaddress<Family, IPPROTO_ICMP> address_type;
typedef char char_type;
typedef std::basic_string<char_type> string_type;

Construction and Member Functions

basic_socket();
basic_socket(const basic_socket& cp);
basic_soket& operator=(const basic_socket& cp);

explicit basic_socket(socket_int s, const address_type& addr);
explicit basic_socket(const char_type* host);
explicit basic_socket(const string_type& host);
virtual ~basic_socket();

basic_socket& connect(const char_type* host);
basic_socket& connect(const string_type& host);

int send_to(const char_type* src, int n, const address_type& addr);
int send_to(const string_type& src, const address_type& addr);
int send_to(const char_type* src, int n, const char_type* host);
int send_to(const string_type& src, const string_type& host);
int send(const char_type* src, int n);
int send(const string_type& src);

int recv(char_type* dest, int n);

const address_type& from() const;
const address_type& to() const;

send() メソッドは,コンストラクタ,または connect() メソッドで指定したホストに対して, 通信を試みます.from() メソッドは,直近の recv() メソッドを用いて受信したデータの送り先を返します.

References

  1. Life like a clown - ICMPパケットを利用したパケット間隔ベースの帯域計測方法
  2. Geekなページ - 簡単なpingの作成(ICMPの送受信)
  3. Winsock Programmer's FAQ - Ping: Raw Sockets Method