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
#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 のような動きをします.指定されたホスト名に対して,
- ICMP ECHO REQUEST を送信する.
- ICMP ECHO REPLY を受信する.
- 1 秒スリープする.
と言う動きを繰り返します.RTT の計測にはタイムスタンプは使わずに,send() メソッドが成功した直後から recv() メソッドが成功した直後までの時間を表示しています.
Template Parameters
- Family
- プロトコルファミリーを指定します.
Related Types
typedef basic_rawsocket<SOCK_RAW, Family, IPPROTO_ICMP> rawsocket; typedef basic_sockaddress<Family, IPPROTO_ICMP> sockaddress; 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 sockaddress& 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 sockaddress& addr); int send_to(const string_type& src, const sockaddress& 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 sockaddress& from() const; const sockaddress& to() const;
send() メソッドは,コンストラクタ,または connect() メソッドで指定したホストに対して, 通信を試みます.from() メソッドは,直近の recv() メソッドを用いて受信したデータの送り先を返します.