RE: お題:pingコマンド
http://blog.practical-scheme.net/shiro/20141013a-ping を受けて ping コマンドを書いてみました。言語は C で、OS は Linux です。普通すぎる。ソースは github で見られます。
以下、解説と言い訳
- コマンドになっています。関数として使いたい人は、int main() を削除し、ping 関数を呼び出してください。
- コマンドライン引数で -4 オプションを付けると IPv4、-6 を付けると IPv6 です。付けなければ IPv4。
- リトライはしません。
- アドレスの与え方はホスト名またはドット記法です。getaddrinfo を使ってホスト名をアドレスに変換しています。socket 関連の man を読んでいたら gethostbyname は古いから getaddrinfo を使え、IPv6 対応も簡単になるぞ、と誘惑されたので使ってみました。
- IPv6 は不十分な対応です。
- ICMP のプロトコル番号
- 初め getprotobyname で取得しようとしたのですが、Plamo Linux の /etc/protocols が古すぎて ipv6-icmp がありませんでした。ファイルの日付が 1999年4月14日でして、どうやら IPv6 のアドレス割り振り開始の当時から更新されていないらしい。
- という事で、netinet/in.h に定義されている IPPROTO_ICMP, IPPROTO_ICMPV6 を使用しました。
- チェックサムの計算は、初め RFC を見たのですがよくわからなかったので、Google で検索して出てきたものをパクってきました。補数には基数の補数と減基数の補数の 2種類あるとか何なんだよ……。
- バイナリパケットの作成
- uint8_t の配列のポインタを、構造体のポインタにキャストしてます。どの程度合法でしたっけ。
- netinet/ip_icmp.h の struct icmphdr と、netinet/icmp6.h の struct icmp6_hdr を使いました。構造体を使って名前をつけて扱いたいけど、構造体はパディングが入る場合があるから移植性が心配だったのですが、システムに用意されているヘッダファイルなら、システムごとに適切に定義されているだろうから、大丈夫だろう、と思って採用。
- しかし、Cygwin には、これらのヘッダファイルは存在しなかった。移植性はむしろ低かった。
- 移植性といえば、1byte が 8bit ではないシステムって、バイナリパケットを作成する時どうやっているのでしょうかね。
- タイムアウトの処理は poll を使ってみました。select は昔使ったことがあったけど、poll は使ったことがなかったので。
- recvfrom で受け取ったレスポンスですが、IPv4 では IP ヘッダが含まれ、IPv6 では IP ヘッダは含まれないようです。何で不統一なの。
- recvfrom での受信は 1回で行わなければならないらしい。少しずつ読んで、読んだ情報を元にバッファ長を決めたり、分岐したりしようとして上手くいかなかった。
- localhost に ping 送信すると、自分が送信した ping を受信してしまうのですね。しばらく嵌った。とりあえず、受信したデータが ECHO_REQUEST だったらもう一度受信する事にしましたが、タイムアウトにかかる時間が 5秒より長くなりそう。
- 「送ったアドレスと異なるアドレスから返事が来ることがある」という事で、recvfrom の第5引数に書き込まれるアドレスを調べていますが、これでよかったのだろうか。