pox控制器學習筆記

一、安裝pox

pox基於python2.7

$ git clone http://github.com/noxrepo/pox
$ cd pox
~/pox$ git checkout dart

二、調用pox

  1. 如果想快速入門
./pox.py samples.pretty_log Forwarding.l2_learning
  1. POX本身有幾個可選的命令行參數,這些參數可以在命令行開始時使用:
選項 含義
–no-cli 不要啓動交互式shell(從betta開始不再適用
–no-openflow 不要自動開始偵聽OpenFlow連接(從dart開始有用,它僅按需加載OpenFlow)
–verbose 顯示其他信息(對於調試啓動問題特別有用)

 

l2_learning具有“透明”模式,其中交換機甚至將轉發通常丟棄的數據包(例如LLDP消息),並且Web服務器的端口號可以從默認值(8000)更改爲任意端口

eg:
./pox.py --no-cli Forwarding.l2_learning --transparent web.webcore --port = 8888

 

三、POX中的組件

3.1庫存成分

  1. py
    該組件使POX啓動一個交互式Python解釋器,該解釋器可用於調試和交互式實驗。在betta版本之前,這是默認行爲

  2. forwarding.hub
    僅在每個交換機上安裝通配泛洪規則

  3. Forwarding.l2_learning
    使OpenFlow交換機充當L2學習交換機的一種。當此組件學習L2地址時,它安裝的流在儘可能多的字段上都是完全匹配的。

  4. forward.l2_pairs
    像l2_learning一樣,此組件也使OpenFlow交換機的行爲類似於L2學習交換機的類型。但是,與l2_learning不同,l2_pairs僅基於MAC地址安裝規則。

  5. Forwarding.l3_learning
    它最有用的方面是作爲使用POX的數據包庫檢查和構造ARP請求和答覆的一個很好的例子。l3_learning並不真正在乎諸如子網之類的傳統IP東西,它只是瞭解IP地址在哪裏。

  6. Forwarding.l2_multi
    L2層地址學習,但該層的學習不是單個交換機的獨立學習,而是通過openflow.discovery交換機之間交換拓撲信息,學習整個網絡的拓撲結構。只要網絡中有一個交換機學習到一個新的Mac地址及其位置,所有的交換機就都能學會。

  7. forwarding.l2_nx
    Open vSwitch的quick-and-dirty組件,需要使用Openvswitch的Nicira擴展安裝。

  8. forwarding.topo_proactive
    基於重要拓撲的IP地址安裝規則。通過DHCP進行地址分配。

  9. openflow.spanning_tree
    該組件使用discovery組件來創建網絡拓撲的視圖,構造一棵生成樹,然後使不在生成樹中的交換機端口的洪泛功能失效,使得網絡中不存在洪泛迴路。
    兩個選項:
      --no-flood,只要交換機連接上了就使該交換機的所有端口洪泛失效,對於某些端口,稍後將使能。
      --hold-down,防止洪泛控制在一個完整的發現迴路完成前被改變
    因此該組件最安全的的使用方法是
    openflow.spanning_tree --no-flood --hold-down .

  10. openflow.webservice
    Openflow的一個簡單JSON-RPC-ish web service交互式接口,由of_service信息服務派生而來,依賴於webcore組件。可以使用HTTP POST方式發送JSON進行訪問。
    目前支持的方法有:
    get_flow_stats,獲取流表的表項
    get_switch_desc,獲取指定交換機詳細信息
    get_switches,獲取交換機列表和基本信息
    set_table , 設置指定交換機的流表

  11. web.webcore
    在Pox進程中啓動一個web服務,其他組件可以通過它提供靜態或動態內容。

  12. messenger
    該組件通過雙向JSON消息爲POX在進程間提供了一個交互接口。該組件本質上是API,通過TCP Socket和HTTP進行通信。具體的功能通過Services實現。messenger.log_service允許遠程操作log(讀log信息, 配置log等)。openflow.of_service 允許一下Openflow的操作(如顯示交換機列表,設置流表表項等)。./tools/pox-log.py是一個獨立的Python應用,可以通過 TCP同log服務進行交互。

  13. openflow.of_01
    該組件同openflow 1.0協議版本的交換機 進行通訊,默認啓動。

  14. openflow.discovery
    該組件在交換機之間使用特製的LLDP報文來發現整個網絡的拓撲結構。當鏈路生效或者失效時,該組件都會產生一個事件(Raise Events)。

  15. openflow.debug
    加載該組件將導致POX創建pcap追蹤(進行抓包),包括openflow報文,可導入wireshark進行分析。該工具並不能完全代替wireshark或tcpdump,不過有一個比較好的特性是每一個openflow報文都一個完整的幀中。

  16. openflow.keepalive
    該組件令POX向已經連接的交換機週期性的發送echo請求。但這會解決兩個問題:
    第一,有些交換機(包括推薦交換機)會認爲空閒連接意味着同控制器連接丟失,將會在一段silence時間後斷開
    連接。
    第二,如果網絡與交換機斷開,控制器將不會立即獲得一個FIN或RST,所以將會很難確定一個交換機失效。通過週期行發送echo請求,並分析交換機的響應,即可解決該問題。

  17. proto.pong
    該組件是一個簡單的檢測ICMP echo請求和應答的樣例組件

  18. proto.arp_responder
    該組件爲一個ARP應用,可以學習和代理ARP請求,也可以通過查詢靜態的表項來回復ARP請求。該組件提供了一個控制檯交互界面來查詢和修改arp表。

  19. info.packet_dump
    該組件將packet_in信息保存至log中,有點類似於在交換機中運行tcpdump

  20. proto.dns_spy
    檢測DNS應答並存儲應答結果,其他組件可以通過DNSSpy檢測這些信息。

  21. proto.dhcp_client
    DHCP客戶端,在同其他組件進行聯合時有用(??)

  22. proto.dhcpd
    簡單的DHCP服務器端,服務器本身的默認地址爲192.168.0.254,下發的地址域爲192.168.0.1~192.168.0.253,同時宣稱自身爲網關和DNS服務器。

  23. misc.of_tutorial
    配合openflow tutorial使用的組件,類似於簡單的hub,但可以修改成L2 learning的交換機

  24. misc.full_payload
    默認情況下,當一個數據包在交換機流表中沒有命中時,交換機只向控制器發送數據包的前128bytes,使用該組件可以將每一個交換機配置成發送整個數據包

  25. misc.mac_blocker
    具有Tkinter-based界面,可以阻塞Mac地址

  26. misc.nat
    實現網絡地址轉換的組件(木有詳細介紹)

  27. misc.ip_loadbalancer
    由carp branch(不理解是啥)啓用的TCP負載均衡器

  28. misc.gephi_topo
    檢測拓撲結構,並將其導入到gephi中進行分析

3.2 自定義組件

  1. ext目錄
    “ ext”目錄是構建您自己的組件的方便位置,因爲POX會自動將其添加到Python搜索路徑(即在其中查找其他模塊),並且將其從POX git存儲庫中排除。
    開始構建自己的POX模塊的一種常見方法是簡單地將現有模塊(例如forwarding/l2_learning.py)複製到ext目錄(例如ext/my_component.py)中。

四、POX API

4.1 pox core對象

pox是許多pox api的中心點,它提供的某些功能只是圍繞其他功能的便捷包裝,而有些則是唯一的。但是,核心對象的另一個主要目的是在組件之間提供一個集合點。

  1. 註冊組件
    對於組件來說,將提供api的對象註冊在覈心對象上會更方便。在OpenFlow實施中可以找到一個示例POS OpenFlow組件默認將OpenFlowNexus的實例住粗爲core.openflow,然後其它程序可以從哪裏訪問許多OpenFlow功能。
  2. 依賴和事件管理
    當pox中的組件依賴於其它組件,通常是因爲他們想要監聽該組件的事件。(例子看不懂)

4.2 使用地址:pox.lib.addresses

POX中的IPv4,IPv6和以太網地址由pox.lib.addresses的IPAddr,IPAddr6和EthAddr類表示。

from pox.lib.addresses import IPAddr, IPAddr6, EthAddr
 
ip = IPAddr("192.168.1.1")
print str(ip) # Prints "192.168.1.1"
print ip.toUnsignedN() # Convert to network-order unsigned integer -- 16885952
print ip.raw # Returns a length-four bytes object (a four byte string, more or less)
 
ip = IPAddr(16885952,networkOrder=True)
print str(ip) # Also prints "192.168.1.1" !

4.3 事件系統:pox.lib.revent

POX中的事件都是revent.Event實例的子類。引發事件的類從revent.EventMixin繼承,並聲明它在eventMixin.events的類級變量中引發的事件。

4.3.1 處理事件

  1. 事件處理程序
    它是一個函數或方法或可調用的其它東西,只有一個參數
  2. 聆聽事件
chef.addListener(SpamFinished, spam_ready)
or
chef.addListenerByName("SpamFinished", spam_ready)
  1. 自動設置監聽器
    使用addListeners()偵聽同一源對象的多個事件。
class HungryPerson (object):
  """ Models a person that loves to eat spam """
 
  def __init__ (self):
    chef.addListeners(self)  //查看chef的事件,self名稱爲方法時,爲偵聽器
 
  def _handle_SpamStarted (self, event):
    print "I can't wait to eat!"
 
  def _handle_SpamFinished (self, event):
    print "Spam is ready!  Smells delicious!"
要用一個類來監聽來自多個事件源的事件,用前綴來區分不同的事件。
class VeryHungryPerson (object):
  """ Models a person that is hungry enough to need two chefs """
 
  def __init__ (self):
    master_chef.addListeners(self, prefix="master")
    backup_chef.addListeners(self, prefix="secondary")
 
  def _handle_master_SpamFinished (self, event):
    print "Spam is ready!  Smells delicious!"
 
  def _handle_secondary_SpamFinished (self, event):
    print "Backup spam is ready.  Smells slightly less delicious."
  1. 創建自己的事件類型
    事件是子類revent.event,因此創建事件只需創建子類Event
class SpamStarted (Event):
  def __init__ (self, brand = "Hormel"):
    Event.__init__(self)
    self.brand = brand
 
  @property
  def genuine (self):
    # If it's not Hormel, it's just canned spiced ham!
    return self.brand == "Hormel"
  1. 引發事件
chef.raiseEvent(SpamStarted("Generic"))
or
chef.raiseEvent(SpamStarted, "Generic")

4.4 使用數據包:pox.lib.packet

POX中的許多應用程序都與數據包進行交互(例如,構造數據包並將其發送到交換機之外,或者會通過ofp_packet_inOpenFlow消息從交換機接收到它們)。爲方便起見,POX具有用於解析和構造數據包的庫。
pox支持的某些數據包類型包括:

  • ethernet
  • ARP
  • IPv4
  • ICMP
  • TCP
  • UDP
  • DHCP
  • DNS
  • LLDP
  • VLAN

引用pox數據包庫爲:import pox.lib.packet as pkt
包對象的find()方法可用於通過所需的類型名稱(例如"icmp")或其類(例如pkt.ICMP)來查找特定的封裝包。如果數據包對象未封裝所請求類型的數據包,則find()返回無。例如:

def handle_IP_packet (packet):
  ip = packet.find('ipv4')
  if ip is None:
    # This packet isn't IP!
    return
  print "Source IP:", ip.srcip

4.4.1 以太網(ethernet)

屬性:

  • dst(EthAddr)
  • src(EthAddr)
  • type(int)-以太網類型或以太網長度字段。對於帶有VLAN標籤的幀,該值爲0x8100
    Effective_ethertype(int)-以太網類型或以太網長度字段。對於帶有VLAN標籤的幀,這將是VLAN標頭中引用的類型。

常數:
IP_TYPE,ARP_TYPE,RARP_TYPE,VLAN_TYPE,LLDP_TYPE,JUMBO_TYPE,QINQ_TYPE-各種以太類型

4.4.2 ipv4

IP版本4(ipv4)
屬性:

  • srcip(IPAddr)
  • dstip(IPAddr)
  • tos(int)-8位服務類型/ DSCP + ECN
  • id(int)-標識字段
  • 標誌(int)
  • frag(int)-片段偏移
  • ttl(int)
  • protocol(int)-有效負載的IP協議編號
  • csum(int)-校驗和

常數:
ICMP_PROTOCOL,TCP_PROTOCOL,UDP_PROTOCOL-各種IP協議編號
DF_FLAG-不分段標誌位
MF_FLAG-更多片段標誌位

4.4.3 tcp

屬性:

  • srcport(int)-源TCP端口號
  • dstport(int)-目標TCP端口號
  • seq(int)-序列號
  • ack(int)-ACK號
  • off(int)-偏移量
  • flags(int)-標記爲位字段(更易於使用全大寫標記屬性)
  • csum(int)-校驗和
  • options(tcp_opt對象的列表)
  • win(int)-窗口大小
  • urg(int)-緊急指針
  • FIN(bool)-設置FIN標誌時爲真
  • SYN(bool)-設置SYN標誌時爲True
  • RST(bool)-設置RST標誌時爲true
  • PSH(bool)-設置PSH標誌時爲true
  • ACK(bool)-設置ACK標誌時爲真
  • URG(bool)-設置URG標誌時爲真
  • ECN(bool)-設置ECN標誌時爲true
  • CWR(bool)-設置CWR標誌時爲true

常數:
FIN_flag,SYN_flag等 -與標誌對應的位

 
tcp_opt類

屬性:
type(int)-TCP選項ID(可能在下面對應於常數)
val(變量)-選項值
常數:
EOL,NOP,MSS,WSOPT,SACKPERM,SACK,TSOPT-選項類型ID

4.4.4 arp messages

def _handle_PacketIn (self, event):
    packet = event.parsed
    if packet.type == packet.ARP_TYPE:
        if packet.payload.opcode == arp.REQUEST:
            arp_reply = arp()
            arp_reply.hwsrc = <requested mac address>
            arp_reply.hwdst = packet.src
            arp_reply.opcode = arp.REPLY
            arp_reply.protosrc = <IP of requested mac-associated machine>
            arp_reply.protodst = packet.payload.protosrc
            ether = ethernet()
            ether.type = ethernet.ARP_TYPE
            ether.dst = packet.src
            ether.src = <requested mac address>
            ether.payload = arp_reply
            #send this packet to the switch
            #see section below on this topic
        elif packet.payload.opcode == arp.REPLY:
            print "It's a reply; do something cool"
        else:
            print "Some other ARP opcode, probably do something smart here"

五、pox中的OpenFlow

 

5.1DPID:

OpenFlow中每個數據路徑(交換機)具有唯一的數據路徑ID或DPID,該ID或DPID是64位值,並且在握手期間通過ofp_switch_features消息從交換機傳遞到控制器。

5.2 與數據路徑通信

  1. 1)當從控制器到交換機進行通信時,這是由控制器代碼執行的,該代碼將OpenFlow消息發送到特定的交換機(稍後對此進行詳細介紹)。2)當消息來自交換機時,它們作爲事件顯示在POX中您可以爲其編寫事件處理程序–通常,有一個事件類型對應於交換機可能發送的每種消息類型。
  2. 與POX中的數據路徑進行通信的方式基本上有兩種:通過Connection該特定數據路徑的對象通過管理該數據路徑的OpenFlow Nexus。Connection連接到POX的每個數據路徑都有一個對象,並且通常有一個OpenFlow Nexus管理所有連接。
  3. 在正常配置中,只有一個OpenFlow關係,可以作爲core.openflow。兩者之間有很多重疊Connections和Nexus。
  4. 偵聽所有事件時可以選擇偵聽nexus,只對一個交換機感興趣時偵聽connection。

