概要
ICMP Echo Requestの送信・受信を1回だけ実行するシンプルなクライアントを作ってみたので、忘れないうちに実装時に学習したことや、気をつけたこと等をつらつらとメモ。
成果物リンク
https://rubygems.org/gems/simple_ping
https://github.com/kuredev/simple_ping
目的
- ネットワークプログラミングの練習
- gem パッケージ公開の練習
- 気晴らし
どんなものか/使い方
できること
- ping(ICMP)した結果の成否をtrue/falseで返却する
- 宛先の指定はIPアドレス
できないこと
- FQDNでの宛先指定
- リトライ
- 送信データのカスタマイズ(ID指定やデータ部の指定等)
- 等など
使い方サンプル
require "simple_ping"
ping_client = SimplePing::Client.new(src_ip_addr: "192.168.1.100")
puts ping_client.exec(dest_ip_addr: "192.168.1.101") ? "Success!" : "Failed.."
※実行時にはroot権限が必須
> sudo ruby ping.rb
Success!
実装メモ
- ソケットプログラミングを行うこととなるが、 ドキュメントを見るといくつかソケットにも種類があるが、今回はICMPヘッダを直に触るので、
SOCK_RAW
(Rawソケット)を用いる。- 第一引数には
Socket::AF_INET
を指定する。IPヘッダ以上のヘッダを触ることができる。
- 第一引数には
- 最終的にデータを送信(send)するときはデータを文字列で指定する必要がある。用意したICMPヘッダのデータ列を1ByteずつASCIIコードに従って文字列に変換する必要がある。
- 受信する際は
recvfrom
メソッドによって受信し、データは文字列で取得できる- 取得したデータはIPヘッダも含んだ形でのデータとなっている。
- IPヘッダのサイズは基本的に20Byteなので(違う場合もあるようだが今回は実装上は想定していない)ICMPヘッダのデータは21Byte目から順番に取得できる
- 例えばICMPヘッダ先頭の「タイプ」であれば以下のように取得できる
-
bytes
メソッドで文字列をASCIIコードに変換できる
-
mesg, _ = socket.recvfrom(1500)
mesg[21].bytes[0] #=> 0
- 受信時には受信したパケットが送信した物の帰りのパケットかを判定する必要があるが、「ID」と「シーケンス番号」と「タイプ」を照合して、送信・受信の対応を確認する(以下RFCの通り)
受信したエコーメッセージに含まれるデータは、エコーリプライメッセージのデータとして返されなければならない。
識別子とシーケンス番号は、エコー送信者がエコー要求とそのリプライとを対応させるのに使用される。例えば TCP や UDP がセッションを特定するために使用するポート番号のように識別子を使用してもよいし、エコー要求のたびにシーケンス番号を増加させてもよい。エコーを返す側は、エコーリプライの中で同じ値を返す。
http://srgia.com/docs/rfc792j.html
参考
RubyでいろいろなSocket作って遊んでみた。 - Qiita https://qiita.com/MikuriyaHiroshi/items/b0a40f5e7b7be1ef327c
【Ruby】RawSocketを使ったネットワークプログラム – Euniclus https://euniclus.com/article/ruby-port-scan/
BasicSocket#send (Ruby 2.7.0 リファレンスマニュアル) https://docs.ruby-lang.org/ja/latest/method/BasicSocket/i/send.html
Internet Control Message Protocol - Wikipedia https://ja.wikipedia.org/wiki/Internet_Control_Message_Protocol#ICMP%E3%83%98%E3%83%83%E3%83%80
ASCII コードと文字を変換する (ord, chr, bytes, unpack, codepoints) | まくまくRubyノート https://maku77.github.io/ruby/number/ascii-char.html
RFC792(INTERNET CONTROL MESASAGE PROTOCOL) http://srgia.com/docs/rfc792j.html