この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
tracerouteは、ネットワークの経路を確認するコマンドですが、このコマンド、ちょっと待ち時間が長くてイライラすることは無いでしょうか。
今回は、このコマンドの高速版を作成してみました。
2 動作のおさらい
ちょっとここで、簡単に動作をおさらいします。
tracerouteは、pingと同じようにICMPのエコー要求パケットを投げるだけですが、この時、TTL値を1から順に増やしながら試します。 TTL値はルータを越えるごとに1づつ減らされるため、TTLが0になった時点で、ルータは「これ以上行けない」という意味で返事(Time Exceeded)を返します。 最終的に宛先ホストまで到着した時点で、経路上のすべてのルータが判明するという事です。
3 ちょっとイライラする
経路上のルータが、全部さっさと返事をすれば、特に問題は無いのですが、返事をしないルータがよくいます。 このようなルータがあると、tracerouteはタイムアウトするまで次の動作に移れないため、ここで待たされる事になり、ちょっとイライラするのです。
図のアスタリクスの状態ですね・・・(デフォルトで3回試行される)
4 高速化プログラム(Swift)
イライラする原因は、タイムアウト待ちが発生することにありますので、タイムアウトの無いプログラムを書いてみました。
(1) 多重化
最初にTTL値が1〜30のICMPパケットを一気に送信しています。 ここでのmaxは、現在30となってます。とりあえず、30ホップぐらいで到達できるだろうという予想です。
main.swift
let ping = Ping()
ping.delegate = self
// 多重スレッドでTTLの違うICMPを送信する
for i in 0 ... max {
dispatch_async(queue, {
ping.send(ipAddr, id: i, ttl: UInt32(i+1))
})
}
// type=0のパケットを受け取るまで待機
while(!isFinish){
sleep(0)
}
(2) 受信処理
返された返事は、到着順に関係なくTTL値の順で格納します。 そして、エコー応答のパケット(宛先ホストの返事)が到着したら、未返事のルータに100msだけ余裕をやって終了としています。
main.swift
// 受信完了
func recved(recvBytes: Int, srcAddr: String, icmpType: UInt8, msec: NSTimeInterval, id: Int) {
// 結果取得用の配列に格納する
var host = NetName.toHost(srcAddr) // IPアドレスから名前解決
results[id].hostName = host[0] // ホスト名
results[id].srcAddr = srcAddr // IPアドレス
results[id].icmpType = icmpType // 応答の内容
results[id].msec = msec // 経過時間
if (icmpType == 0){ //エコー応答の場合
// 到達のパケットを受けてから100msだけ待機する
usleep(100000)
isFinish = true // 終了フラグを立てる
}
}
(3) 結果表示
最終的な結果の表示は次のような感じです。 TTL値1から順に表示しますが、「エコー応答」があった場合は、それで終わりです。 結果を受け取っていないルータは、アスタリクスで表示します。
main.swift
for i in 0 ... max {
let r = results[i]
if(r.srcAddr=="*"){
print(String(format: "%d *",i+1))
}else{
print(String(format: "%d %@ (%@) %.3f ms",i+1,r.hostName,r.srcAddr,r.msec*1000))
}
if(r.icmpType==0){
break
}
}
(4) ICMPパケット
ICMPパケット用のIPPROTO_ICMPは、 SOCK_RAWで使用されているサンプルをよく見ることがありますが、ここでは、SOCK_DGRAMを使用しています。そのため、コマンド実行にroot権限は必要ありません。 また、TTL値は、setsockoptで設定しています。
Ping.swift
var icmpHdr:IcmpHeader = IcmpHeader.init(type: 8, code: 0, sum: 0, id: UInt16(id), seq: UInt16(id))
icmpHdr.sum = (self.checksum(Cast.ptr(&icmpHdr,len: IcmpHeaderLen) as UnsafePointer<UInt8>, start:0,len:IcmpHeaderLen)).bigEndian
let sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (sock < 0) {
delegate?.err("socket() faild. sock=\(sock)")
return -1
}
var t = Int32(ttl)
if( 0 != setsockopt(sock,IPPROTO_IP,IP_TTL,&t,4)){
delegate?.err("error setsockopt")
return -1
}
完成したプログラムを使用しているようすです。(画像をクリックしてください)
※早送りしていません
途中、返事をしないルータがいくつあっても、瞬時に結果を表示することができます。
5 まとめ
今回は、tracerouteの高速版を書いてみました。 多重化することで、到達時間等の測定精度は、かなり悪いと思います。 また、宛先ホストより非常に遅いレスポンスを返すルータがいた場合は、無視されることになります。 単純に、経路を概観するだけのプログラムということでご了承ください。
作成したプログラムは、下記に置きました。