接觸了這么久的SDN,OpenFlow協議前前后后也讀過好多遍,但是一直沒有時間總結一下自己的一些見解。現在有時間了,就寫一寫自己對OpenFlow協議通信流程的一些理解。
2 SDN中Switch和controller
在SDN中很重要的兩個實體是Switch跟Controller。Controller在網絡中相當於上帝,可以知道網絡中所有的消息,可以給交換機下發指令。Switch就是一個實現Controller指令的實體,只不過這個交換機跟傳統的交換機不一樣,他的轉發規則由流表指定,而流表由控制器發送。
2.1 switch組成與傳統交換機的差異
switch組成
switch由一個Secure Channel和一個flow table組成,of1.3之后table變成多級流表,有256級。而of1.0中table只在table0中。
- Secure Channel是與控制器通信的模塊,switch和controller之間的連接時通過socket連接實現。
- Flow table里面存放這數據的轉發規則,是switch的交換轉發模塊。數據進入switch之后,在table中尋找對應的flow進行匹配,並執行相應的action,若無匹配的flow則產生packet_in(后面有講)
of中sw與傳統交換機的差異
- 匹配層次高達4層,可以匹配到端口,而傳統交換機只是2層的設備。
- 運行of協議,實現許多路由器的功能,比如組播。
- 求補充!!(如果你知道,請告訴我,非常感謝!)
OpenFlow的switch可以從以下方式獲得
- 實體of交換機,目前市場上有一些廠商已經制造出of交換機,但是普遍反映價格較貴!性能最好。
- 在實體機上安裝OVS,OVS可以使計算機變成一個OpenFlow交換機。性能相對穩定。
- 使用Mininet模擬環境。可以搭建許多交換機,任意拓撲,搭建拓撲具體教程本博客有一篇。性能依賴虛擬機的性能。
2.2 controller組成
控制器有許多種,不同的語言,如python寫的pox、ryu,如java寫的Floodlight等等。從功能層面controller分為以下幾個模塊:
- 底層通信模塊:OpenFlow中目前controller與switch之間使用的是socket連接,所以控制器底層的通信是socket。
- OpenFlow協議。socket收到的數據的處理規則需按照OpenFlow協議去處理。
- 上層應用:根據OpenFlow協議處理后的數據,開發上層應用,比如pox中就l2_learning、l3_learning等應用。更多的應用需要用戶自己去開發。
3 OpenFlow通信流程
以下教程環境為:Mininet+自編簡單控制器+scapy封裝
3.1 建立連接
首先啟動Mininet,Mininet會自行啟動一個default拓撲,你也可以自己建立你的拓撲。sw建立完成之后,會像controllerIP:controllerport發送數據。
controller啟動之后,監聽指定端口,默認6633,但是好像以后的都改了,因為該端口被其他協議占用。
3次握手之后,建立連接,這個是底層的通信,是整一套系統的基礎設施。
3.2 OFPT_HELLO
創建socket之后,sw跟controller會彼此發送hello數據包。
- 目的:協議協商。
- 內容:本方支持的最高版本的協議
- 成果:使用雙方都支持的最低版本協議。
- 成功:建立連接
- 失敗:OFPT_ERROR (TYPE:OFPT_HELLO_FAILED,CODE =0),終止連接。
3.3 OFPT_ERROR
說到OFPT_ERROR,我們不妨先了解一下。
1
2
3
4
5
6
|
ofp_error_type = { 0: "OFPET_HELLO_FAILED",
1: "OFPET_BAD_REQUEST",
2: "OFPET_BAD_ACTION",
3: "OFPET_FLOW_MOD_FAILED",
4: "OFPET_PORT_MOD_FAILED",
5: "OFPET_QUEUE_OP_FAILED"}
|
錯誤類型如上所示。對應的type還會有對應的code。所以報錯的格式為:
1
2
3
4
|
OFPT_ERROR
TYPE:
CODE:
[PAYLOAD]具體的錯誤信息。
|
如 TYPE:0 CODE:0為:OFPHFC_INCOMPATIBLE
具體對應的關系,請自行查看OF協議。
3.4 OFPT_ECHO
- 分類:對稱信息 OFPT_ECHO_REQUEST, OFPT_ECHO_REPLY
- 作用:查詢連接狀態,確保通信通暢。
當沒有其他的數據包進行交換時,controller會定期循環給sw發送OFPT_ECHO_REQUEST。
3.5 OFPT_FEATURES
當sw跟controller完成連接之后,控制器會向交換機下發OFPT_FEATYRES_REQUEST的數據包,目的是請求交換機的信息。
- 發送時間:連接建立完成之后
- 發送數據:OFPT_FEATURES_REQUEST
- 對稱數據:OFPT_FEATURES_REPLY
- 目的:獲取交換機的信息
OFPT_FEATURES_REQUEST
- TYPE=5
- Without data
OFPT_FEATURES_REPLY
- TYPE =6
- [0:8]為header
- [8:32]長度24byte為sw的features
- [32:]長度與端口數成正比,存放port的信息。每一個port信息長度為48byte。
12345678910111213141516171819class ofp_features_reply(Packet):name = "OpenFlow Switch Features Reply"fields_desc=[ BitFieldLenField('datapath_id', None, 64, length_of='varfield'),BitFieldLenField('n_buffers', None, 32, length_of='varfield'),XByteField("n_tables", 0),X3BytesField("pad", 0),#featuresBitField("NOT DEFINED", 0, 24),BitField("OFPC_ARP_MATCH_IP", 0, 1), #1<<7 Match IP address in ARP packetsBitField("OFPC_QUEUE_STATS", 0, 1), #1<<6 Queue statisticsBitField("OFPC_IP_STREAM", 0, 1), #1<<5 Can reassemble IP fragmentsBitField("OFPC_RESERVED", 0, 1), #1<<4 Reserved, must be zeroBitField("OFPC_STP", 0, 1), #1<<3 802.1d spanning treeBitField("OFPC_PORT_STATS", 0, 1), #1<<2 Port statisticsBitField("OFPC_TABLE_STATS", 0, 1), #1<<1 Table statisticsBitField("OFPC_FLOW_STATS", 0, 1), #1<<0 Flow statisticsBitFieldLenField('actions', None, 32, length_of='varfield'),]bind_layers( ofp_header, ofp_features_reply, type=6 )
以上的結構是交換機的features,緊跟在后面的是端口的結構:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
class ofp_phy_port(Packet):
name = "OpenFlow Port"
fields_desc=[ ShortEnumField("port_no", 0, ofp_port),
MACField("hw_addr", "00:00:00:00:00:00"),
StrFixedLenField("port_name", None, length=16),
BitField("not_defined", 0, 25),
BitField("OFPPC_NO_PACKET_IN", 0, 1),
BitField("OFPPC_NO_FWD", 0, 1),
BitField("OFPPC_NO_FLOOD", 0, 1),
BitField("OFPPC_NO_RECV_STP",0, 1),
BitField("OFPPC_NO_RECV", 0, 1),
BitField("OFPPC_NO_STP", 0, 1),
BitField("OFPPC_PORT_DOWN", 0, 1),
#uint32_t for state
BitField("else", 0, 31),
BitField("OFPPS_LINK_DOWN", 0, 1),
#uint32_t for Current features
BitField("not_defined", 0, 20),
BitField("OFPPF_PAUSE_ASYM", 0, 1),
BitField("OFPPF_PAUSE", 0, 1),
BitField("OFPPF_AUTONEG", 0, 1),
BitField("OFPPF_FIBER", 0, 1),
BitField("OFPPF_COPPER", 0, 1),
BitField("OFPPF_10GB_FD", 0, 1),
BitField("OFPPF_1GB_FD", 0, 1),
BitField("OFPPF_1GB_HD", 0, 1),
BitField("OFPPF_100MB_FD", 0, 1),
BitField("OFPPF_100MB_HD", 0, 1),
BitField("OFPPF_10MB_FD", 0, 1),
BitField("OFPPF_10MB_HD", 0, 1),
#uint32_t for features being advised by the port
BitField("advertised", 0, 32),
#uint32_t for features supported by the port
BitField("supported", 0, 32),
#uint32_t for features advertised by peer
BitField("peer", 0, 32)]
|
交換機和端口的配置信息在整一個通信過程起着至關的作用,因為所有關於的操作都需要從features里面提取相關的信息,如dpid、port_no,等在整個通信過程中多次被用到的重要數據。所以,對這兩個數據結構了然於心,對於研究OpenFlow來說,至關重要。每一次交換機連到控制器,都會收到控制器的features_request,當sw將自己的features回復給控制器之后,控制器就對交換機有了一個全面的了解,從而為后面的控制提供的控制信息。
3.6 OFPT_PACKET_IN
在控制器獲取完交換機的特性之后,交換機開始處理數據。
對於進入交換機而沒有匹配流表,不知道如何操作的數據包,交換機會將其封裝在packet_in中發給controller。包含在packet_in中的數據可能是很多種類型,arp和icmp是最常見的類型。
當然產生packet_in的原因不止一種,產生packet_in的原因主要有一下兩種:
- OFPR_NO_MATCH
- OFPR_ACTION
無法匹配的數據包會產生packet_in,action也可以指定將數據包發給packet_in,也就是說我們可以利用這一點,將需要的數據包發給控制器。
packet_in事件之后,一般會觸發兩類事件:
- packet_out
- flow_mod
如果是廣播包,如arp,控制器一般會將其包裝起來,封裝成packet_out數據包,將其發給交換機,讓其flood,flood操作是將數據包往除去in_port以外的所有端口發送數據包。
3.7 OFPT_PACKET_OUT
很多人不是特別了解packet_out的作用。
- 作用:通過控制器發送交換機希望發送的數據
- 例子:arp在廣播的時候,在ofsw中不能直接將arp廣播,需要上報控制器,控制器下發packet_out,指定交換機對該數據包做泛洪操作。
PS:前一個版本此處有誤:特此聲明。packet_out的作用在與指導交換機做flood操作,並把ARP包廣播出去,packet_out本身並不攜帶arp數據包。
3.8 OFPT_FLOW_MOD
OFPT_FLOW_MOD是整個OpenFlow協議中最重要的數據結構,沒有之一。
OFPT_FLOW_MOD由header+match+flow_mod+action[]組成。為了操作簡單,以下的結構是將wildcards和match分開的形式,形成兩個結構,在編程的時候能更方便一些。由於這個數據包很重要,所以,我將把這個數據包仔細拆分解讀。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
flow_mod = of.ofp_header(type=14,length=72)/of.ofp_flow_wildcards(OFPFW_NW_TOS=1,
OFPFW_DL_VLAN_PCP=1,
OFPFW_NW_DST_MASK=0,
OFPFW_NW_SRC_MASK=0,
OFPFW_TP_DST=1,
OFPFW_TP_SRC=1,
OFPFW_NW_PROTO=1,
OFPFW_DL_TYPE=1,
OFPFW_DL_VLAN=1,
OFPFW_IN_PORT=1,
OFPFW_DL_DST=1,
OFPFW_DL_SRC=1)\
/of.ofp_match(in_port=msg.payload.payload.payload.in_port,
dl_src=pkt_parsed.src,
dl_dst=pkt_parsed.dst,
dl_type=pkt_parsed.type,
dl_vlan=pkt_parsed.payload.vlan,
nw_tos=pkt_parsed.payload.tos,
nw_proto=pkt_parsed.payload.proto,
nw_src=pkt_parsed.payload.src,
nw_dst=pkt_parsed.payload.dst,
tp_src = 0,
tp_dst = 0)\
/of.ofp_flow_mod(cookie=0,
command=0,
idle_timeout=10,
hard_timeout=30,
out_port=msg.payload.payload.payload.payload.port,
buffer_id=buffer_id,
flags=1)
|
OFP_HEADER
header是所有數據包的報頭,有三個參數:
- type:類型
- length:整個數據包的長度
- xid:數據包的編號
比如ofp_flow_mod的type就是14,具體的哪一種數據的類型將在文章最后給出。length最基本長度為72,每一個action長度為8。所以長度必定為8的倍數才是一個正確的數據長度。
WILDCARDS
這是從match域提取出來的前32bit。
在of1.0中這里的0,1意義跟我們平時接觸的如子網掩碼等意義相反,如OFPFW_NW_DST_MASK=0則表示全匹配目標IP。如果為63,則表示不匹配IP。為什么拿這個舉例?原因就在於,他的長度是6bit,最大是63,需要將數值轉變成對應2進制數值才是我們想要的匹配規則,且注意,1是忽略,0是匹配。如果wildcards全0,則表示由match精確指定,即所有12元組都匹配。
當然高興的是,在1.3的時候,這個邏輯改成了正常的與邏輯。即1為使能匹配,0為默認不匹配。
MATCH
這個數據結構會出現在機會所有重要的數據包中,因為他存的就是控制信息。
如有packet_in引發的下發流表,則match部分應對應填上對應的數據,這樣下發的流表才是正確的。
但是在下發的時候還需要注意許多細節,比如:
- 並不是所有的數據包都有vlan_tag。如0x0800就是純IP,並沒有攜帶vlan_tag,所以填充式應根據packet_in的具體情況填充。
- 並不是所有的數據都有四層端口,所以四層的源端口,目的端口都不是任何時候都能由packet_in去填充的。不去管就好了,默認的會填充一個默認值,匹配的時候不去匹配4層端口就沒有問題。
FLOW_MOD
這里面的信息也是至關重要的。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class ofp_flow_mod(Packet):
name = "OpenFlow Flow Modify"
fields_desc=[ BitField("cookie", 0, 64), #Opaque controller-issued identifier
ShortEnumField("command", 0, ofp_flow_mod_command),
ShortField("idle_timeout", 60),
ShortField("hard_timeout", 0),
ShortField("priority", 0),
IntField("buffer_id", 0),
ShortField("out_port", 0),
#flags are important, the 1<<0 bit is OFPFF_SEND_FLOW_REM, send OFPT_FLOW_REMOVED
#1<<1 bit is OFPFF_CHECK_OVERLAP, checking if the entries' field overlaps(among same priority)
#1<<2 bit is OFPFF_EMERG, used only switch disconnected with controller)
ShortField("flags", 0)]
|
command里面的類型決定了flow_mod的操作是添加,修改還是刪除等。類型如下
1
2
3
4
5
|
ofp_flow_mod_command = { 0: "OFPFC_ADD", # New flow
1: "OFPFC_MODIFY", # Modify all matching flows
2: "OFPFC_MODIFY_STRICT", # Modify entry strictly matching wildcards
3: "OFPFC_DELETE", # Delete all matching flows
4: "OFPFC_DELETE_STRICT"} # Strictly match wildcards and priority
|
例如:如果要添加一條新流,command=0。
兩個時間參數idle_timeout & idle_timeout:
- idle_timeout:如值為10,則某條流在10秒之內沒有被匹配,則刪除,可以稱之為活躍時間吧。
- hard_timeout:如值為30,則30秒到達的時候,一定刪除這條流,即使他還活躍,即被匹配。
priority
priority是流的優先級的字段,字數越大則優先級越高,存放在號數越小的table中。
buffer_id
由交換機指定的buffei_id,准確的說是由dpid指定的。如果是手動下發的流,buffer_id應填-1,即0xffff,告訴交換機這個數據包並沒有緩存在隊列中。
out_port
指定流的出口,但是這個出口並不是直接指導流轉發的,至少我是這么覺得,指導流轉發的出口會在action里面添加,這個端口是為了在flow_removed的時候查詢,並返回控制器的作用。(求糾正!)
有一些端口是很特殊的,如flood、local等。具體分類如下:
1
2
3
4
5
6
7
8
9
|
ofp_port = { 0xff00: "OFPP_MAX",
0xfff8: "OFPP_IN_PORT",
0xfff9: "OFPP_TABLE",
0xfffa: "OFPP_NORMAL",
0xfffb: "OFPP_FLOOD",
0xfffc: "OFPP_ALL",
0xfffd: "OFPP_CONTROLLER",
0xfffe: "OFPP_LOCAL",
0xffff: "OFPP_NONE"}
|
如果你不知道端口是多少,最好填flood,也就是0xfffb。
flags
在上面的注釋中也說得比較清楚了。如果沒有特殊用處,請將他置1,因為這樣能讓交換機在刪除一條流的時候給交換機上報flow_removed信息。
3.9 ACTION
action是OpenFlow里面最重要的結構。對,他也是最重要的。每一條流都必須指定必要的action,不然匹配上之后,沒有指定action,交換機會默認執行drop操作。
action有2種類型:
- 必備行動:Forward and Drop
- 選擇行動:FLOOD、NALMAL 等
如添加output就是一個必須要添加的action。每一個action最好有一個action_header(),然后再接一個實體。如:
1
|
ofp_action_header(type=0)/ofp_action_output(type =0, port =oxfffb,len =8)
|
具體的action類型如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
ofp_action_type = { 0: "OFPAT_OUTPUT",
1: "OFPAT_SET_VLAN_VID",
2: "OFPAT_SET_VLAN_PCP",
3: "OFPAT_STRIP_VLAN",
4: "OFPAT_SET_DL_SRC",
5: "OFPAT_SET_DL_DST",
6: "OFPAT_SET_NW_SRC",
7: "OFPAT_SET_NW_DST",
8: "OFPAT_SET_NW_TOS",
9: "OFPAT_SET_TP_SRC",
10: "OFPAT_SET_TP_DST",
11: "OFPAT_ENQUEUE"
}
|
action不僅僅會出現在flow_mod中,也會出現在如stats_reply中。
3.10 OFPT_BARRIER_REQUEST && REPLY
這個數據包可以的作用很簡單,交換機在收到OFPT_BARRIER_REQUEST的時候,會回復控制器一個OFPT_BARRIER_REPLY。我們默認數據下發的順序不會在傳輸中發生變化,在進入消息隊列之后處理也是按照FIFO進行的,那么只要在flow_mod之后發送這個數據,當收到reply之后,交換機默認flow已經寫成功。也許你會問他只是保證了flow_mod命令執行了,寫入的結果如何並沒有保證,如何確定確實寫入流表了呢?
- 如果非邏輯錯誤,那么交換機在處理flow_mod的時候會報錯。所以我們會知道寫入結果。
- 如果是邏輯錯誤,那么會寫進去,但是邏輯錯誤應該是人的問題,所以barrier還是有他的功能的。
3.11 OFPT_FLOW_REMOVED
如果flow_mod的flags填成1,則該流在失效之后會回復控制器一條OFPT_FLOW_REMOVED信息。
- 結構:header()/wildcards()/match()/flow_removed()
- 作用:在流失效的時候回復控制器,並攜帶若干統計數據。
1234567891011121314class ofp_flow_removed(Packet):name = "OpenFlow flow removed"fields_desc = [ BitField("cookie", 0, 64),BitField("priority", 0,16),BitField("reason", 0, 8),ByteField("pad", None),BitField("duration_sec", 0, 32),BitField("duration_nsec", 0, 32),BitField("idle_timeout", 0, 16),ByteField("pad", 0),ByteField("pad", 0),BitField("packet_count", 0, 64),BitField("byte_count", 0, 64)]
其實的duration_sec是流存在的時間,單位為秒,duration_nsec單位為納秒。
3.12 OFPT_STATS_REQUEST && REPLY
以上的數據都是通信過程中必須的部分。還有一些數據包是為了某些目的而設計的,如OFPT_STATS_REQUEST && REPLY可以獲得統計信息,我們可以利用統計信息做的事情就太多了。如:負載平衡, 流量監控等基於流量的操作。
OFPT_STATS_REQUEST
OFPT_STATS_REQUEST類型有很多,回復的類型也很多。
1
2
3
4
|
class ofp_stats_request(Packet):
name = "OpenFlow Stats Request"
fields_desc=[ ShortEnumField("type", 0, ofp_stats_types),
ShortField("flag", 0)]
|
Type
- 0:請求交換機版本信息,制造商家等信息。
- 1:單流請求信息
- 2:多流請求信息
- 3:流表請求信息
- 4:端口信息請求
- 5:隊列請求信息
- 6:vendor請求信息,有時候沒有定義。
1
2
3
4
5
6
7
8
|
msg = { 0: of.ofp_header(type = 16, length = 12)/of.ofp_stats_request(type = 0), #Type of OFPST_DESC (0)
1: of.ofp_header(type = 16, length = 56)/of.ofp_stats_request(type =1)/ofp_flow_wildcards/ofp_match/of.ofp_flow_stats_request(out_port = ofp_flow_mod.out_port), #flow stats
2: of.ofp_header(type = 16, length =56)/of.ofp_stats_request(type = 2)/ofp_flow_wildcards/of.ofp_match/of.ofp_aggregate_stats_request(), # aggregate stats request
3: of.ofp_header(type = 16, length = 12)/of.ofp_stats_request(type = 3), #Type of OFPST_TABLE (0)
4: of.ofp_header(type = 16, length =20)/of.ofp_stats_request(type = 4)/of.ofp_port_stats_request(port_no = port), # port stats request
5: of.ofp_header(type = 16, length =20)/of.ofp_stats_request(type =5)/of.ofp_queue_stats_request(), #queue request
6: of.ofp_header(type = 16, length = 12)/of.ofp_stats_request(type = 0xffff) #vendor request
}
|
OFPT_STATS_REPLY
每一種請求信息都會對應一種回復信息。我們只介紹最重要的flow_stats_reply。
- 結構:header(type=17)/reply_header()/flow_stats/wildcards/match/ flow_stats_data
- 作用:攜帶流的統計信息,如通過的數據包個數,字節數。
ofp_flow_stats(body[4:8])里面會有的table_id字段表明該流存放在哪一個流表里。flow_stats_data里面有packet_count和byte_count是最有價值的字段,流量統計就是由這兩個字段提供的信息。如想統計某條流的速率:前后兩個reply的字節數相減除以duration_time只差就可以求得速率。由速率我們可以做很多基於流量的app,如流量監控,負載均衡等等。
值得注意的是,在這些數據之后,其實還有一些action,但是目前我還沒有查看這些action到底是干什么用的。
4 后續
寫到這里,我使用到的數據包都寫了一遍,其他的報文其實道理也是一樣的。如OFPT_GET_CONFIG_REQUEST和REPLY,道理應該和stats一樣,只是數據結構不一樣罷了。不再多說。
最后把我們用的一些比較多的信息帖出來讓大家更好的學習。
ERROR
在調試的過程中遇到錯誤是再所難免的,前面也提到了error的結構。這里就貼一下type跟code吧。
Type
1
2
3
4
5
6
|
ofp_error_type = { 0: "OFPET_HELLO_FAILED",
1: "OFPET_BAD_REQUEST",
2: "OFPET_BAD_ACTION",
3: "OFPET_FLOW_MOD_FAILED",
4: "OFPET_PORT_MOD_FAILED",
5: "OFPET_QUEUE_OP_FAILED"}
|
相關的code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
ofp_hello_failed_code = { 0: "OFPHFC_INCOMPATIBLE",
1: "OFPHFC_EPERM"}
ofp_bad_request_code = { 0: "OFPBRC_BAD_VERSION",
1: "OFPBRC_BAD_TYPE",
2: "OFPBRC_BAD_STAT",
3: "OFPBRC_BAD_VENDOR",
4: "OFPBRC_BAD_SUBTYPE",
5: "OFPBRC_EPERM",
6: "OFPBRC_BAD_LEN",
7: "OFPBRC_BUFFER_EMPTY",
8: "OFPBRC_BUFFER_UNKNOWN"}
ofp_bad_action_code = { 0: "OFPBAC_BAD_TYPE",
1: "OFPBAC_BAD_LEN",
2: "OFPBAC_BAD_VENDOR",
3: "OFPBAC_BAD_VENDOR_TYPE",
4: "OFPBAC_BAD_OUT_PORT",
6: "OFPBAC_BAD_ARGUMENT",
7: "OFPBAC_EPERM", #permissions error
8: "OFPBAC_TOOMANY",
9: "OFPBAC_BAD_QUEUE"}
ofp_flow_mod_failed_code = { 0: "OFPFMFC_ALL_TABLES_FULL",
1: "OFPFMFC_OVERLAP",
2: "OFPFMFC_EPERM",
3: "OFPFMFC_BAD_EMERG_TIMEOUT",
4: "OFPFMFC_BAD_COMMAND",
5: "OFPFMFC_UNSUPPORT"}
ofp_port_mod_failed_code = { 0: "OFPPMFC_BAD_PORT",
1: "OFPPFMC_BAD_HW_ADDR"}
ofp_queue_op_failed_code = { 0: "OFPQOFC_BAD_PORT",
1: "OFPQOFC_BAD_QUEUE"}
|
謝謝我的兩個師傅richardzhao,kimi帶我走進OpenFlow的世界。整篇文檔均為牧紫星原創,轉載請聲明告知。希望能給你帶來一些幫助。