Ryu利用组表实现组播
组播是现代网络中非常重要的组成部分,当我们需要发送数据给多台主机的时候,如果采用单播的方式,我们需要发送多个数据包,而采用广播又会使得网络中的每个终端都必须接收数据,所以组播应运而生,组播的特点就是组播源只需要发送一次数据包,而且只有一组特定的主机会接收数据包,不想接收的主机是收不到的。
要利用Ryu实现组播,需要考虑以下几点:
1.获取,管理组播组成员的信息。
2.寻找组播源去往组播组成员的最佳路径。
在Ryu里已经提供了用于管理组成员和寻路的库ryu/lib/igmplib.py
本文将基于这个库来使用组表实现无环路拓扑的组播
igmplib
这个库集成了有关组的管理,组成员管理,流表的管理等等组播要考虑的绝大部分功能,实现组播的时候只需要把它作为一个线程启动即可,app/simple_switch_igmp_13.py就是这么一个例子。也正是因为组播的功能都在这里面实现,我们用组表实现组播关键就是改进这个库。
先简单介绍一下这个库里的6个类:
- EventPacketIn:
处理非IGMP的Packet-In报文的。
- EventMulticastGroupStateChanged
描述组状态改变的。
- IgmpLibIGMP
IGMP核心类,负责线程的启动,停止,交换机角色设定,处理IGMP的Packet-In报文等。
- IgmpBase
在IGMPv2里面交换机有两种角色:querier和snooper。这是他俩的基类,负责流表下发,删除等。
- IgmpQuerier
这是querier类。作为一个网络的组管理者,定期下发query消息,处理组成员的加入,删除,存储组成员和接口相关信息。
- IgmpSnooper
这是snooper类。协助querier进行组管理,收到query消息进行广播,处理组成员的加入,删除,有组成员离开时代表querier发送query消息进行查询,存储组成员和接口相关信息。
组管理
主机在加入组的时候会发送report消息,离开组的时候会发送leave消息。querier和snooper就是根据这两种消息来修改组的信息。
以querier为例,下面是它收到report消息进行的处理:
def _do_report(self, report, in_port, msg):
"""the process when the querier received a REPORT message."""
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
if ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
size = 65535
else:
size = ofproto.OFPCML_MAX
update = False
self._mcast.setdefault(report.address, {})
if in_port not in self._mcast[report.address]:
update = True
self._mcast[report.address][in_port] = True
if update:
group_id = self._ipv4_text_to_int(report.address)
actions = [parser.OFPActionGroup(group_id=group_id)]
self._set_flow_entry(datapath, actions, self.server_port, report.address)
buckets = []
for port in self._mcast[report.address]:
buckets.append(parser.OFPBucket(actions = [parser.OFPActionOutput(port)]))
if len(buckets)==1:
req = parser.OFPGroupMod(datapath, ofproto.OFPFC_ADD,ofproto.OFPGT_ALL, group_id, buckets)
else:
req = parser.OFPGroupMod(datapath, ofproto.OFPGC_MODIFY,ofproto.OFPGT_ALL, group_id, buckets)
datapath.send_msg(req)
self._set_flow_entry(
datapath,
[parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, size)],
in_port, report.address)
这里首先把接口加入到组里面,如果是第一次收到report消息就会下发流表项和组表项,流表项分为两种:把之后的report或者leave消息交给控制器处理,把匹配的组播流交给组表处理。
在收到leave消息之后:
def _do_leave(self, leave, in_port, msg):
"""the process when the querier received a LEAVE message."""
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
self._mcast.setdefault(leave.address, {})
if in_port in self._mcast[leave.address]:
group_id = self._ipv4_text_to_int(leave.address)
self._del_flow_entry(datapath, in_port, leave.address)
del self._mcast[leave.address][in_port]
buckets = []
for port in self._mcast[leave.address]:
buckets.append(parser.OFPBucket(actions = [parser.OFPActionOutput(port)]))
if len(buckets):
actions = [parser.OFPActionGroup(group_id=group_id)]
self._set_flow_entry(datapath, actions, self.server_port, leave.address)
req = parser.OFPGroupMod(datapath, ofproto.OFPGC_MODIFY,ofproto.OFPGT_ALL, group_id, buckets)
datapath.send_msg(req)
else:
self._del_flow_entry(datapath, self.server_port, leave.address)
req = parser.OFPGroupMod(datapath, ofproto.OFPGC_DELETE,ofproto.OFPGT_ALL, group_id)
datapath.send_msg(req)
因为不确定是不是还有组成员连接到这个交换机,所以对流表项和组表项的操作要看组成员接口,会删除或者修改相关接口的流表项,然后修改或者删除现有的组表项。
设置完对流表项的操作,这个库就算设置完了,我们需要一个app对其进行调用,可以直接使用Ryu提供的igmp_13,它继承自simple_switch_13,在这里面处理非IGMP报文,也可以自己编写一个具有交换功能的代码,初始化的时候创建igmplib的线程即可。不过在这里面也应该包括两个函数,来处理普通Packet-In事件和EventMulticastGroupStateChanged事件。
写完app就可以进行组播测试了。
源代码已经放至Github,具体使用方法在Readme,还不是很完善,后续会进行修改。
这里推荐使用vlc进行组播测试,它是一个播放器,可以模拟udp组播流。
在Ubuntu下安装命令
apt-get install vlc
即可安装,安装完毕输入vlc即可运行。
vlc有两种运行方法,命令行和图形化界面,具体使用方法这里不再赘述。
下面展示组播测试结果。
组成员接收组播视频流如图1所示,中间交换机的流表项如图2所示,图3是同一台交换机的组表项。
总结
原本Ryu是以流表项添加多个output实现的组播,我改为用组表实现,用组表可能会比流表更慢一些,因为流表只需要查找一次,组表要查找两次,仅仅是为了使用组表。实验效果可能因电脑配置而异,视频不流畅不一定是网络有环路,毕竟是在虚拟机播放视频,对硬件还有要求的。
这只是一个非常简单的无环组播,如果考虑环路的话还不仅仅是广播风暴,因为IGMP的report和leave报文也可能会到达同一交换机的不同接口,而无法确认这个报文到底是不是真正应该起作用,我会继续研究,如果我的内容有什么不对的地方,还请各位读者批评指正。
61234
ttttt