天天看點

Ryu控制器代碼解析-任意位址Ping應答

  在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()函數發出資料包。

  下面展示一下執行過程:

首先建立一個拓撲

一個交換機連接配接一個主機。

看一下這個主機的位址:

Ryu控制器代碼解析-任意位址Ping應答

再看一下主機路由:

Ryu控制器代碼解析-任意位址Ping應答

發現沒有網關,那這台主機隻能ping通同一網段的位址,要想ping通其他網段的位址,我們需要給他加一個預設路由。

Ryu控制器代碼解析-任意位址Ping應答

現在有了預設路由,我們可以開始實驗了。

運作Ryu檔案

先用主機ping一下同網段位址,這個位址是不存在的。

Ryu控制器代碼解析-任意位址Ping應答

通了。

為了友善,我們用wireshark抓包看一下。

Ryu控制器代碼解析-任意位址Ping應答

ARP請求也有回複,看一下MAC位址是我們之前定義的那個。

Ryu控制器端也有輸出:

Ryu控制器代碼解析-任意位址Ping應答

  再換一個不同一網段的位址,除了一開始需要ARP請求網關的MAC位址,其他的都一樣。大家可以自行實驗。

繼續閱讀