簡単なpingの作成(ICMPの送受信)
ここでは、Mac OS XでRAWソケットを利用してICMPパケットの送受信を行う方法を説明します。
単純なpingプログラム
pingコマンドはICMP(Internet Control Message Protocol)ECHOメッセージを送信し、相手ホストからのICMP ECHO REPLYメッセージを受け取る事により実現しています。 ここでは、ICMP ECHOメッセージを送信し、ICMP ECHO REPLYメッセージを受信する方法を説明したいと思います。 ICMPメッセージの送受信を行うにはRAWソケットと呼ばれるソケットを作成する必要があります。 RAWソケットの作成にはroot権限が必要です。 下記サンプルプログラムをコンパイルするのは一般ユーザでも大丈夫ですが、実行はrootで行う必要があります。 「pingコマンドはrootじゃなくても出来るけど?」と思う方もいるかも知れませんが、それはpingコマンドがrootにsetuidしてあるからです。 Mac OS Xではrootパスワードが初期設定されていないので、必要に応じてsudoコマンドを使うなどしてご利用下さい。
pingのサンプルコードを以下に示します。
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
/*
* チェックサムを計算する関数です。
* ICMPヘッダのチェックサムフィールドを埋めるために利用します。
* IPヘッダなどでも全く同じ計算を利用するので、
* IPヘッダのチェックサム計算用としても利用できます。
*/
unsigned short
checksum(unsigned short *buf, int bufsz)
{
unsigned long sum = 0;
while (bufsz > 1) {
sum += *buf;
buf++;
bufsz -= 2;
}
if (bufsz == 1) {
sum += *(unsigned char *)buf;
}
sum = (sum & 0xffff) + (sum >> 16);
sum = (sum & 0xffff) + (sum >> 16);
return ~sum;
}
/* main 文はここからです。*/
int
main(int argc, char *argv[])
{
int sock;
struct icmp hdr;
struct sockaddr_in addr;
int n;
char buf[2000];
struct icmp *icmphdrptr;
struct ip *iphdrptr;
if (argc != 2) {
printf("usage : %s IPADDR\n", argv[0]);
return 1;
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_len = sizeof(addr);
/* RAWソケットを作成します */
sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sock < 0) {
perror("socket");
return 1;
}
memset(&hdr, 0, sizeof(hdr));
/* ICMPヘッダを用意します */
hdr.icmp_type = ICMP_ECHO;
hdr.icmp_code = 0;
hdr.icmp_cksum = 0;
hdr.icmp_hun.ih_idseq.icd_id = 0;
hdr.icmp_hun.ih_idseq.icd_seq = 0;
/* ICMPヘッダのチェックサムを計算します */
hdr.icmp_cksum = checksum((unsigned short *)&hdr, sizeof(hdr));
/* ICMPヘッダだけのICMPパケットを送信します */
/* ICMPデータ部分はプログラムを簡潔にするために省いています */
n = sendto(sock,
(char *)&hdr, sizeof(hdr),
0, (struct sockaddr *)&addr, sizeof(addr));
if (n < 1) {
perror("sendto");
}
/* ICMP送信部分はここまでです*/
/* ここから下はICMP ECHO REPLY受信部分になります */
memset(buf, 0, sizeof(buf));
/* 相手ホストからのICMP ECHO REPLYを待ちます */
n = recv(sock, buf, sizeof(buf), 0);
if (n < 1) {
perror("recv");
}
/* 受信データからIPヘッダ部分へのポインタを取得します */
iphdrptr = (struct ip *)buf;
/*
* 本当はIPヘッダを調べて
* パケットがICMPパケットかどうか調べるべきです
*/
/* 受信データからICMPヘッダ部分へのポインタを取得します */
icmphdrptr = (void *)(buf + (iphdrptr->ip_hl * 4));
/* ICMPヘッダからICMPの種類を特定します */
if (icmphdrptr->icmp_type == ICMP_ECHOREPLY) {
printf("received ICMP ECHO REPLY\n");
} else {
printf("received ICMP %d\n", icmphdrptr->icmp_type);
}
/* 終了 */
close(sock);
return 0;
}
上記サンプルではかなり色々省略してありますが、最低限は動くと思います。 動作しない、よくわからない、説明が足りない、間違っているなどがあればご指摘頂ければ幸いです。