5.2.1 connection對象

每次開關連接到POX時,都會有一個關聯的Connection對象。

connection對象屬性 描述
ofnexus 對與此連接關聯的關係對象的引用
dpid 交換機的數據路徑標識符
features 交換機在握手過程中具有交換機發送的回覆
ports 交換機上的端口。此屬性是對特殊PortCollection對象的引用,有端口號,以太網地址,端口名稱
sock 連接到對等方的套接字。
send(msg) 一種用於向交換機發送OpenFlow消息的方法。

使用方法:self.connections = set()

5.2.2 Nexus – core.openflow

一個OpenFlow nuxus 本質上是一組OpenFlow的管理器Connections。

屬性 描述
miss_send_len 當數據包與數據路徑上的任何表條目都不匹配時,數據路徑將在數據包進入消息中將數據包轉發到控制器。
clear_flows_of_connect 如果爲True(默認值),則POX在連接時將刪除交換機第一個表上的所有流。
connections 一個特殊的集合,包含對該關係正在處理的所有連接的引用。
getConnection(< dpid>) 通過其DPID或特定(如果不可用)獲取特定數據路徑的connection對象。
sendToDPID(< dpid>,< msg>) 將OpenFlow消息發送到特定的數據路徑,如果未連接數據路徑,則丟棄該消息(並記錄警告)。

