2012-04-04 92 views
0

我想在Python中實現基於ICMP的Traceroute。我發現了一個非常有用的指南(https://blogs.oracle.com/ksplice/entry/learning_by_doing_writing_your),它允許我創建基於UDP的Traceroute,因此只需要修改即可。不過,我環顧四周,無法更改發送套接字並使其工作。有人能幫助我嗎?在Python中創建ICMP跟蹤路由

#!/usr/bin/python 

import socket 

def main(dest_name): 
    dest_addr = socket.gethostbyname(dest_name) 
    port = 33434 
    max_hops = 30 
    icmp = socket.getprotobyname('icmp') 
    udp = socket.getprotobyname('udp') 
    ttl = 1 
    while True: 
     recv_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp) 
     send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, udp) 
     send_socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl) 
     recv_socket.bind(("", port)) 
     send_socket.sendto("", (dest_name, port)) 
     curr_addr = None 
     curr_name = None 
     try: 
      _, curr_addr = recv_socket.recvfrom(512) 
      curr_addr = curr_addr[0] 
      try: 
       curr_name = socket.gethostbyaddr(curr_addr)[0] 
      except socket.error: 
       curr_name = curr_addr 
     except socket.error: 
      pass 
     finally: 
      send_socket.close() 
      recv_socket.close() 

     if curr_addr is not None: 
      curr_host = "%s (%s)" % (curr_name, curr_addr) 
     else: 
      curr_host = "*" 
     print "%d\t%s" % (ttl, curr_host) 

     ttl += 1 
     if curr_addr == dest_addr or ttl > max_hops: 
      break 

if __name__ == "__main__": 
    main('google.com') 
+0

它是**接收**套接字,如果您不以root身份運行,會導致「操作不允許」。 – 2012-04-04 09:18:18

+0

