天天看點

scapy-其中疊代器的實作細節

前面知道了在scapy中,不管是ip包,還是以太包,還有tcp包,或者它們集合在一起的包都是一個Packet,這是在資料包組織意義上講的,另外從資料包最終組合的意義上講,scapy最引人入勝的地方還在于Packet本身也是一個生成器,即generator,這是python中一個很重要的概念。Packet是一個生成器并不是為了友善或者美觀,而是scapy本身要求的。

     由于Packet表示的是一個單獨層的資料包或者多層組合後的資料包,比如tcp資料段作為ip資料報的載荷(payload),是以如果設計的好一些,我們希望能按照協定棧本身了解資料包的方式去組合資料包,也就是說,完全按照分層協定的自解釋機制,來一層一層從下往上将資料包建構好,比如在鍊路層的時候,看到上層是ip,那麼就生成一個IP對象,然後在ip層,看到上層是tcp,那麼生成一個TCP對象,依次類推,這樣就會很友善,如果按照這個思路去設計,我覺得任何人都能想到遞歸和枚舉,剩下的就是針對不同語言的編碼問題了,scapy由python實作,将Packet本身作為一個generoter也是一個很自然的想法,這是因為對于一個自鍊路層開始向上建構的資料包,它隻知道ip是它的payload,而不知道ip上面是什麼,對于ip來講也是一樣,可是不管怎樣,任何的payload都是一個Packet,是以如果我們能從鍊路層開始對其payload做for...in...操作的話,就能周遊到整個的協定層,将以太層的payload設定為payloadS(複數,因為這個payload還包含有更上層的payloadS...),然後第一次我們取出eth本身,第二次取出ip1,第三次取出tcp1...,針對于每一層的Packet,它本身又是一個生成器,生成器生成一個疊代器,該疊代器傳回的next即是它本身,這樣再通過遞歸就可以将所有層次的資料包組合在一起了。

     所有這一切的開始都在str函數,在str中:

def __str__(self):

    return self.__iter__().next().build()

self.__iter__().next()得到了Ether對象,調用它的build即可先建構以太頭,再建構payload:

def do_build(self):

    p=""

    for f in self.fields_desc:

        p = f.addfield(self, p, self.__getattr__(f))

    pkt = p+str(self.payload)

    return pkt

在do_build中,str(self.payload)又會開啟一次針對payload的__str__()(間接的對payload的__iter__的調用)的調用,進而開始了建構IP對象的過程,依次類推。這樣就需要将__iter__實作的非常好:

00def __iter__(self):

01    def loop(todo, done, self=self):

02      if todo:

03        eltname = todo.pop()  

04        elt = self.__getattr__(eltname)

05        if not isinstance(elt,  Gen):

06          if  self.fieldtype[eltname].islist:

07            elt = SetGen([elt])

08          else:

09            elt = SetGen(elt)

10        for e in elt:

11          done[eltname] = e

12          for x in loop(todo[:], done):

13            yield x  

14      else:

15        if isinstance(self.payload,NoPayload):

16          payloads = [None]

17        else:

18          payloads = self.payload

19          for payl in payloads:

20            done2 = done.copy()

21            for k in done2:

22              if isinstance(done2[k], RandNum):

23                done2[k] = int(done2[k])

24              pkt = self.__class__(**done2)

25              pkt.underlayer = self.underlayer

26              pkt.overload_fields = self.overload_fields.copy()

27              if payl is None:

28                yield pkt

29              else:

30                yield pkt/payl  

31  return loop(map(lambda x:str(x), self.fields.keys()), {}) 

其實它隻有一行,那就是第31行,它調用了一個子函數,首先map函數将所有我們手工設定的該層Packet的fields轉化成一個list,然後調用loop,整個loop分為兩大塊,以17行的else做分割。在前半部分,将fields連結清單中的字段一個個pop出,比如對于IP對象而言就是src,dst,ttl之類,直到沒有了的時候再次在12行遞歸調用loop的時候會進入後半部分else分支,此時處理它的payloadS(所有的上層資料),這個核心的實作盡在第19行,一個for...in的調用,這裡就會對payloadS調用其__iter__方法,過程又回到了第31行,隻是這次的self變了,也就是對象變了,變成了上一層資料的Packet對象了,然後一切如故...。

     在L3PacketSocket的send中從str的調用開始建構整個以太幀,整個調用過程如下:

00 call Ethernet obj-__str__ 1 

01  call Ethernet obj-__iter__ 2

02   call Ethernet obj-__iter__-loop 3

03    沒有為Ether設定屬性,是以直接進入else

04    call IP obj-__iter__ 4

05     call IP obj-__iter__-loop 5

06     取出dst

07      call IP obj-__iter__-loop 6

08       取出src

09       call IP obj-__iter__-loop 7

10        沒有其它屬性了,進入else

11        call TCP obj-__iter__ 8

12         call TCP obj-__iter__-loop 9

13          取出sport

14          call TCP obj-__iter__-loop 10

15           取出dport

16           call TCP obj-__iter__-loop 11

17            沒有其它屬性了,進入else

18            call None obj-__iter__ 12

19             11傳回TCP obj

20          10傳回TCP obj

21         9傳回TCP obj

22        8傳回IP obj

23       7傳回IP obj

24      6傳回IP obj

25    4傳回Ether obj

26  2傳回Ether obj

27  call Ethernet obj-__iter__-next 13

28   13傳回Ether obj

29   call Ether obj-build 14

30    call Ether obj.payload-str 15

31     call IP obj-__str__

32      從第4行到第24行

33 ...

從32行可以看出,在從下到上的建構過程中,在建構上一層的時候也即是建構此層的payload的時候,需要對payload調用str函數,由于在第一次建構Ether obj的時候已經做過這件事了,也就是說,在建構鍊路層資料包的時候調用str(Ether obj),由于__iter__的實作有遞歸的性質,以上各層的Packet對象其實已經構造好了,實在沒有必要再來一次,就是說,如果現在有4件事,分别分1,2,3,4,從1開始做,我們希望的是1-2-3-4這個順序,或者1,2,3,4也可以,然而scapy的方式卻是:1,2,3,4-2,3,4-3,4-4這實在是一個我認為可以改進的地方。不過既然scapy實作了如此複雜又美妙的__iter__以及資料包兼作generator的角色,我覺得已經可以抵消美中之不足矣!

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271134