簡単な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;
}


上記サンプルではかなり色々省略してありますが、最低限は動くと思います。 動作しない、よくわからない、説明が足りない、間違っているなどがあればご指摘頂ければ幸いです。