traceroute 的歷史

前幾天看到同學在 Windows 裡的 VM 做 traceroute 的測試,看到兩種不同的結果:

  • 在 Windows Guest VM 使用 tracert 這個指令時,完全沒事
  • 在 Ubuntu Guest VM 使用 traceroute 這個指令時,卻冒出一堆星星

上網找了一下為什麼會出現這種情況,一查才發現原來 traceroute 在 Linux 預設是送 UDP 不是 ICMP Echo Request,把指令變成 traceroute -I 就得到理想的結果了,判斷有可能 UDP 在 Windows 上直接被防火牆給擋了。

man traceroute

RFC792

因為 ICMP 現在應該很普及了,所以覺得很奇怪為什麼預設要用 UDP 不用 ICMP,查了一下才發現 RFC792 曾經有規定不准機器在收到 ICMP 後回 ICMP,原文如下:

RFC792 Introduction

The ICMP messages typically report errors in the processing of datagrams. To avoid the infinite regress of messages about messages etc., no ICMP messages are sent about ICMP messages. Also ICMP messages are only sent about errors in handling fragment zero of fragemented datagrams. (Fragment zero has the fragment offeset equal zero).

看原文的意思,可能是希望看到 ICMP 封包中的 TypeCode 欄位,就知道這個 ICMP 在表達什麼,而不希望有巢狀的結構,要一層一層打開來才能看到。

Van Jacobson’s traceroute

Van Jacobson 在 1987 年開發了 traceroute 這個程式,但是受限於 RFC792 的規定,在當時大部分 router 的實作中都不會在收到 ICMP Echo Request TTL=0 的時候回送一個 ICMP Time Exceeded,導致這個程式很少能夠正常運作。

後來 Van Jacobson 將這個程式修改成 UDP 的版本,透過戳裝置上的高位 port (33434+) 回傳的 ICMP Port Unreachable 當作中止條件,並透過在每跳一個 hop 將 port number + 1 的方式來維持 sequence number,以此來解決 RFC792 的問題。

RFC1122

在 1989 年,RFC1122 重新調整了這個規範:

RFC1122 3.2.2

An ICMP error message MUST NOT be sent as the result of receiving:

  • an ICMP error message, or

調整成不准機器收到 ICMP Error 後回 ICMP Error,後來 ICMP 版本的 traceroute 才能比較正常的運作,但 traceroute 預設的 UDP 模式也就這樣保留到現在了。(Windows 的 tracert 預設是送 ICMP)

Appendix. History about sending ICMP packet

在查資料的過程,也有查到有人traceroute 如果要送 ICMP 就需要使用到 raw_socket,就需要 root privilege,因為這個原因才把 UDP 當作預設的模式使用。

以這個方向繼續查,就這樣查到 pingtraceroute 在很久以前是有 setuid 的,而且後來也轉成權限更嚴格的 Capabilities,但我在我的電腦上用 getcap /usr/bin/pinggetcap /usr/bin/traceroute,都沒有看到有相關的資訊。

繼續往下查,才發現送 ICMP 已經早就不需要 raw_socket 了,現在可以透過 socket(PF_INET, SOCK_DGRAM, PROT_ICMP) 來送 ICMP 封包。

strace traceroute 結果

雖然這個方向我感覺不是 traceroute 預設走 UDP 模式的主要原因,但也因此發現了一些有趣的事阿。

參考資料

RFC792 - INTERNET CONTROL MESSAGE PROTOCOL

RFC1122 - Requirements for Internet Hosts – Communication Layers

net: ipv4: add IPPROTO_ICMP socket kind

comments powered by Disqus