1bit-Trie(一種二叉樹)實現路由表

樹的定義和基本術語

樹:是n(>=0)個結點的有限集,它或爲空樹(n=0);或爲非空樹,對於非空樹T:
(1)有且僅有一個稱之爲根的結點;
(2)除根結點以外的其餘節點可分爲 m(m>0)互不相交的有限集T1,T2,…,Tm,其中每一個集合本身又是一棵樹,並且稱爲根的子樹(subtree).
從樹的定義中我們要知道樹的固有特性,即樹的定義中又用到了樹的定義,是一個遞歸的定義.

鍵樹的基本概念


鍵樹又稱爲數字查找樹,它是一顆度大於等於2的樹,樹中的每個結點中不是包含一個或幾個關鍵字,而是隻含有組成關鍵字的符號。例如,若關鍵字爲數值,則結點中只包含一個數位;若關鍵字爲單詞,則結點中只包含一個字母字符。這種樹會給某種類型關鍵字的表的查找帶來方便。

 

1bit-Trie

代碼實現

#coding=UTF-8
#使用二叉trie實現路由表,實現添加、查詢、刪除
#author=劉一凡
#date=20180303
#python3.4

import ipaddress            

class Node():
    def __init__(self,bit=None,network=None,prefix_len=None,next_hop=None,lchild=None,rchild=None):
        self.bit = bit
        self.next_hop = next_hop
        self.network = network
        self.prefix_len = prefix_len
        self.lchild = lchild
        self.rchild = rchild

#使用二叉trie實現路由表
class BiTrie():
    def __init__(self):
        #根節點bit和next_hop爲空,可以用來存放缺省路由,目前沒有支持
        self.root = Node()

    #添加路由或者修改路由的下一跳
    def add(self,network,prefix_len,next_hop):
        #parent是每次循環要操作(可能只是經過,也可能是存儲路由)的結點current_node的父節點,
        #從root開始向下進行操作
        parent = self.root
        #提取前綴
        prefix = (int(ipaddress.IPv4Address(network)))>>(32-prefix_len)
        for i in range(prefix_len-1,-1,-1):
            #從高到低,逐bit處理
            #對於最高位bit不需要&0x1,但是其它bit需要
            bit = (prefix>>i)&0x1
            #print(bit)
            
            if bit==0:
                if not parent.lchild:
                    #左孩子不存在,則新建左孩子結點
                    parent.lchild = Node(bit=bit)
                #如果左孩子存在,它可能用於存儲路由,也可能只是提供路徑,不過這裏不區分(除了i==0時)
                #即是否有掩碼短,包含了當前network的路由,並不區分               
                current_node = parent.lchild
            elif bit==1:
                if not parent.rchild:
                    parent.rchild = Node(bit=bit)
                current_node = parent.rchild

            

                '''
                import sys
                #current_node size is 56
                print("current_node size is",sys.getsizeof(current_node))
                #24
                print(sys.getsizeof(current_node.bit))
                #57
                print(sys.getsizeof(current_node.network))
                #28
                print(sys.getsizeof(current_node.prefix_len))
                #56
                print(sys.getsizeof(current_node.next_hop))
                '''

            #當前結點current_node成爲下次循環的parent
            parent = current_node
        
        #存儲最後一個bit的結點存儲路由信息,i==0時循環結束
        #雖然代碼上沒有區別,但是功能上實現了添加和修改下一跳的兩種情況,具體是添加還是修改,取決於軟件的數據狀態(亦即取決於代碼的執行路徑)
        current_node.bit = bit
        current_node.network = network
        current_node.prefix_len = prefix_len
        current_node.next_hop = next_hop
        
        #print(node.next_hop)

    #路由查找,實現最長掩碼匹配,可以根據DIP,也可以根據Dst network查詢
    def search(self,network,prefix_len):
        #存儲匹配到的路由的下一跳
        next_hops = []
        #parent是每次循環要操作(可能只是經過,也可能是記錄路由下一跳)的結點current_node的父節點,
        #從root開始向下進行操作
        parent = self.root
        prefix = (int(ipaddress.IPv4Address(network)))>>(32-prefix_len)
        for i in range(prefix_len-1,-1,-1):
            bit = (prefix>>i)&1
            #print(bit)
            if bit==0:
                #左孩子中可能存儲着路由,也可能只是爲子孫節點提供路徑
                if parent.lchild:
                    current_node = parent.lchild
                #可能匹配失敗,或者通過next_hops[-1]可以獲取到掩碼最長的路由的下一跳
                else:
                    break
            elif bit==1:
                if parent.rchild:
                    current_node = parent.rchild
                else:
                    break
            #如果該結點存儲了路由,則記錄路由的下一跳
            if current_node.next_hop:
                next_hops.append(current_node.next_hop)
                #print(current_node.next_hop)
                
            #當前結點current_node成爲下次循環的parent
            parent = current_node

        #print(next_hops)
        #如果有多個下一跳,返回掩碼最長的network對應的下一跳 
        if next_hops:
            return next_hops[-1]
        else:
            return None

    #刪除路由
    def delete(self,network,prefix_len):
        if not self.search(network,prefix_len):
            print("The route you want to delete dose not exist!")
            return -1
        layer_path=[]
        parent=self.root
        prefix=(int(ipaddress.IPv4Address(network)))>>(32-prefix_len)
        for i in range(prefix_len-1,-1,-1):
            bit=(prefix>>i)&1
            #因爲上面已經檢查了精確路由存在,所以需要的孩子結點一定存在
            if bit==0:
                node=parent.lchild
            elif bit==1:
                node=parent.rchild
                            
            layer_path.append(node)
            parent=node
        
        #這個時候i==0,node是最後一個bit對應的結點    
        node.network=None
        node.prefix_len=None
        node.next_hop=None
        
        #回溯刪除不再需要的結點        
        for i in range(len(layer_path)-1,-1,-1):
            if not layer_path[i].lchild and not layer_path[i].rchild and not layer_path[i].next_hop:
                if layer_path[i].bit==0:
                    layer_path[i-1].lchild=None
                else:
                    layer_path[i-1].rchild=None
            #遇到一個結點不能刪除,則停止回溯,這樣可以提高性能
            else:
                break
            
    #打印路由表,先序遍歷整棵樹,先打印root,然後遞歸遍歷左右子樹(需要先將左右孩子轉換成樹)
    def PreOrderTraverse(self):
        parent=self.root
        if parent.next_hop:
            print("{0}/{1} {2}".format(parent.network,parent.prefix_len,parent.next_hop))
        if parent.lchild:
            lchild_trie=BiTrie()
            lchild_trie.root=parent.lchild
            lchild_trie.PreOrderTraverse()
        if parent.rchild:
            rchild_trie=BiTrie()
            rchild_trie.root=parent.rchild
            rchild_trie.PreOrderTraverse()            
            