哪個平臺在運行?其實你提到的文章提到'因爲原始套接字需要root權限,所以traceroute通常是setuid。對於我們的目的,我們可以以root身份運行腳本:'。但是,它看起來像在RHEL5上,'traceroute'不是setuid root(參見http://traceroute.sourceforge.net/) – 2012-04-04 09:29:33

+0

是在Ubuntu上運行並以root身份運行,沒有運行問題的問題是在運行時使用語法 – Jamesla 2012-04-04 11:31:53

回答

-2

結束了寫我自己使用scapy,因爲這是不可能的。

+0

請幫助別人,即使你使用了另一個庫(scapy)。 – KillianDS 2012-11-09 12:25:12

2

不知道你爲什麼選擇scapy(雖然它是很好的模塊),因爲這當然可以使用python。要發送一個ICMP數據包,你只需發送你的recv_socket。爲了發出這個套接字,你需要先創建一個ICMP包。

但是,你似乎想要通過ICMP套接字發送UDP數據包。這不會像你想象的那樣工作。

首先,讓我明白地說,Linux內核中存在一個補丁,它將允許SOCK_DGRAM和IPPROTO_ICMP:ICMP sockets (linux)。我沒有測試過這個。

雖然,這種套接字標誌的組合不起作用。這是因爲ICMP套接字需要ICMP頭。如果要發送類似於send_socket的空字符串,則內核將丟棄該數據包。此外,如果要在UDP頭部上覆蓋一個ICMP頭部,接收系統只會對收到的ICMP頭部做出反應,並將UDP頭部視爲僅附加到ICMP的數據。事實上,在它的ICMP回覆中,遠程系統將首先將你的UDP頭部包含在你發送給它的數據中。

你能在send_socket發送一個空字符串的原因是因爲內核創造了UDP報頭的你,和你發送了該UDP套接字被簡單地追加爲數據的UDP報頭。這不像ICMP套接字那樣。正如我寫的,你需要創建icmp頭在這個套接字上發送。

的一個UDP的「ping」會發生什麼的現狀是這樣的:一個UDP分組是通過UDP套接字到遠程系統發送的,使用(希望)未開封口作爲目的端口。這會從遠程系統中獲取ICMP響應(類型3,代碼3)。此時,您將需要一個ICMP註冊的套接字來處理ICMP回覆,這需要root(或Windows上的管理員)權限。

要創建一個ICMP報頭是很容易的:

import struct 
icmp = struct.pack(">BBHHH", 8, 0, 0, 0, 0) 
icmp = struct.pack(">BBHHH", 8, 0, checksum(icmp), 0, 0) 

幾件事情要與這個頭注意。首先,參數四和五是「標識符」和「序列號」字段。通常,標識符被設置爲進程ID,而序列號從1開始,因此您可以跟蹤發送到回覆的序列。在這個例子中,我將數字設置爲零僅僅是爲了說明(儘管如果不關心它們,以這種方式保持它是完全正常的)。

其次,請參閱我提到的校驗和(icmp)函數?我假設你有權訪問這樣做的代碼。這是被稱爲1的補碼校驗,並根據RFC 1071校驗和是至關重要的,因爲批准,而不會被計算正確,接收系統可能無法將數據包轉發到任何ICMP套接字句柄。第三,如果您想知道爲什麼我最初創建的校驗和爲零,那是因爲內核在校驗和結果爲零的情況下,在實際校驗和被創建並添加回頭部之前。

由於結構模塊處理所謂的「打包」二進制數據,我建議您熟悉位移和位運算符。當你進一步進入原始套接字時,你將需要這些,特別是當你需要從可能或不可能重疊的位字段中提取某些位時。例如,下面是IP報頭:

0     1     2     3 
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
0 |Version| IHL |Type of Service|   Total Length   | 
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
4 |   Identification  |Flags|  Fragment Offset | 
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
8 | Time to Live | Protocol |   Header Checksum  | 
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
12 |      Source Address       | 
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
16 |     Destination Address      | 
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
20 |     Options     | Padding | 
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 

假設你想在源字段插入IP地址爲192.168.1.10,首先必須記下其長度,4個字節。要正確插入到這個結構(只是這個領域,沒有休息頭的,作爲一個例子),你必須:

struct.pack(">I", 192 << 24| 168 << 16| 1 << 8| 10) 

這樣做增加了對這一領域,3232235786L適當的整數。 (有些讀者可能會指出,struct.pack(「> BBBB」,192,168,1,10)可能會有相同的結果。雖然這可以用於這個用例,但它在一般情況下是不正確的在這種情況下,它的工作原理是因爲IP地址是面向字節的;但是,不是面向字節的字段(如校驗和字段)將失敗,因爲校驗和的結果值大於255,也就是說,所以一般不要這樣做,即使用協議字段期望的確切位)。

作爲學習位移和操作的另一個例子,取VER字段的IP頭。這是一個半字節大小的字段,即4位。爲了提取這一點,你會做以下爲例(假設國際人道法是零):

# Assume ip[] already has a full IP header. 
ver = struct.unpack("!B", ip[0])[0] 

# This produces the integer 4, which is required for sending IPV4 
ver >> 4 

概括地說,我已經右移一個字節的值下降了4位。在這種降檔,相比舊值新的價值現在看起來是這樣的:

# OLD VALUE IN BINARY; value is 64 
# 01000000 

# NEW VALUE IN BINARY; value is 4 
# 00000100 

需要注意的是,如果沒有這個轉變,檢查原始的二進制值將導致64,這不是什麼很重要預期。對於逆,以「包」的值4到一個字節中的高4位,這樣做:

# New value will be 64, or binary 01000000 
x = 4 << 4 

希望這點你在正確的方向。

0

老問題,但增加了另一點清晰,因爲我最近不得不這樣做。

您可以使用套接字編寫本地Python版本的ICMP跟蹤路由。要解決的兩個重要問題是:

  1. 創建ICMP報頭與正確的校驗(如尤金上述規定)
  2. 設置插座的使用sockopts

對於第一個問題的TTL,在pyping模塊中有一個很好的例子,可以很好地工作。這是我編寫traceroute實用程序時使用的。

對於第二個,它是那麼容易,因爲:

current_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, 
           socket.getprotobyname("icmp")) 
current_socket.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl) 

其中「TTL」是你設置

的TTL的整數值,然後,它只是一個做的SEND/RECV的事並在一個增加TTL的控制結構中觀察返回數據包中的類型/代碼。

我在pyping模塊中已經寫入的東西之後模擬了我的頭文件包/解包;只需查找標題類型= 11,中間跳轉代碼爲0,到達目的地時標題類型= 0。

Scapy工作正常,但速度較慢,並且是一個額外的外部依賴項。我能夠在一個下午用AS查找(通過RADb)和地理位置編寫健壯的traceroute,只使用套接字。