nexus本質上是一個字典,其中的鍵是DPID,值是Connection對象。

5.3 openflow事件:對交換機的響應

大多數與OpenFlow相關的事件都是直接響應從交換機收到的消息而引發的。
與openflow相關的event具有以下三個屬性:

屬性 類型 描述、
connection connection 與相關交換機的連接(例如,發送此事件對應的消息的交換機)
ofp ofp_header子類 導致此事件的OpenFlow消息對象。
dpid long 相關交換機的數據路徑ID

5.3.1 ConnectionUp

不會響應於從交換機接收到特定的OpenFlow消息而發出-只是響應於通過開關建立新的控制通道而觸發。

屬性 類型 描述
ofp ofp_switch_features 包含有關交換機的信息,例如受支持的操作類型和端口信息

5.3.2 ConnectionDown

此事件不會在響應實際的OpenFlow消息時觸發,僅在終止與交換機的連接時觸發該事件。

5.3.3 PortStatus

class PortStatus (Event):
  def __init__ (self, connection, ofp):
    Event.__init__(self)
    self.connection = connection
    self.dpid = connection.dpid
    self.ofp = ofp
    self.modified = ofp.reason == of.OFPPR_MODIFY
    self.added = ofp.reason == of.OFPPR_ADD
    self.deleted = ofp.reason == of.OFPPR_DELETE
    self.port = ofp.desc.port_no