trie=BiTrie()

'''
輸出爲:
5.5.5.5
8.8.8.8
2.2.2.2
128.0.0.0 2.2.2.2
224.0.0.0 5.5.5.5
228.0.0.0 8.8.8.8
2.2.2.2
8.8.8.8
2.2.2.2
128.0.0.0 2.2.2.2
228.0.0.0 8.8.8.8
'''
#不考慮IP地址的分類
trie.add("224.0.0.0",3,"5.5.5.5")
trie.add("228.0.0.0",6,"8.8.8.8")
trie.add("128.0.0.0",1,"2.2.2.2")
print(trie.search("224.0.0.0",3))
print(trie.search("228.0.0.0",6))
print(trie.search("128.0.0.0",1))
trie.PreOrderTraverse()
trie.delete("224.0.0.0",3)
print(trie.search("224.0.0.0",3))
print(trie.search("228.0.0.0",6))
print(trie.search("128.0.0.0",1))
trie.PreOrderTraverse()

'''
#None
print(trie.search('126.0.0.0',8))
#講解樹的基本概念後,然後講解鍵樹的基本概念,然後用圖講解添加、刪除、查詢的過程,最後講解代碼,和演示功能、性能效果,最後講解測試覆蓋(多重條件覆蓋、循環內路徑覆蓋、循環間路徑覆蓋)
trie.add('96.0.0.0',4,"1.1.1.1")
trie.add('96.0.0.0',8,"1.1.1.3")

#1.1.1.3
print(trie.search('96.0.0.1',32))
#None
print(trie.search('1.0.0.0',8))
trie.add('200.76.8.0',24,"1.1.1.2")
#1.1.1.2
print(trie.search('200.76.8.0',24))
#None
print(trie.search('200.76.9.0',24))
'''

