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=0,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=0,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()函数发出数据包。

  下面展示一下执行过程:

首先创建一个拓扑

mn --topo linear,1  --mac --switch ovsk --controller remote

一个交换机连接一个主机。

看一下这个主机的地址:
主机IP

再看一下主机路由:
这里写图片描述
发现没有网关,那这台主机只能ping通同一网段的地址,要想ping通其他网段的地址,我们需要给他加一个默认路由。

ip route add default via 10.0.0.10

这里写图片描述
现在有了默认路由,我们可以开始实验了。

运行Ryu文件

ryu-manager ping.py

先用主机ping一下同网段地址,这个地址是不存在的。
这里写图片描述

通了。

为了方便,我们用wireshark抓包看一下。
这里写图片描述

ARP请求也有回复,看一下MAC地址是我们之前定义的那个。
Ryu控制器端也有输出:

Ryu控制器代码解析-任意地址Ping应答_第1张图片

  再换一个不同一网段的地址,除了一开始需要ARP请求网关的MAC地址,其他的都一样。大家可以自行实验。

你可能感兴趣的:(SDN-RYU控制器)