Table of Contents
Groovyに続いて、KotlinでPcap4Jを使ってパケットキャプチャしてみた。
KotlinからでもPcap4Jちゃんと動くよということを実証するのが主な目的。 また、今後JavaなアプリはKotlinで書こうかと思っているので、その予習も兼ねている。
Kotlinとは
KotlinはJVM言語、つまりJavaのバイトコードにコンパイルされてJavaの実行環境で動くプログラミング言語のひとつ。 IntelliJ IDEAで有名なJetBrains社によってOSSとして開発されている。
2011年に生まれた新しめな言語で、2016/2/17にv1がリリースされ、主にAndroidアプリの開発用として注目されている。
「実用的」であることを売りにしていて、つまり少ない学習コストで導入でき、既存のJavaコードやMavenなどのツールとの相互運用性を持つとされている。 IntelliJ IDEA、Android Studio、Eclipseといった主要なIDEのサポートもあり、開発環境は整っている。 v1以降の後方互換性の維持も表明されていて、長期サポートが必要な製品開発にも堪える。
さらに、厳格な静的型付けやNullable/Non-Null型などにより安全性を確保しつつ、型推論やラムダ式などで生産性を高めている。
Javaのバイトコードだけでなく、JavaScriptを生成するバックエンドを持っているのも大きな特徴。 ユースケースがよく分からないが。
GitHubにホストされているKotlinプロジェクトは、2016/4/15現在、全体の 0.1% (3493⁄3215549) しかない。 v1のリリースは結構注目を集めたので、この割合は今後増えていくと期待される。
Kotlinのインストール
チュートリアルに従えば、IDEやテキストエディタ+コマンドラインの環境を整えてHello Worldを書いて実行するところまで簡単にできる。 筆者はEclipse(Mars)とコマンドラインの環境をWindows 7上で作った。 Kotlinのバージョンは1.0.1-2。
コマンドラインについては、GitHub Releasesからアーカイブをダウンロードして、適当なところに展開してbin
フォルダにパスを通すだけ。
前提となるJavaについては、環境変数JAVA_HOME
を設定するか、java
コマンドにパスを通せばいい模様。
因みにKotlinの書き方は、Kotlin Koansという例題集をオンラインのIDEで解きながらを学ぶことができる。
パケットキャプチャ with Pcap4J in Java
Pcap4Jでパケットキャプチャするコードを普通にJavaで書くと以下の様になる。 (Groovyの時のと一緒。)
import java.io.IOException; | |
import org.pcap4j.core.BpfProgram.BpfCompileMode; | |
import org.pcap4j.core.NotOpenException; | |
import org.pcap4j.core.PacketListener; | |
import org.pcap4j.core.PcapHandle; | |
import org.pcap4j.core.PcapNativeException; | |
import org.pcap4j.core.PcapNetworkInterface; | |
import org.pcap4j.core.PcapNetworkInterface.PromiscuousMode; | |
import org.pcap4j.packet.Packet; | |
import org.pcap4j.util.NifSelector; | |
public class Pcap4jLoop { | |
public static void main(String [] args) throws PcapNativeException, IOException, NotOpenException, InterruptedException { | |
String filter = null; | |
if (args.length != 0) { | |
filter = args[0]; | |
} | |
PcapNetworkInterface nif = new NifSelector().selectNetworkInterface(); | |
if (nif == null) { | |
System.exit(1); | |
} | |
final PcapHandle handle = nif.openLive(65536, PromiscuousMode.PROMISCUOUS, 10); | |
if (filter != null && filter.length() != 0) { | |
handle.setFilter(filter, BpfCompileMode.OPTIMIZE); | |
} | |
PacketListener listener = new PacketListener() { | |
@Override | |
public void gotPacket(Packet packet) { | |
printPacket(packet, handle); | |
} | |
}; | |
handle.loop(5, listener); | |
} | |
private static void printPacket(Packet packet, PcapHandle ph) { | |
StringBuilder sb = new StringBuilder(); | |
sb.append("A packet captured at ") | |
.append(ph.getTimestamp()) | |
.append(":"); | |
System.out.println(sb); | |
System.out.println(packet); | |
} | |
} |
これを実行すると、パケットキャプチャするネットワークインターフェースを選択し、5つのパケットをキャプチャしてタイムスタンプと共にコンソールに表示する。
パケットキャプチャ with Pcap4J in Kotlin
上記処理をKotlinで書くと以下の様になる。
import org.pcap4j.core.BpfProgram.BpfCompileMode | |
import org.pcap4j.core.NotOpenException | |
import org.pcap4j.core.PacketListener | |
import org.pcap4j.core.PcapHandle | |
import org.pcap4j.core.PcapNativeException | |
import org.pcap4j.core.PcapNetworkInterface | |
import org.pcap4j.core.PcapNetworkInterface.PromiscuousMode | |
import org.pcap4j.core.PcapStat | |
import org.pcap4j.packet.Packet | |
import org.pcap4j.util.NifSelector | |
fun PcapHandle.printLoop(count: Int) { | |
loop(count, {packet: Packet -> | |
println("A packet captured at ${getTimestamp()}:") | |
println(packet) | |
}) | |
} | |
fun main(args: Array<String>) { | |
val filter = if (args.size != 0) args[0] else null | |
val nif = NifSelector().selectNetworkInterface() | |
nif ?: System.exit(1) | |
val handle = nif.openLive(65536, PromiscuousMode.PROMISCUOUS, 10) | |
filter?.let { | |
if (filter.length != 0) { | |
handle.setFilter(filter, BpfCompileMode.OPTIMIZE) | |
} | |
} | |
handle.printLoop(count = 5) | |
} |
メインクラスはGroovy同様書かなくていいが、main
関数は必要。
型推論があってとても楽。 ラムダ式、補間文字列(String interpolation)、名前付き引数といったモダンめな機能は普通に使える。 (名前付き引数はJavaで書いたメソッドをKotlinから呼ぶときは使えない。)
オープンクラスを実現する機能であるExtensionsをPcapHandle
に使ってみた。
なんだか便利そう。
Nullable/Non-Null型がすごい。言語仕様でNullPointerException
が発生しないように守ってくれる。
例えばfilter
は宣言の時点では初期化文でnull
が入る可能性があるのでNullable
なString
という型に推論されるが、filter?.let
というNullチェックをするメソッドに渡したブロック内では自動でNon-Null
なString
にキャストされ、filter.length
を安全に評価できるようになっている。
Nullチェックをしないでfilter.length
と書くとコンパイルエラーになる。すごい。
けどJavaのコードから返ってくるオブジェクトは普通、プラットフォーム型というものになり、このNullセーフな仕組みが働かない。 これに対してはNull可能性アノテーションを使えば幸せになれるらしい。
さらに、上記コードには表れていないが、キャストも安全になっている模様。(cf. スマートキャスト、セーフキャスト)
Kotlinは基本コンパイラ言語なので、上記コードを実行するには以下ようなコマンドで一旦コンパイルする必要がある。
kotlinc -cp pcap4j-core.jar Pcap4jLoop.kt -include-runtime -d Pcap4jLoop.jar
このコマンドだとKotlinのランタイム入りjarファイルができる。 このjarを、Pcap4J 1.6.2、Slf4J 1.7.12、JNA 4.2.1を使って、以下のコマンドで実行できることを確認した。
java -cp pcap4j-core.jar;pcap4j-packetfactory-static.jar;jna.jar;slf4j-api.jar;Pcap4jLoop.jar Pcap4jLoopKt tcp
このコマンドで指定しているメインクラスPcap4jLoopKt
は、上記コードでクラスの記述を省いた為にKotlinがソースファイル名(Pcap4jLoop.kt)を基に自動生成したもの。
コンパイル/実行方法は他にもある。
スクリプトなKotlin
Kotlinプログラムはスクリプトとしても書けて、コンパイル無しで実行することができる。
この場合、main
関数は消してその中身をトップレベルに書き、ファイルの拡張子を.kts
にする。
import org.pcap4j.core.BpfProgram.BpfCompileMode | |
import org.pcap4j.core.NotOpenException | |
import org.pcap4j.core.PacketListener | |
import org.pcap4j.core.PcapHandle | |
import org.pcap4j.core.PcapNativeException | |
import org.pcap4j.core.PcapNetworkInterface | |
import org.pcap4j.core.PcapNetworkInterface.PromiscuousMode | |
import org.pcap4j.core.PcapStat | |
import org.pcap4j.packet.Packet | |
import org.pcap4j.util.NifSelector | |
fun PcapHandle.printLoop(count: Int) { | |
loop(count, {packet: Packet -> | |
println("A packet captured at ${getTimestamp()}:") | |
println(packet) | |
}) | |
} | |
val filter = if (args.size != 0) args[0] else null | |
val nif = NifSelector().selectNetworkInterface() | |
nif ?: System.exit(1) | |
val handle = nif.openLive(65536, PromiscuousMode.PROMISCUOUS, 10) | |
filter?.let { | |
if (filter.length != 0) { | |
handle.setFilter(filter, BpfCompileMode.OPTIMIZE) | |
} | |
} | |
handle.printLoop(count = 5) |
上記スクリプトは以下のコマンドで実行できた。
kotlinc -cp pcap4j-core.jar;jna.jar;pcap4j-packetfactory-static.jar;slf4j-api.jar -script Pcap4jLoop.kts tcp
EclipseのKotlinプラグインはこのスクリプト形式をまだサポートしていないようで残念。