在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
一个交换机连接一个主机。
再看一下主机路由:
发现没有网关,那这台主机只能ping通同一网段的地址,要想ping通其他网段的地址,我们需要给他加一个默认路由。
ip route add default via 10.0.0.10
运行Ryu文件
ryu-manager ping.py
通了。
ARP请求也有回复,看一下MAC地址是我们之前定义的那个。
Ryu控制器端也有输出:
再换一个不同一网段的地址,除了一开始需要ARP请求网关的MAC地址,其他的都一样。大家可以自行实验。