tracerouteの高速版をSwiftで書いてみた
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ホップぐらいで到達できるだろうという予想です。
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だけ余裕をやって終了としています。
// 受信完了 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から順に表示しますが、「エコー応答」があった場合は、それで終わりです。 結果を受け取っていないルータは、アスタリクスで表示します。
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で設定しています。
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の高速版を書いてみました。 多重化することで、到達時間等の測定精度は、かなり悪いと思います。 また、宛先ホストより非常に遅いレスポンスを返すルータがいた場合は、無視されることになります。 単純に、経路を概観するだけのプログラムということでご了承ください。
作成したプログラムは、下記に置きました。