5.3.4 FlowRemoved

當控制器ofp_flow_removed從交換機接收到OpenFlow流刪除消息()時引發事件

屬性 類型 含義
idleTimeout bool 如果由於閒置而刪除了條目,則爲真
hardTimeout bool 如果條目由於超時而被刪除,則爲真
timeout bool 如果條目由於任何超時而被刪除,則爲True
deleted bool 如果條目被明確刪除則爲真

5.3.5 Statistics Events

事件 openflow統計類型
SwitchDescReceived ofp_desc_stats
FlowStatsReceived ofp_flow_stats
AggregateFlowStatsReceived ofp_aggregate_stats_reply
TableStatsReceived ofp_table_stats
PortStatsReceived ofp_port_stats
QueueStatsReceived ofp_queue_stats

5.3.6 PacketIn

當控制器收到openflow的packet-in消息時觸發。該消息表示到達交換機端口的包未能匹配表中的所有條目,或者匹配條目包括的動作–指定發送該包的到控制器。
除了通常的openflow事件屬性外:

  • port(int)-數據包進入的端口號
  • data(bytes)-原始數據包數據
  • 解析(包子類)-pox.lib.packet的解析版本
  • ofp(ofp_packet_in)-導致此事件的OpenFlow消息

5.3.7 ErrorIn

