設置 OVS 交換機和虛擬接口
# 啓動 OVS 服務
sudo service openvswitch-switch start
# 創建一個 OVS 交換機
sudo ovs-vsctl add-br br0
# 創建兩個 veth 對
sudo ip link add veth1 type veth peer name ovs-veth1
sudo ip link add veth2 type veth peer name ovs-veth2
# 將一端 veth 接口添加到 OVS 交換機
sudo ovs-vsctl add-port br0 ovs-veth1
sudo ovs-vsctl add-port br0 ovs-veth2
# 啓動接口
sudo ip link set ovs-veth1 up
sudo ip link set ovs-veth2 up
# 設置 OVS 控制器
sudo ovs-vsctl set-controller br0 tcp:127.0.0.1:6633
創建網絡命名空間並移動接口
# 創建兩個網絡命名空間
sudo ip netns add ns1
sudo ip netns add ns2
# 將 veth1 移動到 ns1 命名空間
sudo ip link set veth1 netns ns1
# 將 veth2 移動到 ns2 命名空間
sudo ip link set veth2 netns ns2
# 在 ns1 中啓動 veth1 接口並分配 IP 地址
sudo ip netns exec ns1 ip addr add 10.0.0.1/24 dev veth1
sudo ip netns exec ns1 ip link set veth1 up
# 在 ns2 中啓動 veth2 接口並分配 IP 地址
sudo ip netns exec ns2 ip addr add 10.0.0.2/24 dev veth2
sudo ip netns exec ns2 ip link set veth2 up
運行 Ryu 控制器
ryu-manager simple_switch.py
測試網絡連接
# 從 ns1 中 ping ns2 中的 veth2
sudo ip netns exec ns1 ping 10.0.0.2
驗證流表
你可以使用 ovs-ofctl 工具查看 OVS 交換機的流表,驗證 Ryu 控制器是否成功安裝了流表項。
# 查看 OVS 交換機的流表
sudo ovs-ofctl dump-flows br0
應該看到類似以下的輸出,表示兩個接口之間的連接正常,並且流量通過 OVS 交換機:
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.033 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.031 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=0.032 ms
simple_switch.py 它的工作原理更接近於傳統的以太網交換機,通過學習和存儲 MAC 地址到端口的映射來轉發數據包。因此,它的行為更像是一個 MAC 地址表(也稱為轉發表),而不是路由表。
# simple_switch.py
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
class SimpleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(SimpleSwitch13, self).__init__(*args, **kwargs)
# 用於存儲 MAC 地址到端口的映射
self.mac_to_port = {}
# 處理交換機特性消息的事件處理器
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# 安裝一個默認的 table-miss 流表項(即匹配不到其他流表項時的默認動作)
match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions)
# 添加流表項的方法
def add_flow(self, datapath, priority, match, actions, buffer_id=None):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# 創建指令,這裏是應用指定的動作
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)]
if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match, instructions=inst)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
# 發送流表項到交換機
datapath.send_msg(mod)
# 處理 Packet-In 消息的事件處理器
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
in_port = msg.match['in_port']
# 解析接收到的數據包
pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
dst = eth.dst
src = eth.src
dpid = datapath.id
self.mac_to_port.setdefault(dpid, {})
# 學習源 MAC 地址到端口的映射
self.mac_to_port[dpid][src] = in_port
# 如果目的 MAC 地址在我們的映射表中,獲取相應的端口
if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
else:
# 否則,泛洪數據包
out_port = ofproto.OFPP_FLOOD
actions = [parser.OFPActionOutput(out_port)]
# 如果目的端口不是泛洪,安裝一個流表項以避免下次再觸發 Packet-In
if out_port != ofproto.OFPP_FLOOD:
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
# 如果有有效的 buffer_id,避免發送流表項和 Packet-Out
if msg.buffer_id != ofproto.OFP_NO_BUFFER:
self.add_flow(datapath, 1, match, actions, msg.buffer_id)
return
else:
self.add_flow(datapath, 1, match, actions)
# 發送 Packet-Out 消息
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
in_port=in_port, actions=actions, data=msg.data)
datapath.send_msg(out)
simple_switch.py 解釋:
@set_ev_cls 是 Ryu 控制器框架中的一個裝飾器,用於將特定的事件處理函數與事件類型和狀態機狀態關聯起來
- EventOFPSwitchFeatures: 交換機首次連接到控制器時,控制器會接收到這個消息。可以在此事件中安裝默認的流表項。
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# 安裝一個默認的 table-miss 流表項(即匹配不到其他流表項時的默認動作)
match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions)
- EventOFPPacketIn: 當交換機接收到一個數據包並將其發送到控制器時,會觸發這個事件。可以在此事件中處理數據包並決定轉發路徑。
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
in_port = msg.match['in_port']
......
- EventOFPPortStatus: 當交換機端口狀態發生變化(如端口啓用/禁用)時,會觸發這個事件。可以在此事件中更新端口狀態信息。
@set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
def port_status_handler(self, ev):
# 處理端口狀態消息
EventOFPFlowRemoved: 當流表項過期或被刪除時,會觸發這個事件。可以在此事件中進行清理操作或統計流量信息。
@set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER)
def flow_removed_handler(self, ev):
# 處理流表項移除消息
EventOFPEchoRequest: 用於保持控制器與交換機之間的連接。可以在此事件中回覆 Echo Reply。
@set_ev_cls(ofp_event.EventOFPEchoRequest, MAIN_DISPATCHER)
def echo_request_handler(self, ev):
# 處理 Echo Request 消息
CONFIG_DISPATCHER: 通常在交換機剛連接到控制器時,用於處理交換機特性消息(Switch Features)。
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
# 處理交換機特性消息
MAIN_DISPATCHER: 用於處理大多數 OpenFlow 消息,如 Packet-In、Port Status 等。
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
# 處理 Packet-In 消息
DEAD_DISPATCHER: 用於處理連接斷開或交換機失效的情況。
@set_ev_cls(ofp_event.EventOFPStateChange, DEAD_DISPATCHER)
def state_change_handler(self, ev):
# 處理交換機狀態變化消息
ev的數據結構:
ev 對象的主要屬性包括:
msg: 包含了 OpenFlow 消息的詳細信息。
datapath: 表示與交換機的連接。
version: OpenFlow 版本。
msg_type: 消息類型。
msg_len: 消息長度。
xid: 事務 ID。
n_buffers: 交換機的緩衝區數量。
n_tables: 交換機的流表數量。
capabilities: 交換機的能力標誌。
ports: 交換機的端口信息。
datapath 是 Ryu 控制器中表示與 OpenFlow 交換機連接的對象。它封裝了與交換機通信所需的各種信息和方法。datapath 對象在處理 OpenFlow 消息時非常重要,因為它提供了發送消息到交換機的接口。
datapath:主要屬性:
id: 交換機的唯一標識符(通常是交換機的 datapath ID)
address: 交換機的 IP 地址和端口號
ofproto: 表示 OpenFlow 協議的常量和枚舉值
ofproto_parser: 用於解析和生成 OpenFlow 消息的解析器
msg: 最近接收到的 OpenFlow 消息
ports: 交換機的端口信息
datapath:主要方法:
send_msg: 發送 OpenFlow 消息到交換機 參數:消息對象
匹配條件:
//創建一個空的匹配條件對象。
match = parser.OFPMatch()
//可以通過調用其方法來設置具體的匹配字段
match.set_ipv4_src("10.0.0.1")
match.set_tcp_dst(80)
執行動作:
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)]
- parser.OFPActionOutput: 這是一個用於創建輸出動作的類。輸出動作指定數據包應該被髮送到哪個端口。
- ofproto.OFPP_CONTROLLER: 這是一個常量,表示控制器端口。將數據包發送到控制器端口意味着數據包將被轉發到控制器進行處理。
- ofproto.OFPCML_NO_BUFFER: 這是一個常量,表示不使用緩衝區。數據包將被完整地發送到控制器,而不是隻發送數據包的一部分。
優先級:
在 OpenFlow 協議中,流表項的優先級是一個 16 位整數,範圍從 0 到 65535(即 0x0000 到 0xFFFF)。優先級用於決定當多個流表項匹配同一個數據包時,應該應用哪個流表項。優先級值越高,流表項的優先級越高,越優先匹配。
def add_flow(self, datapath, priority, match, actions, buffer_id=None):
pass
OFPInstructionActions 是 OpenFlow 協議中的一個類,用於表示一種指令(Instruction),該指令指定一組動作(Actions)在匹配條件滿足時應該被執行。
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)]
OFPFlowMod 是 OpenFlow 協議中的一個類,用於創建和發送流表項修改消息(Flow Modification Message)。這種消息用於在交換機中添加、修改或刪除流表項。
class OFPFlowMod:
def __init__(self, datapath, cookie=0, cookie_mask=0, table_id=0, command=OFPFC_ADD,
idle_timeout=0, hard_timeout=0, priority=0, buffer_id=OFP_NO_BUFFER,
out_port=OFPP_ANY, out_group=OFPG_ANY, flags=0, match=None, instructions=[]):
# 參數初始化
參數解釋
- datapath:
類型: Datapath
説明: 表示連接到交換機的通道。通過這個參數,控制器可以知道消息應該發送到哪個交換機。 - cookie:
類型: int
説明: 用户定義的標識符,用於標識流表項。可以用於統計和管理。 - cookie_mask:
類型: int
説明: 用於掩碼匹配的 cookie 值。通常在修改或刪除流表項時使用。 - table_id:
類型: int
説明: 流表的 ID,指定流表項應該添加到哪個流表中。默認值為 0。 - command:
類型: int
説明: 指定流表項的操作類型。常見的操作類型包括添加(OFPFC_ADD)、修改(OFPFC_MODIFY)和刪除(OFPFC_DELETE)。 - idle_timeout:
類型: int
説明: 流表項的空閒超時時間(秒)。如果流表項在指定時間內沒有匹配任何數據包,則會被刪除。默認值為 0,表示沒有空閒超時。 - hard_timeout:
類型: int
説明: 流表項的硬超時時間(秒)。無論是否有數據包匹配,流表項在指定時間後都會被刪除。默認值為 0,表示沒有硬超時。 - priority:
類型: int
説明: 流表項的優先級。優先級越高,流表項越優先匹配。範圍從 0 到 65535。 - buffer_id:
類型: int
説明: 數據包的緩衝區 ID。如果數據包存儲在交換機的緩衝區中,可以使用這個 ID 直接引用數據包。默認值為 OFP_NO_BUFFER,表示沒有緩衝區。 - out_port:
類型: int
説明: 指定流表項的有效輸出端口。用於刪除流表項時,指定需要刪除的流表項的輸出端口。默認值為 OFPP_ANY,表示任意端口。 - out_group:
類型: int
説明: 指定流表項的有效輸出組。用於刪除流表項時,指定需要刪除的流表項的輸出組。默認值為 OFPG_ANY,表示任意組。 - flags:
類型: int
説明: 流表項的標誌位。可以設置一些特殊的標誌,例如 OFPFF_SEND_FLOW_REM 表示當流表項被刪除時發送通知。 - match:
類型: OFPMatch
説明: 流表項的匹配條件。定義哪些數據包應該匹配這個流表項。 - instructions:
類型: list
説明: 流表項的指令列表。定義當數據包匹配流表項時應該執行的動作。
mac_to_port 數據結構:
mac_to_port
├── 1 (DPID)
│ ├── 00:11:22:33:44:55 -> 1 (Port)
│ └── 66:77:88:99:aa:bb -> 2 (Port)
└── 2 (DPID)
├── aa:bb:cc:dd:ee:ff -> 3 (Port)
└── 11:22:33:44:55:66 -> 4 (Port)
圖示的實際應用
- 學習階段:
當交換機 1 接收到來自端口 1 的數據包,其源 MAC 地址為 00:11:22:33:44:55,交換機會將這個 MAC 地址和端口的映射關係存儲到 mac_to_port 中,即 mac_to_port1 = 1。
類似地,當交換機 2 接收到來自端口 3 的數據包,其源 MAC 地址為 aa:bb:cc:dd:ee:ff,交換機會將這個 MAC 地址和端口的映射關係存儲到 mac_to_port 中,即 mac_to_port2 = 3。 - 轉發階段:
當交換機 1 接收到一個目的 MAC 地址為 66:77:88:99:aa:bb 的數據包時,會查找 mac_to_port1,找到對應的端口 2,並將數據包轉發到端口 2。
如果交換機 2 接收到一個目的 MAC 地址為 11:22:33:44:55:66 的數據包時,會查找 mac_to_port2,找到對應的端口 4,並將數據包轉發到端口 4。