【致大学的自己——PoxDesk二次开发】优化Poxdesk拓扑图可视化效果,将主机+交换机图标化显示
备注:本人水平有限,文中内容可能存在许多错误,欢迎在评论区留言交流批评指正。
目录
【致大学的自己——PoxDesk二次开发】优化Poxdesk拓扑图可视化效果,将主机+交换机图标化显示
1 背景和意义
1.1 背景
读了三年研究生,从大四的2018年10月国庆节开始正式学习SDN网络,并将以SDN为方向做了大四的毕业设计,到现在的2021年5月,已有3年半了。记得当时还是使用FloodLight控制器,不过后来主要精力放在SDN网络拓扑发现这一块,考虑到我主要用C++,对java不是很熟悉,而且更重要的是Floodlight控制器算是一个体量比较大的控制器了,对拓扑发现过程进行了自己的改动,不是最原始、简单的拓扑发现了。
为了找一个使用最原始、纯粹的OFDP进行拓扑发现的控制器,后来注意到了POX控制器,POX控制器是Python语言开发的,体量很小,相比Floodlight、ODL、ONOS这些,算是老古董级的控制器了,但由于这个控制器是最原始的,所以内部的模块机制很容易看懂,二次开发也很友好。
由于我研究生的毕业设计主要是做网络拓扑发现的研究,结合自己的科研和毕业设计需要,就继续选择了在POX中实现自己的毕业设计。我个人觉得对于学习SDN网络的新人来说,POX是一个很好的入门级控制器,很适合初学者学习控制器的各种机制。
我主要关注控制器的拓扑发现功能,因此拓扑图信息是我经常要用到的一个功能。Pox控制器虽然可以通过安装poxdesk以图形化界面显示网络链路信息,但仅仅显示交换机之间的链路信息,且交换机都显示的是交换机ID编号而不是以图标显示,也不能显示主机,应对简单拓扑还好,但当拓扑图稍微复杂一点,整个界面都是交换机的编号挤在一起,根本看不清链路,就像下面的图一样。
1.2 现有Poxdesk存在的问题
现在是2021.5.19,近期正好闲着,虽然一直不喜欢网络,但是也算接触了近4年了,所以想对poxdesk进行二次开发,目前poxdesk的可视化拓扑图两个显著问题是:
问题1 不能显示主机设备、主机与交换机的链路关系
问题2 设备不是以图标显示,及其不美观且妨碍观察
1.3 开发目的和意义
开发这个的主要目的算是有两个吧。
(1) POX是一个很适合新手入门学习的SDN控制器,很适合进行一些学术研究类型的基础开发,拓扑图可视化对于观察研究和实验结果都是很实用的功能,相信如果能有一个良好的可视化拓扑,对SDN的一些研究者应该会有帮助;
(2) 临近毕业,学了近4年的SDN,本科和研究生学位都是通过研究SDN获得的,开发这个也算是自己为SDN做的一点微不足道的贡献吧,希望能帮助到后来者。
1.4 开发目标
暂定以下目标,优先实现前三个吧,后面的两个看具体时间安排:
(1)在Poxdesk的TopoViewer现有基础上进行改进,将交换机设备图标化显示;
(2)尝试在拓扑图中显示主机,并图标化显示
(3)通过点击设备,显示设备的MAC或IP等信息
(4)在web网页中添加链路信息表,标注单向/双向,图形化显示
(5)在web页面中添加设备信息表,图形化显示
2 实现过程
2.1 图标化显示交换机设备
2.1.1 开发思路
这个比较简单,毕竟现有拓扑中已经有交换机和边的信息了,理论上说只要把交换机的编号换成图标就行。找到绘图代码并进行相应修改就可以,不再赘述。
2.1.2 核心代码
绘图代码在springyui.js中,找到绘制交换机的function drawNode(node, p)函数进行修改即可。
2.1.2 实现效果
2.2 显示主机信息
2.2.1 问题分析
通过分析TopoViewer.js的代码,发现poxdesk获取拓扑信息的代码位置如下:
在浏览器看到的数据和对应拓扑如下:
通过分析上面data的内容,可以发现data回传的数据中只有交换机和交换机的链路信息,并不包含主机的设备信息。也就是说,我们要想在拓扑图中获取主机信息,需要自己动手,获取到主机设备信息。
进一步分析data数据的来源,定位到pox/ext/poxdesk/tinytopo.py文件,poxdesk正是通过tinytopo内的函数从core中获取数据信息。
对tinytopo.py的函数进行一个简单的分析:
- _handle_openflow_ConnectionUp 交换机注册
- _handle_openflow_ConnectionDown 交换机下线
- _handle_openflow_discovery_LinkEvent 链路更新事件发生
- _exec_cmd_refresh 刷新拓扑cmd命令
- send_table 刷新网络拓扑
- _all_dependencies_met 定期刷新,poxdesk中设置为10s
大概是这么个逻辑:
着重看一下_do_send_table函数:
ef _do_send_table (self):
assert self.pending
self.pending = False
switches = {}
for s in self.switches:
switches[s] = {'label':s}
edges = []
for e in self.links:
if e[0] not in switches: continue
if e[1] not in switches: continue
edges.append(e)
#print self.switches,switches
#print self.links,edges
self.send(topo={'links':edges,'switches':switches})
可以发现最后一行
self.send(topo={'links':edges,'switches':switches})
写的是发送拓扑中 交换机之间的链路edges信息 和 交换机switches信息 ,对比TopoViewer.js的data数据,可以看到data中有“links”和“switches”两个字段的内容,二者相对应,也应证了我们上面说法的合理性。
同时,可以发现发送的拓扑数据中不包含主机host信息,而这正是我们所需要的。
主机的发现需要借助POX中的host_tracker(主机追踪模块),关于该模块的介绍如下:
URL:https://openflow.stanford.edu/display/ONL/POX+Wiki#POXWiki-host_tracker
源码位置:pox/pox/host_tracker.py
host_tracker
该组件尝试跟踪网络中的主机–主机的位置以及配置方式(至少是其MAC / IP地址)。当事情发生变化时,组件将引发一个HostEvent。有关host_tracker用法的示例,请参见misc.gephi_topo组件。简而言之,host_tracker的工作方式是检查入站消息,并以此方式学习MAC和IP绑定。然后,我们定期对主机进行ARP ping操作,以查看它们是否仍然存在。请注意,这意味着它依赖于进入控制器的数据包,因此转发必须以相当被动的方式进行(与forwarding.l2_learning一样),否则您必须安装其他流条目才能将数据包带到控制器。您可以从命令行设置各种超时。名称和默认值:
arpAware
= 60 * 2此后,对ARP静默响应条目执行ping操作arpSilent
= 60 * 20这用于未知的ARP响应的uiet条目arpReply
= 4重试之前等待ARP答复的时间timerInterval
定时器例行程序激活之间= 5秒entryMove
= 60预期物理条目移动的最短时间良好的测试价值:--arpAware=15 --arpSilent=45 --arpReply=1 --entryMove=4
您还可以指定在决定失败之前我们尝试多少次ARP ping:--pingLim=2
对host_tracker做一个简单的实验,拓扑图如下:
当h1 ping h2,能ping通时,如下:
h1-h2不通时,如下:
输出的日志中,会出现4个mac地址,经过对比验证,如下:s1-eth2、s2-eth2、h2(10.0.0.2)、h1(10.0.0.1),也就是说连接主机的s1-eth1和s2-eth1的mac都没有收到,其实也符合逻辑,解释如下图,图中每个颜色对应每个端口发出的包。因为S1-eth1(紫色)和S2-eth1(浅蓝色)的包发出,但是没有被控制器收到,所以控制器无法从这里获取到这两个MAC信息。
h1 ping h2时的流表,分为icmp和arp包,如下:
mininet> dpctl dump-flows
*** s1 ------------------------------------------------------------------------
cookie=0x0, duration=5.264s, table=0, n_packets=1, n_bytes=42, idle_timeout=10, hard_timeout=30, priority=65535,arp,in_port="s1-eth2",vlan_tci=0x0000,dl_src=b6:2a:32:d6:f7:43,dl_dst=c2:43:8c:c2:60:ed,arp_spa=10.0.0.2,arp_tpa=10.0.0.1,arp_op=2 actions=output:"s1-eth1"
cookie=0x0, duration=0.072s, table=0, n_packets=1, n_bytes=42, idle_timeout=10, hard_timeout=30, priority=65535,arp,in_port="s1-eth2",vlan_tci=0x0000,dl_src=b6:2a:32:d6:f7:43,dl_dst=c2:43:8c:c2:60:ed,arp_spa=10.0.0.2,arp_tpa=10.0.0.1,arp_op=1 actions=output:"s1-eth1"
cookie=0x0, duration=0.070s, table=0, n_packets=1, n_bytes=42, idle_timeout=10, hard_timeout=30, priority=65535,arp,in_port="s1-eth1",vlan_tci=0x0000,dl_src=c2:43:8c:c2:60:ed,dl_dst=b6:2a:32:d6:f7:43,arp_spa=10.0.0.1,arp_tpa=10.0.0.2,arp_op=2 actions=output:"s1-eth2"
cookie=0x0, duration=5.262s, table=0, n_packets=1, n_bytes=98, idle_timeout=10, hard_timeout=30, priority=65535,icmp,in_port="s1-eth1",vlan_tci=0x0000,dl_src=c2:43:8c:c2:60:ed,dl_dst=b6:2a:32:d6:f7:43,nw_src=10.0.0.1,nw_dst=10.0.0.2,nw_tos=0,icmp_type=8,icmp_code=0 actions=output:"s1-eth2"
cookie=0x0, duration=5.254s, table=0, n_packets=1, n_bytes=98, idle_timeout=10, hard_timeout=30, priority=65535,icmp,in_port="s1-eth2",vlan_tci=0x0000,dl_src=b6:2a:32:d6:f7:43,dl_dst=c2:43:8c:c2:60:ed,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,icmp_type=0,icmp_code=0 actions=output:"s1-eth1"
cookie=0x0, duration=32.262s, table=0, n_packets=6, n_bytes=246, priority=65000,dl_dst=01:23:20:00:00:01,dl_type=0x88cc actions=CONTROLLER:65535
*** s2 ------------------------------------------------------------------------
cookie=0x0, duration=5.270s, table=0, n_packets=1, n_bytes=42, idle_timeout=10, hard_timeout=30, priority=65535,arp,in_port="s2-eth1",vlan_tci=0x0000,dl_src=b6:2a:32:d6:f7:43,dl_dst=c2:43:8c:c2:60:ed,arp_spa=10.0.0.2,arp_tpa=10.0.0.1,arp_op=2 actions=output:"s2-eth2"
cookie=0x0, duration=0.078s, table=0, n_packets=1, n_bytes=42, idle_timeout=10, hard_timeout=30, priority=65535,arp,in_port="s2-eth1",vlan_tci=0x0000,dl_src=b6:2a:32:d6:f7:43,dl_dst=c2:43:8c:c2:60:ed,arp_spa=10.0.0.2,arp_tpa=10.0.0.1,arp_op=1 actions=output:"s2-eth2"
cookie=0x0, duration=0.072s, table=0, n_packets=1, n_bytes=42, idle_timeout=10, hard_timeout=30, priority=65535,arp,in_port="s2-eth2",vlan_tci=0x0000,dl_src=c2:43:8c:c2:60:ed,dl_dst=b6:2a:32:d6:f7:43,arp_spa=10.0.0.1,arp_tpa=10.0.0.2,arp_op=2 actions=output:"s2-eth1"
cookie=0x0, duration=5.264s, table=0, n_packets=1, n_bytes=98, idle_timeout=10, hard_timeout=30, priority=65535,icmp,in_port="s2-eth2",vlan_tci=0x0000,dl_src=c2:43:8c:c2:60:ed,dl_dst=b6:2a:32:d6:f7:43,nw_src=10.0.0.1,nw_dst=10.0.0.2,nw_tos=0,icmp_type=8,icmp_code=0 actions=output:"s2-eth1"
cookie=0x0, duration=5.261s, table=0, n_packets=1, n_bytes=98, idle_timeout=10, hard_timeout=30, priority=65535,icmp,in_port="s2-eth1",vlan_tci=0x0000,dl_src=b6:2a:32:d6:f7:43,dl_dst=c2:43:8c:c2:60:ed,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,icmp_type=0,icmp_code=0 actions=output:"s2-eth2"
cookie=0x0, duration=32.264s, table=0, n_packets=7, n_bytes=287, priority=65000,dl_dst=01:23:20:00:00:01,dl_type=0x88cc actions=CONTROLLER:65535
结合源码,得出:
1. host_tracker有一张MAC表,通过数据包获取设备信息,ip和mac是分开的,以MAC地址作为设备唯一标识,
2. 当获取到IP后,会把IP绑定到MAC上,作为设备的一个属性
3.mac地址在哪个端口出现,控制器就会设为设备此前已出现过,那么它就会认为设备发生了移动,这也是主机位置伪造(又称主机位置劫持)攻击出现的原因。
问:为什么host_tracker会先获取到设备的MAC呢,而IP却要手动进行ping命令,才能获得呢?
答:因为主机和交换机连接网络之后,会向周围发出IPv6协议的组播/单播数据包,比如目的地址为:33:33:00:00:00:16、33:33:00:00:00:fb、33:33:00:00:00:02这类, 此前我用wireshark进行过实验,这里不再重复实验,可以通过自己抓包验证。因为这些包只有源MAC和目的MAC,所以控制器只能获取到MAC信息,而只有使用携带IP的数据包时,控制器才能获取到IP,并将数据包的IP和MAC与MAC表中的MAC信息进行绑定。
具体可以看这个博客 https://blog.csdn.net/yuhanfeng314/article/details/38151515
在IPv6组播地址中,有一种特别的组播地址,叫做Solicited-node地址(请求节点组播地址)。Solicited-node地址是一种特殊用途的地址.主要用于重复地址检测和地址解析。Solicited-node地址由前缀FF02::1:FF00:0/l04和单播地址的最后24位组成。一个单播地址对应一个Solicited-node地址。Solicited-node地址受限范围为本地链路范围::FF02:0:0:0:0:1:FFXX:XXXX,用于地址解析、DAD
和请求节点组播地址相近的还有一个概念:IPv6多播映射地址
用于NS报文中是33:33:ff:xx:yy:zz
用于RS报文中是33:33:00:xx:yy:zz
其中xx:yy:zz是所请求的IPv6地址的后24位,ARP报文用的是广播,这里用组播减少了对网络的影响。ipv6节点的每个接口有一个本地链路地址、一个回环地址,可以有一到多个分配的可聚合全球单播地址和相应的被请求节点多播地址。
————————————————
版权声明:本文为CSDN博主「yuhanfeng314」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yuhanfeng314/article/details/38151515
可能大家会产生一个疑问,明明只有h1和h2是主机,上面mac表有4个数据项,其中第1-2条不是主机信息,那么如何处理呢?其实继续阅读 host_tracker.py中的_handle_PacketIn函数 源码并实验可以发现,当交换机的链路生成后,S1-eth2和S2-eth2的端口会被占用,这时候会出现一些下面这样的信息,意思是:端口已经用于生成链路,不在当作设备端口。
通过上面的信息,我们可以理出一个大概的流程,如下:
- host_tracker把所有收到的mac都当作设备,其中包含主机的mac和交换机的端口mac,有h1、h2、s1-eh2、s2-eth2,共四个
- 当discovery模块,即链路生成模块开始进行链路发现后,会产生链路<s1-eh2,s2-eth2>,这时候这两个端口用于生成链路,并在链路信息表中,当再从这两个端口收到数据包时,比如收到一个来自S1-eth1的包,执行代码:
if not core.openflow_discovery.is_edge_port(dpid, inport): # No host should be right behind a switch-only port log.debug("%i %i ignoring packetIn at switch-only port", dpid, inport) return
由于S1的eth1端口已经是链路的一个端口,所以不会再认为该端口连接设备。
所以说,我们可以先获得保存所有mac设备信息的MAC_Table表,然后通过链路表中的端口信息,将连接在链路端口的mac从MAC_Table表中删除,这样就得到纯粹的主机设备信息了。
2.2.2 开发思路
分析完毕,总的来说,我们要实现本节在拓扑图中显示主机信息的目标,分为两步:
step 1:在tinytopo.py中收集【主机设备信息】和【主机与交换机的链路信息】,并发送到TopoViewer.js中
step 2:在绘图文件springyui.js中,对主机节点进行绘制
使用的拓扑图:
sudo mn --topo=tree,2,3 --controller=remote
2.2.3 主机显示收据获取步骤
(1)在TinyTopo类中构造一个主机设备信息表hosts,字典类型
def __init__ (self):
core.listen_to_dependencies(self, components=['MessengerNexus'])
self.switches = set()
self.links = set()
self.pending = False
self.hosts = {} # Dictionary type , by yongpu
(2)新增_handle_PacketIn函数,将检测到的mac加入到host_table,相当于做加法
# -----------------------------yongpu---------------------------------------start
def _handle_openflow_PacketIn (self, event):
dpid = event.connection.dpid
inport = event.port
packet = event.parse()
if not packet.parsed:
log.warning("%i %i ignoring unparsed packet", dpid, inport)
return
if packet.type == ethernet.LLDP_TYPE: # Ignore LLDP packets
return
if not core.openflow_discovery.is_edge_port(dpid, inport):
log.debug("%i %i ignoring packetIn at switch-only port", dpid, inport)
return
log.debug("PacketIn: %i %i ETH %s => %s", dpid, inport, str(packet.src), str(packet.dst))
macEntry = self.hosts.get(packet.src)
if macEntry == None:
macEntry = host_tracker.MacEntry(dpid, inport, packet.src)
self.hosts[packet.src] = macEntry
log.info("Learned %s", str(macEntry))
elif macEntry != (dpid, inport, packet.src):
log.info("Learned %s moved to %i %i", str(macEntry), dpid, inport)
self.hosts[packet.src].dpid = dpid
self.hosts[packet.src].port = inport
log.info(self.hosts[packet.src])
macEntry.refresh()
return
# -----------------------------yongpu---------------------------------------end
(3)在_handle_openflow_discovery_LinkEvent函数中将链路节点上的mac剔除掉,相当于做减法
def _handle_openflow_discovery_LinkEvent (self, event):
#print "LE"
s1 = event.link.dpid1
s2 = event.link.dpid2
if s1 > s2: s1,s2 = s2,s1
s1 = dpidToStr(s1)
s2 = dpidToStr(s2)
if event.added:
self.links.add((s1,s2))
elif event.removed and (s1,s2) in self.links:
self.links.remove((s1,s2))
# by yongpu
if event.added:
log.debug(str("Add Link: ") + str(event.link) + str("----------------------------"))
# remove mac_info from MacEntry if the port of mac_info in link
rm_mac_list_1 = []
rm_mac_list_2 = []
for macEntry in self.hosts.values():
log.debug(macEntry)
if self.hosts.get(macEntry.macaddr) and macEntry.dpid == event.link.dpid1 and macEntry.port == event.link.port1:
log.debug("wait to remove " + str(macEntry.macaddr))
rm_mac_list_1.append(macEntry.macaddr)
if self.hosts.get(macEntry.macaddr) and macEntry.dpid == event.link.dpid2 and macEntry.port == event.link.port2:
log.debug("wait to remove " + str(macEntry.macaddr))
rm_mac_list_1.append(macEntry.macaddr)
for mac in rm_mac_list_1:
log.debug(str("remove ") + str(mac))
del self.hosts[mac]
for mac in rm_mac_list_2:
log.debug(str("remove ") + str(mac))
del self.hosts[mac]
self.send_table()
(4)在_do_send_table函数中,将主机数据发出去
def _do_send_table (self):
assert self.pending
self.pending = False
switches = {}
for s in self.switches:
switches[s] = {'label':s}
edges = []
for e in self.links:
if e[0] not in switches: continue
if e[1] not in switches: continue
edges.append(e)
# send hosts data -- by yongpu
hosts = {}
for macEntry in self.hosts.values():
log.debug("add a mac-------------")
log.debug(macEntry)
hosts[str(macEntry.macaddr)] = host_tracker.MacEntry(macEntry.dpid, macEntry.port, macEntry.macaddr)
hosts[str(macEntry.macaddr)].dpid = dpidToStr(macEntry.dpid)
#print self.switches,switches
#print self.links,edges
# self.send(topo={'links':edges,'switches':switches})
self.send(topo={'links':edges,'switches':switches,'hosts':hosts}) # yongpu
(5)成功获取Host数据信息(注:暂时还未将主机数据绘制到界面中)
(6)将主机数据绘图
未完待续....................................