當從交換機收到一個openflow錯誤時觸發。
除了通常的openflow事件屬性外:

屬性 含義
should_log 通常,OpenFlow錯誤會導致出現日誌消息。如果處理ErrorIn事件,則可以將此屬性設置爲False,以使默認日誌消息靜音。
asString() 將此錯誤格式化爲字符串。

5.3.8 BarrierIn屏障進入

當控制器從交換機接收到OpenFlow屏障回覆時觸發,表明交換機已在相應的屏障請求之前完成了控制器發送的處理命令.
 

5.4 openflow消息

OpenFlow消息是OpenFlow交換機與控制器進行通信的方式。

5.4.1 ofp_packet_out-從交換機發送數據包

該消息的主要目的是指示交換機發送數據包(或使其入隊)。但是,它也可用作指示交換機丟棄緩衝的數據包的方法。

屬性 類型 默認 描述
in_port int OFPP_NONE 重新發送數據包時,數據包到達的交換端口。
data 字節/以太網/ ofp_packet_in ‘’ 要發送的數據
buffer_id int/none none 數據包存儲在數據路徑中的緩衝區的ID。如果您不是按ID重新發送緩衝區,請使用“無”。
actions ofp_action_XXXX的清單 [ ] 如果只有一個項目,則也可以使用初始化程序的命名參數“ action”進行指定。

~~

附:
在這裏插入圖片描述


"""
A stupid L3 switch

For each switch:
1) Keep a table that maps IP addresses to MAC addresses and switch ports.
   Stock this table using information from ARP and IP packets.
2) When you see an ARP query, try to answer it using information in the table
   from step 1.  If the info in the table is old, just flood the query.
3) Flood all other ARPs.
4) When you see an IP packet, if you know the destination port (because it's
   in the table from step 1), install a flow for it.
"""

from pox.core import core
import pox
log = core.getLogger()

from pox.lib.packet.ethernet import ethernet, ETHER_BROADCAST
from pox.lib.packet.ipv4 import ipv4
from pox.lib.packet.arp import arp
from pox.lib.addresses import IPAddr, EthAddr
from pox.lib.util import str_to_bool, dpid_to_str
from pox.lib.recoco import Timer

import pox.openflow.libopenflow_01 as of

from pox.lib.revent import *

import time

# Timeout for flows
FLOW_IDLE_TIMEOUT = 10

# Timeout for ARP entries
ARP_TIMEOUT = 60 * 2

# Maximum number of packet to buffer on a switch for an unknown IP
MAX_BUFFERED_PER_IP = 5

# Maximum time to hang on to a buffer for an unknown IP in seconds
MAX_BUFFER_TIME = 5


class Entry (object):
  """
  Not strictly an ARP entry.
  We use the port to determine which port to forward traffic out of.
  We use the MAC to answer ARP replies.
  We use the timeout so that if an entry is older than ARP_TIMEOUT, we
   flood the ARP request rather than try to answer it ourselves.
  """
  def __init__ (self, port, mac):
    self.timeout = time.time() + ARP_TIMEOUT
    self.port = port
    self.mac = mac

  def __eq__ (self, other):
    if type(other) == tuple:
      return (self.port,self.mac)==other
    else:
      return (self.port,self.mac)==(other.port,other.mac)

  def __ne__ (self, other):
    return not self.__eq__(other)

  def isExpired (self):
    if self.port == of.OFPP_NONE: return False
    return time.time() > self.timeout


def dpid_to_mac (dpid):
  return EthAddr("%012x" % (dpid & 0xffFFffFFffFF,))


