JavaでPingを打つ

結論:JavaPingを打つには、外部プロセスを使う。

public class NativeProcess {
	/** ping のタイムアウト(ms) */
	private static String TIMEOUT = "3000";
	
	public static void main(String... args) throws IOException, InterruptedException{
		boolean ret = ping(Inet4Address.getByName("192.168.1.1"));
		System.out.println(ret ? "SUCCESS" : "FAILED");
	}
	
	/**
	 * Ping を実行し、ホストとの疎通を確認します。
	 * 
	 * @param target 疎通確認をしたいホスト
	 * @return 疎通が確認できれば true, 確認できないなら false
	 */
	public static boolean ping(InetAddress target) throws IOException, InterruptedException{
		// Windows の場合
		String[] command = {"ping", "-n", "1", "-w", TIMEOUT, target.getHostAddress()};
		// Linux の場合
		// String[] command = {"ping", "-c", "1", "-t", TIMEOUT, target.getHostAddress()};
		
		return new ProcessBuilder(command).start().waitFor() == 0;
	}
}

Javaだけじゃできない

最初は、InetAddress#isReachable(int timeout)を使えばできると思ってました。
でも、コマンドプロンプトping が通るのが確認できているホストなのに、このメソッドだと false を返すという現象が発生しました。
おかしいと思ってAPIを読み返すと、以下のような記述が。

そのアドレスに到達可能かどうかをテストします。(中略)通常の実装では、特権を取得できる場合は ICMP ECHO REQUEST を使用し、それ以外の場合は接続先ホストのポート 7 (Echo) 上で TCP 接続を確立しようとします。

Oracle Technology Network for Java Developers | Oracle Technology Network | Oracle

パケットアナライザーで確認したところ、どうやら ICMP ECHO REQUEST ではなく TCP ECHO が発信されている模様*1
つまり、上記でいう「特権」とやらが取得できていない状態でした。


調べたら、ここでいう「特権」というのは Java の特権コードではなく、OSの特権(システムコール)のことのようです。
なんで、高々 ping で OS の特権なんて大げさな話になるのかと思ったら、ICMP パケットの送信/受信にはOSの特権(システムコール)が必要だ、とのこと。
How to do a true Java ping from Windows? - Stack Overflow
Mutant World: ICMP and InetAddress.isReachable()


そのため、Windows上や、Linux 上でも root 権限がないときには TCP ECHO で疎通確認をするみたいです。
でも、相手側が TCP ECHO の応答を返してくれるかどうかがあいまい*2なので、ping と同じ結果にはなりません。

なので、Javaだけじゃ無理。

そもそもの目的

輪番停電前にサーバを落とすので、全マシンにマルチスレッドで一斉に ping を打って状態確認ができればいいなと思って調べてみました*3
結局、冒頭のやり方を使ったんですが・・・。
まさか、こんなにめんどくさいものだとは思いませんでした。



ちなみに作ってから気づいたんですが。
冒頭のやり方だと「マルチスレッド」じゃなくて「マルチプロセス」になります。
なので、同時にサーバ100台に ping を打つと一気に100プロセスが起動します。
ご利用の際は、負荷にご注意を(笑)

*1:通常、pingコマンドで使われているのは ICMP ECHO REQUEST です。

*2:なぜか応答が返ってきたり来なかったりしたサーバがありました。なんでだろ・・・。

*3:バッチファイルやシェルだとシングルスレッドなので、タイムアウト1秒にしても30台をやろうとすれば30秒かかる・・・。