在SDN中最重要的就是控制器,控制器的代碼決定了整個網絡的特點,Ryu提供了很多協定的資料包代碼,我們可以根據這些代碼對網絡中的資料包進行修改,達到我們想要的效果。
Ping是我們經常用到的一個指令,我們用它來檢測網絡連通性,如果收到了目标IP的應答消息,我們就認為Ping成功了。
據此我們可以編寫一個程式,來實作主機不管ping什麼位址,我們都可以給它應答。
下面先放代碼:
import sys
sys.path.append("/home/manminglei/ryu")
from ryu.lib.packet import *
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
class IcmpResponder(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(IcmpResponder, self).__init__(*args, **kwargs)
self.hw_addr = '66:66:66:66:66:66'
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def _switch_features_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
actions = [parser.OFPActionOutput(port=ofproto.OFPP_CONTROLLER,max_len=ofproto.OFPCML_NO_BUFFER)]
inst = [parser.OFPInstructionActions(type_=ofproto.OFPIT_APPLY_ACTIONS,actions=actions)]
mod = parser.OFPFlowMod(datapath=datapath,priority=,match=parser.OFPMatch(),instructions=inst)
datapath.send_msg(mod)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
port = msg.match['in_port']
pkt = packet.Packet(data=msg.data)
self.logger.info("--------------------")
self.logger.info("Receive Packet-in from %d",datapath.id)
pkt_ethernet = pkt.get_protocol(ethernet.ethernet)
if not pkt_ethernet:
return
pkt_arp = pkt.get_protocol(arp.arp)
if pkt_arp:
self._handle_arp(datapath, port, pkt_ethernet, pkt_arp)
return
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
pkt_icmp = pkt.get_protocol(icmp.icmp)
if pkt_icmp:
self._handle_icmp(datapath, port, pkt_ethernet, pkt_ipv4, pkt_icmp)
return
def _handle_arp(self, datapath, port, pkt_ethernet, pkt_arp):
if pkt_arp.opcode != arp.ARP_REQUEST:
return
pkt = packet.Packet()
pkt.add_protocol(ethernet.ethernet(ethertype=pkt_ethernet.ethertype,dst=pkt_ethernet.src,src=self.hw_addr))
pkt.add_protocol(arp.arp(opcode=arp.ARP_REPLY,src_mac=self.hw_addr,src_ip=pkt_arp.dst_ip,dst_mac=pkt_arp.src_mac,dst_ip=pkt_arp.src_ip))
self.logger.info("Receive ARP_REQUEST,request IP is %s",pkt_arp.dst_ip)
self._send_packet(datapath, port, pkt)
def _handle_icmp(self, datapath, port, pkt_ethernet, pkt_ipv4, pkt_icmp):
if pkt_icmp.type != icmp.ICMP_ECHO_REQUEST:
return
pkt = packet.Packet()
pkt.add_protocol(ethernet.ethernet(ethertype=pkt_ethernet.ethertype,dst=pkt_ethernet.src,src=self.hw_addr))
pkt.add_protocol(ipv4.ipv4(dst=pkt_ipv4.src,src=pkt_ipv4.dst,proto=pkt_ipv4.proto))
pkt.add_protocol(icmp.icmp(type_=icmp.ICMP_ECHO_REPLY,code=icmp.ICMP_ECHO_REPLY_CODE,csum=,data=pkt_icmp.data))
self.logger.info("Receive ICMP_ECHO_REQUEST,request IP is %s",pkt_ipv4.dst)
self._send_packet(datapath, port, pkt)
def _send_packet(self, datapath, port, pkt):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
pkt.serialize()
if pkt.get_protocol(icmp.icmp):
self.logger.info("Send ICMP_ECHO_REPLY")
if pkt.get_protocol(arp.arp):
self.logger.info("Send ARP_REPLY")
self.logger.info("--------------------")
data = pkt.data
actions = [parser.OFPActionOutput(port=port)]
out = parser.OFPPacketOut(datapath=datapath,buffer_id=ofproto.OFP_NO_BUFFER,in_port=ofproto.OFPP_CONTROLLER,actions=actions,data=data)
datapath.send_msg(out)
這裡我所有的代碼都沒有分行,大家可以複制到notepad++或者别的編輯器裡檢視。
下面開始解析代碼:
1. 引用部分:
這裡我仍然是在外部建立的代碼,然後把目錄設定為Ryu的目錄。
我們這裡需要解析資料包,是以我們引入了Ryu的包庫,是lib/packet中的内容。
這是一個獨立的Ryu程式,不需要繼承其他之前編寫的程式,是以我們隻引入了APP的基類app_manager。
2. 初始化
初始化這裡我預先定義了一個MAC位址用來響應主機的資料包。
3. 下發預設流表
在交換機和控制器建立連接配接之後,因為在這裡交換機還無法響應主機的資料包,是以給交換機下發一個Table-miss流表項,告訴交換機以後收到的資料包都交給控制器處理。
port=ofproto.OFPP_CONTROLLER訓示交換機傳出接口是連接配接控制器的接口。
max_len=ofproto.OFPCML_NO_BUFFER這裡訓示交換機不緩存資料包,把資料包封裝一下發給控制器。
OFPIT_APPLY_ACTIONS表明立刻執行action。
函數OFPFlowMod專門用于下發流表,其中流表的優先級priority設定為0,意為最後比對;比對域OFPMatch()是空的,表示比對所有。
函數send_msg用來發出流表項。
4. 處理Packet-In消息。
主機發出資料包,交換機讀取之後發現不知道向哪裡發送,這時候由于Table-miss的存在,它會向控制器轉發這個資料包,控制器将這個資料包解析,進行回應。
在擷取并判斷完資料包的類型之後,我們就可以進行操作了。
比如,如果這個資料包是ARP的請求,我們就可以對其進行回複,如果這是一個ICMP請求,我們也可以對其進行回複。
處理ARP請求和ICMP請求的函數分别是_handle_arp和_handle_icmp
5. 處理ARP請求
pkt = packet.Packet()
因為我們的處理是回複這個請求,是以我們要構造一個資料包發回去,首先我們生成一個資料包對象。
pkt.add_protocol()
然後向裡面逐層添加協定,把一些必要的參數設定好,要注意的是,我們的分析過程在處理Packet-In消息的時候已經完成了。我們現在擷取到了源資料包的資訊,現在是根據這些資訊構造回複的資料包,特定的參數要設定正确。
以太網類型是預設的,目的MAC位址是源資料包的源MAC位址,源MAC位址是我們預先定義的MAC位址。
ARP消息的類型是REPLY,源IP位址是源資料包請求的,目的IP是源資料包的源IP位址。
最後調用發送函數發出這個資料包。
6. 處理ICMP請求
參照之前的ARP請求處理方法,這裡不再贅述。
7. 發送資料包
構造一個Packet-Out資料包發送到交換機,之前沒有緩存資料包,這裡也要指明不是緩存的資料包。從交換機的OFPP_CONTROLLER接口發到交換機。
最後用send_msg()函數發出資料包。
下面展示一下執行過程:
首先建立一個拓撲
一個交換機連接配接一個主機。
看一下這個主機的位址:
再看一下主機路由:
發現沒有網關,那這台主機隻能ping通同一網段的位址,要想ping通其他網段的位址,我們需要給他加一個預設路由。
現在有了預設路由,我們可以開始實驗了。
運作Ryu檔案
先用主機ping一下同網段位址,這個位址是不存在的。
通了。
為了友善,我們用wireshark抓包看一下。
ARP請求也有回複,看一下MAC位址是我們之前定義的那個。
Ryu控制器端也有輸出:
再換一個不同一網段的位址,除了一開始需要ARP請求網關的MAC位址,其他的都一樣。大家可以自行實驗。