class l3_switch (EventMixin):
  def __init__ (self, fakeways = [], arp_for_unknowns = False, wide = False):
    # These are "fake gateways" -- we'll answer ARPs for them with MAC
    # of the switch they're connected to.
    self.fakeways = set(fakeways)

    # If True, we create "wide" matches.  Otherwise, we create "narrow"
    # (exact) matches.
    self.wide = wide

    # If this is true and we see a packet for an unknown
    # host, we'll ARP for it.
    self.arp_for_unknowns = arp_for_unknowns

    # (dpid,IP) -> expire_time
    # We use this to keep from spamming ARPs
    self.outstanding_arps = {}

    # (dpid,IP) -> [(expire_time,buffer_id,in_port), ...]
    # These are buffers we've gotten at this datapath for this IP which
    # we can't deliver because we don't know where they go.
    self.lost_buffers = {}

    # For each switch, we map IP addresses to Entries
    self.arpTable = {}

    # This timer handles expiring stuff
    self._expire_timer = Timer(5, self._handle_expiration, recurring=True)

    core.listen_to_dependencies(self)

  def _handle_expiration (self):
    # Called by a timer so that we can remove old items.
    empty = []
    for k,v in self.lost_buffers.iteritems():                 '''不知道self.lost_buffers的表項是如何'''
      dpid,ip = k

      for item in list(v):                                         '''v是一個表項,expires_at,buffer_id,in_port'''
        expires_at,buffer_id,in_port = item                     """,expires_at是到期時間,buffer_id爲數據包存儲再數據路徑中的緩衝區id"""
        if expires_at < time.time():
          # This packet is old.  Tell this switch to drop it.
          v.remove(item)
          po = of.ofp_packet_out(buffer_id = buffer_id, in_port = in_port)          '''交換機發出的消息'''
          core.openflow.sendToDPID(dpid, po)              '''將OpenFlow消息發送到特定的數據路徑,如果未連接數據路徑,則丟棄該消息'''
      if len(v) == 0: empty.append(k)                   '''條目如果是空的,則將該表項的dpid和ip加入empty'''

    # Remove empty buffer bins
    for k in empty:
      del self.lost_buffers[k]           '''刪除empty中的表項'''

  def _send_lost_buffers (self, dpid, ipaddr, macaddr, port):
    """
    We may have "lost" buffers -- packets we got but didn't know
    where to send at the time.  We may know now.  Try and see.
    """
    if (dpid,ipaddr) in self.lost_buffers:                            '''如果有dpid和主機的ip在緩衝區內'''
      # Yup!
      bucket = self.lost_buffers[(dpid,ipaddr)]                        '''bucket爲一個表項,expires_at, buffer_id, in_port'''
      del self.lost_buffers[(dpid,ipaddr)]                  '''丟掉該表項'''
      log.debug("Sending %i buffered packets to %s from %s"
                % (len(bucket),ipaddr,dpid_to_str(dpid)))
      for _,buffer_id,in_port in bucket:                     '''對於上述提取的表項發送數據包,設置目的mac和出端口併發送到特定的數據路徑,如果不存在,就丟失'''
        po = of.ofp_packet_out(buffer_id=buffer_id,in_port=in_port)            '''從交換機發出的消息,參數有buffer_id,in_port,actions,date'''
        po.actions.append(of.ofp_action_dl_addr.set_dst(macaddr))             '''添加設置目的mac地址的動作'''
        po.actions.append(of.ofp_action_output(port = port))                    '''設置出端口'''
        core.openflow.sendToDPID(dpid, po)                    '''將OpenFlow消息發送到特定的數據路徑,如果未連接數據路徑,則丟棄該消息'''

  def _handle_openflow_PacketIn (self, event):
    dpid = event.connection.dpid                            '''connection對象有ofnexus,dpid,features,ports,sock,send(msg)對象'''
    inport = event.port
    packet = event.parsed                                '''pox.lib.packet的解析版本'''
    if not packet.parsed:             '''如果不能解析,則丟棄該數據包'''
      log.warning("%i %i ignoring unparsed packet", dpid, inport)
      return

    if dpid not in self.arpTable:          '''如果交換機的dpid不在arp表中'''
      # New switch -- create an empty table
      self.arpTable[dpid] = {}                 '''創建一個該交換機的arp表項'''
      for fake in self.fakeways:              '''將該交換機匹配虛擬網關加入arptable中'''
        self.arpTable[dpid][IPAddr(fake)] = Entry(of.OFPP_NONE,
         dpid_to_mac(dpid))

    if packet.type == ethernet.LLDP_TYPE:                  '''如果包類型是lldp(鏈路層發現協議)則丟棄'''
      # Ignore LLDP packets
      return

    if isinstance(packet.next, ipv4):             '''如果報文是ipv4的話執行,next中包含  包類型,源地址和目的地址 '''
      log.debug("%i %i IP %s => %s", dpid,inport,
                packet.next.srcip,packet.next.dstip)

      # Send any waiting packets...
      self._send_lost_buffers(dpid, packet.next.srcip, packet.src, inport)      '''嘗試能不能從緩衝區發出去(next.srcip就是ipaddr)'''

      # Learn or update port/MAC info
      if packet.next.srcip in self.arpTable[dpid]:                   '''如果包源ip在arp表中'''
        if self.arpTable[dpid][packet.next.srcip] != (inport, packet.src):    '''如果arp表項的入端口和包源地址不匹配'''
          log.info("%i %i RE-learned %s", dpid,inport,packet.next.srcip)
          if self.wide:
            # Make sure we don't have any entries with the old info...
            msg = of.ofp_flow_mod(command=of.OFPFC_DELETE)           '''刪除所有流表項'''
            msg.match.nw_dst = packet.next.srcip                         '''設置流表項目的地址爲包的目的地址'''
            msg.match.dl_type = ethernet.IP_TYPE
            event.connection.send(msg)
      else:                                                 '''包源ip不在arp表中'''
        log.debug("%i %i learned %s", dpid,inport,packet.next.srcip)
      self.arpTable[dpid][packet.next.srcip] = Entry(inport, packet.src)             '''對應包dpid和包源地址的條例創建爲此包的匹配項目'''

      # Try to forward
      dstaddr = packet.next.dstip                    '''取包的目的地址'''
      if dstaddr in self.arpTable[dpid]:             '''如果目的地址在arp表中'''
        # We have info about what port to send it out on...

        prt = self.arpTable[dpid][dstaddr].port          '''取表中目的交換機和目的地址對應的端口'''
        mac = self.arpTable[dpid][dstaddr].mac           '''取表中目的交換機和目的地址對應的地址'''
        if prt == inport:                                  '''如果出端口等於入端口,不發送'''
          log.warning("%i %i not sending packet for %s back out of the "
                      "input port" % (dpid, inport, dstaddr))
        else:                                              '''如果出端口不等於入端口,發送'''
          log.debug("%i %i installing flow for %s => %s out port %i"
                    % (dpid, inport, packet.next.srcip, dstaddr, prt))
          actions = []                               '''建立行爲列表'''
          actions.append(of.ofp_action_dl_addr.set_dst(mac))                   '''添加動作,爲設置目的mac地址'''
          actions.append(of.ofp_action_output(port = prt))                      '''添加動作,爲交換機發消息的出端口爲arp表中目的交換機的端口'''
          if self.wide:                                 '''如果爲廣泛匹配'''
            match = of.ofp_match(dl_type = packet.type, nw_dst = dstaddr)            
          else:
            match = of.ofp_match.from_packet(packet, inport)

          msg = of.ofp_flow_mod(command=of.OFPFC_ADD,            '''添加流表項'''
                                idle_timeout=FLOW_IDLE_TIMEOUT,        '''在FLOW_IDLE_TIMEOUT時間內,如果沒有報文觸發,則該規則刪除'''
                                hard_timeout=of.OFP_FLOW_PERMANENT,       '''到達OFP_FLOW_PERMANENT時間時,無論如何都刪除該規則'''
                                buffer_id=event.ofp.buffer_id,          '''設計數據包存儲在數據路徑中的緩衝區的ID'''
                                actions=actions,                         '''動作爲動作列表'''
                                match=match)
          event.connection.send(msg.pack())
      elif self.arp_for_unknowns:                         '''也就是說目的地址不在arp表中'''
        # We don't know this destination.
        # First, we track this buffer so that we can try to resend it later
        # if we learn the destination, second we ARP for the destination,
        # which should ultimately result in it responding and us learning
        # where it is

        # Add to tracked buffers
        if (dpid,dstaddr) not in self.lost_buffers:             '''如果交換機的dpid和目的地址不在緩衝區內'''
          self.lost_buffers[(dpid,dstaddr)] = []             '''創建該交換機的表項'''
        bucket = self.lost_buffers[(dpid,dstaddr)]               '''取緩衝區的表項'''
        entry = (time.time() + MAX_BUFFER_TIME,event.ofp.buffer_id,inport)        '''建立條例,當前的時間,條例最大存在時間,數據包存儲在數據路徑中的緩衝區的ID,入端口'''
        bucket.append(entry)
        while len(bucket) > MAX_BUFFERED_PER_IP: del bucket[0]          '''如果bucket項目的長度大於交換機上用於未知ip的最大數據包數'''

        # Expire things from our outstanding ARP list...
        self.outstanding_arps = {k:v for k,v in                       
         self.outstanding_arps.iteritems() if v > time.time()}

        # Check if we've already ARPed recently
        if (dpid,dstaddr) in self.outstanding_arps:
          # Oop, we've already done this one recently.
          return

        # And ARP...
        self.outstanding_arps[(dpid,dstaddr)] = time.time() + 4            '''目的地址表項過期時間爲4秒,不再接收'''

        r = arp()                                 '''構建一個arp消息'''
        r.hwtype = r.HW_TYPE_ETHERNET                 '''arp消息的類型爲以太網'''
        r.prototype = r.PROTO_TYPE_IP                    '''報文proto類型爲ip'''
        r.hwlen = 6                         
        r.protolen = r.protolen                 '''協議長度'''
        r.opcode = r.REQUEST                    '''廣播請求'''
        r.hwdst = ETHER_BROADCAST               '''廣播'''
        r.protodst = dstaddr               '''協議目的爲目標地址'''
        r.hwsrc = packet.src               '''源地址是請求包的地址'''
        r.protosrc = packet.next.srcip            '''協議源是請求包源ip'''
        e = ethernet(type=ethernet.ARP_TYPE, src=packet.src,
                     dst=ETHER_BROADCAST)
        e.set_payload(r)             '''將arp消息封裝在鏈路層協議中'''
        log.debug("%i %i ARPing for %s on behalf of %s" % (dpid, inport,
         r.protodst, r.protosrc))
        msg = of.ofp_packet_out()         '''指示交換機發送數據包'''
        msg.data = e.pack()                               '''將e封裝在msg的數據域中'''
        msg.actions.append(of.ofp_action_output(port = of.OFPP_FLOOD))
        msg.in_port = inport
        event.connection.send(msg)



    elif isinstance(packet.next, arp):
      a = packet.next                         '''a中有包類型,目的地址,源地址'''
      log.debug("%i %i ARP %s %s => %s", dpid, inport,
       {arp.REQUEST:"request",arp.REPLY:"reply"}.get(a.opcode,
       'op:%i' % (a.opcode,)), a.protosrc, a.protodst)

      if a.prototype == arp.PROTO_TYPE_IP:           '''如果a的協議類型爲arp協議'''
        if a.hwtype == arp.HW_TYPE_ETHERNET:           '''如果爲鏈路層協議'''
          if a.protosrc != 0:                 '''並且包的協議源地址不爲0.0.0.0'''

            # Learn or update port/MAC info
            if a.protosrc in self.arpTable[dpid]:                 '''如果協議源地址在arp表中'''
              if self.arpTable[dpid][a.protosrc] != (inport, packet.src):       '''並且交換機號和源地址不等於入端口和包源地址的是哦胡'''
                log.info("%i %i RE-learned %s", dpid,inport,a.protosrc)
                if self.wide:
                  # Make sure we don't have any entries with the old info...
                  msg = of.ofp_flow_mod(command=of.OFPFC_DELETE)   '''刪除流表項'''
                  msg.match.dl_type = ethernet.IP_TYPE
                  msg.match.nw_dst = a.protosrc
                  event.connection.send(msg)
            else:
              log.debug("%i %i learned %s", dpid,inport,a.protosrc)
            self.arpTable[dpid][a.protosrc] = Entry(inport, packet.src)           '''不在的話建立表項'''

            # Send any waiting packets...
            self._send_lost_buffers(dpid, a.protosrc, packet.src, inport)       '''嘗試把緩衝區發出去'''

            if a.opcode == arp.REQUEST:            '''如果操作碼爲請求'''
              # Maybe we can answer

              if a.protodst in self.arpTable[dpid]:
                # We have an answer...

                if not self.arpTable[dpid][a.protodst].isExpired():
                  # .. and it's relatively current, so we'll reply ourselves

                  r = arp()
                  r.hwtype = a.hwtype
                  r.prototype = a.prototype
                  r.hwlen = a.hwlen
                  r.protolen = a.protolen
                  r.opcode = arp.REPLY
                  r.hwdst = a.hwsrc
                  r.protodst = a.protosrc
                  r.protosrc = a.protodst
                  r.hwsrc = self.arpTable[dpid][a.protodst].mac
                  e = ethernet(type=packet.type, src=dpid_to_mac(dpid),
                               dst=a.hwsrc)
                  e.set_payload(r)
                  log.debug("%i %i answering ARP for %s" % (dpid, inport,
                   r.protosrc))
                  msg = of.ofp_packet_out()
                  msg.data = e.pack()
                  msg.actions.append(of.ofp_action_output(port =
                                                          of.OFPP_IN_PORT))
                  msg.in_port = inport
                  event.connection.send(msg)
                  return

      # Didn't know how to answer or otherwise handle this ARP, so just flood it
      log.debug("%i %i flooding ARP %s %s => %s" % (dpid, inport,
       {arp.REQUEST:"request",arp.REPLY:"reply"}.get(a.opcode,
       'op:%i' % (a.opcode,)), a.protosrc, a.protodst))

      msg = of.ofp_packet_out(in_port = inport, data = event.ofp,
          action = of.ofp_action_output(port = of.OFPP_FLOOD))
      event.connection.send(msg)


def launch (fakeways="", arp_for_unknowns=None, wide=False):
  fakeways = fakeways.replace(","," ").split()
  fakeways = [IPAddr(x) for x in fakeways]
  if arp_for_unknowns is None:
    arp_for_unknowns = len(fakeways) > 0
  else:
    arp_for_unknowns = str_to_bool(arp_for_unknowns)
  core.registerNew(l3_switch, fakeways, arp_for_unknowns, wide)

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