1. 寫在前面
本文是基於上一篇“ping詳解”寫的;
不同操作系統下的命令也不同,本文僅針對windows系統,命令為“tracert xxx”,效果如下
2. 使用traceroute做什么
與上一篇ping相似,原理上都是通過向目的主機發送一條消息並通過回顯應答來判斷目的主機狀態。不同的是,traceroute主要用於遍歷由源主機到目的主機交互線路上所有的路由器並判斷其狀態,一般以30為最大TTL(time to live,生存時間),即以該線路上有不超過29(30-1)台路由器為前提進行遍歷。
3. 編寫traceroute要比編寫ping多知道些什么
(1)traceroute基本原理
當路由器收到一份IP數據報,如果該報文的TTL字段是1,則意味着它在網路中的生存周期已經消耗殆盡,本路由處理后還未到達目的主機的話,需要將該數據報丟棄,並給信源主機發送一份ICMP超時報文(包含該中間路由器的地址),這意味着:通過發送一份TTL字段為n的IP數據報給目的主機,就得到了該路徑中的第n個路由器的IP地址,那么我們使IP數據報的TTL字段值從1開始依次遞增,就可以獲得所有中間路由的ip地址。當IP數據報到達目的主機時,由於已經到達目的主機,因此不會再發送ICMP超時報文了,而是ICMP應答報文,通過區分收到的ICMP報文是超時報文(type=11)還是應答報文(type=0),以判斷程序應該何時結束;
(2)運行traceroute時要關閉防火牆(直接由系統發起的則不用,CMD)
(至於為什么暫時講不明白,待續);
(3)TTL
這里的TTL值可自定義,每經過一個節點(路由器等)TTL就會減少1,等到減少到0的時候我們稱TTL過期了,此時會返回一條消息給源主機反映這條數據所到達的最后一台路由器的狀態/信息;
上面寫最大TTL為30,事實上,我們是將數據的TTL從1開始設置的,依次增加1,這樣我們就能夠依次訪問距離源主機一個節點、兩個節點、三個節點……的路由器了(即遍歷這條路上所有的主機/路由器);
(4)TIMEOUT
超時時間,事實上我們在ping上也提到了,這里在敘述一下,我們在發送了消息之后直到收到回復之后才會返回下一條消息,但是如果消息剛發出去就丟失了,或者其他情況導致沒有消息返回呢?我們不可能無節制地等下去,因此,我們設置一個TIMEMOUT並規定,如果響應時間超過了這個長度我們就認為這一次的交流失敗了,並向源主機(或者使用者)報告“請求超時”;
(5)根據返回消息的type和code來判斷該主機(路由器)狀態
編寫ping的時候我們只需要指導回顯請求(type = 8,code = 0)和回顯應答(type = 0, code = 0)就行了,但是對於traceroute,我們還要注意到其他幾個不同的type:
type 11,code = 0:ttl過期,換句話說就是當數據經過某一個路由器時ttl減少為零了,此時接收到的返回信息反映了這個路由器的狀態(這個路由器的ip等);
type 3:出錯了,所對應的code有數個值,但是我們只要知道返回消息向我們反映說這一次的路線不能正確地得到路由器的狀態就行了,詳細信息可以看下圖中的type = 3時code對應的狀態
(6)tries
我們上面提到,消息再發送出去的時候可能會丟失或者其他意外情況出現,但是我們並不希望一次失敗就認為這個路由器不可訪問等,因此,我們基本上會對這條線路嘗試多次,一般為三次
(7)DNS
發送和接收消息都很快(以ms為單位),但是通過DNS查詢返回消息的主機信息就顯得有些慢了(以s為單位),因此,我們通常在嘗試發送三次消息后再通過DNS查看是哪個主機響應了
4. 源碼
# --coding:utf-8-- import socket import os import struct import time import select # ICMP echo_request TYPE_ECHO_REQUEST = 8 CODE_ECHO_REQUEST_DEFAULT = 0 # ICMP echo_reply TYPE_ECHO_REPLY = 0 CODE_ECHO_REPLY_DEFAULT = 0 # ICMP overtime TYPE_ICMP_OVERTIME = 11 CODE_TTL_OVERTIME = 0 # ICMP unreachable TYPE_ICMP_UNREACHED = 3 MAX_HOPS = 30 # set max hops-30 TRIES = 3 # detect 3 times # checksum def check_sum(strings): csum = 0 countTo = (len(strings) / 2) * 2 count = 0 while count < countTo: thisVal = strings[count + 1] * 256 + strings[count] csum = csum + thisVal csum = csum & 0xffffffff count = count + 2 if countTo < len(strings): csum = csum + strings[len(strings) - 1] csum = csum & 0xffffffff csum = (csum >> 16) + (csum & 0xffff) csum = csum + (csum >> 16) answer = ~csum answer = answer & 0xffff answer = answer >> 8 | (answer << 8 & 0xff00) return answer # get host_info by address def get_host_info(host_addr): try: host_info = socket.gethostbyaddr(host_addr) except socket.error as e: display = '{0}'.format(host_addr) else: display = '{0} ({1})'.format(host_addr, host_info[0]) return display # construct ICMP datagram def build_packet(): # primitive checksum my_checksum = 0 # process_id my_id = os.getpid() # sequence as 1(>0) my_seq = 1 # 2's header my_header = struct.pack("bbHHh", TYPE_ECHO_REQUEST, CODE_ECHO_REQUEST_DEFAULT, my_checksum, my_id, my_seq) # SYS_time as payload my_data = struct.pack("d", time.time()) # temporary datagram package = my_header + my_data # true checksum my_checksum = check_sum(package) # windows-big endian my_checksum = socket.htons(my_checksum) # repack my_header = struct.pack("bbHHh", TYPE_ECHO_REQUEST, CODE_ECHO_REQUEST_DEFAULT, my_checksum, my_id, 1) # true datagram ip_package = my_header + my_data return ip_package def main(hostname): print("routing {0}[{1}](max hops = 30, detect tries = 3)".format(hostname, socket.gethostbyname(hostname))) for ttl in range(1, MAX_HOPS): print("%2d" % ttl, end="") for tries in range(0, TRIES): # create raw socket icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp")) icmp_socket.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, struct.pack('I', ttl)) icmp_socket.settimeout(TIMEOUT) # construct datagram icmp_package = build_packet() icmp_socket.sendto(icmp_package, (hostname, 0)) # waiting for receiving reply start_time = time.time() select.select([icmp_socket], [], [], TIMEOUT) end_time = time.time() # compute time of receiving during_time = end_time - start_time if during_time >= TIMEOUT or during_time == 0: print(" * ", end="") else: print(" %4.0f ms " % (during_time * 1000), end="") if tries >= TRIES - 1: try: ip_package, ip_info = icmp_socket.recvfrom(1024) except socket.timeout: print(" request time out") else: # extract ICMP header from IP datagram icmp_header = ip_package[20:28] # unpack ICMP header after_type, after_code, after_checksum, after_id, after_sequence = struct.unpack("bbHHh", icmp_header) output = get_host_info(ip_info[0]) if after_type == TYPE_ICMP_UNREACHED: # unreachable print("Wrong!unreached net/host/port!") break elif after_type == TYPE_ICMP_OVERTIME: # ttl overtimr print(" %s" % output) continue elif after_type == 0: # type_echo print(" %s" % output) print("program run over!") return else: print("request timeout") print("program run wrongly!") return if __name__ == "__main__": while True: try: ip = input("please input a ip address:") global TIMEOUT TIMEOUT = int(input("Input timeout you want: ")) main(ip) break except Exception as e: print(e) continue