'''
#3.2G的CPU的PC添加這100萬條路由需要75秒左右,佔用內存大約540M,所以平均存儲一條路由佔用存儲空間540字節
for i in range(1000000):
    trie.add(str(ipaddress.IPv4Address('1.0.0.0') + 256*i),24,str(ipaddress.IPv4Address('100.0.0.0') + i))
print("Finish add")
#100.11.250.250
print(trie.search("12.250.250.1",32))
'''


'''
trie.delete('200.76.8.0',24)
print(trie.search('200.76.8.0',24))

trie.delete('96.0.0.0',8)
print(trie.search('96.0.0.1',32))
'''

'''
#經檢查,內存沒有泄露
for i in range(1000000):
    trie.delete(str(ipaddress.IPv4Address('1.0.0.0') + 256*i),24)
print("Finish delete")
#None
print(trie.search("12.250.250.1",32))
            
while 1:
    pass
      
'''
        
'''
黑盒測試思路:
所有等價類都可能再細化,並不保證每一種排列或者組合都可行,也不保證無重疊,沒有考慮性能,IP地址的分類
角度1:四種基本操作:添加、查詢、刪除、修改
覆蓋所有操作順序:12種。
角度2:分別測試操作對象存在、不存在、重複操作3種情況,可以和角度1組合,就是12種情況
角度3:考慮包含性,考慮4個目的網絡,N1、N2、N3、N4
N1包含N2、N4,N2包含N3,N4和N2、N3沒有包含關係
分別對4個目的網絡進行4種基本操作,即角度1和角度3進行組合:16種
角度4:前綴長度覆蓋:33種
角度5:前綴特點分類:01重複、10重複、11重複、00重複 4種情況。還可以嘗試更大步長
如果覺得有價值,不同角度之間可以再組合。
加強對添加、刪除的測試。比如可以對4個目的網絡N1、N2、N3、N4逐一進行添加共12種順序,逐一進行添加,共12種順序。
'''    

測試思路

白盒測試:多重條件覆蓋、循環內路徑覆蓋、循環間路徑覆蓋

黑盒測試思路:
所有等價類都可能再細化,並不保證每一種排列或者組合都可行,也不保證無重疊,沒有考慮性能,IP地址的分類
角度1:四種基本操作:添加、查詢、刪除、修改
覆蓋所有操作順序:12種。
角度2:分別測試操作對象存在、不存在、重複操作3種情況,可以和角度1組合,就是12種情況
角度3:考慮包含性,考慮4個目的網絡,N1、N2、N3、N4
N1包含N2、N4,N2包含N3,N4和N2、N3沒有包含關係
分別對4個目的網絡進行4種基本操作,即角度1和角度3進行組合:16種
角度4:前綴長度覆蓋:33種
角度5:前綴特點分類:01重複、10重複、11重複、00重複 4種情況。還可以嘗試更大步長
如果覺得有價值,不同角度之間可以再組合。
加強對添加、刪除的測試。比如可以對4個目的網絡N1、N2、N3、N4逐一進行添加共12種順序,逐一進行添加,共12種順序。

 

頭腦風暴測試點:

IP路由表的測試
考慮添加路由、刪除路由、查詢、修改
不考慮路由目的網絡的分類

1、不同路由來源
2、刪除存在的路由
3、查詢最長掩碼匹配掩碼長、短
4、包含關係
5、查詢最大條數和統計
6、重複添加相同的路由,重複刪除
7、路由出接口震盪:shutdown/no shutdown和插拔網線兩種方式,下一跳IP在震盪,對其它路由器的影響、metric、distance
8、修改路由
9、不同的下一跳,metric修改
10、等價路由
11、多種路由來源
12、遞歸路由
13、路由容量
14、黑洞路由
15、默認路由
16、兩種修改方式
17、刪除路由,分這個路由正在轉發數據,沒有在轉發數據 兩種情況
18、secondary地址
19、distance
20、下一跳、出接口方式
21、重發布
22、策略路由
23、協議路由策略
24、內存泄露
25、兩種來源的路由,正在轉發數據,高disctance的路由被回收
26、出接口類型
27、所有前綴長度33中
28、輸入檢查,配置無效的路由
29、vrf
30、選擇邏輯接口中哪個物理接口(震盪)
31、收斂時間(區分不同接口)
32、重啓後靜態路由、直連路由
33、網段非常相似
34、過載測試,過載加變化
35、路由策略(過濾、總數限制、修改屬性)
36、彙總
37、組播路由
38、IPv4、IPv6雙棧
39、不對稱路徑

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章