SDN上机实验
实验目的
-
- 能够使用Mininet的实现网络拓扑构建;
-
- 熟悉Open vSwitch交换机的基本配置;
-
- 熟悉OpenFlow协议的通信原理
-
- 掌握pox控制器的基本使用方法;
-
- 掌握Ryu控制期的基本使用方法;
-
- 掌握北向应用的基本开发方法
实验环境
基础环境选择ubuntu-20.04.6-desktop-amd64
实验内容
- 任务1
-
- 展示构建拓扑所用到的Python代码和Mininet命令,以及VLAN配置后的主机连通性测试结果;
-
- 写出四个问题的答案;
-
- 任务2
-
- POX关键代码、交换机下发的流表项以及连通性测试结果;
-
- RYU关键源码、交换机下发的流表项以及连通性测试结果;
-
- 任务3
-
- 程序源代码和相关配置文件内容直接粘贴至报告中
-
- 截图展示运行结果
-
- 任务4
-
- 提交完整系统设计的图文说明,粘贴关键代码和相关配置文件内容,每段代码不超过一页,截图展
示最终的系统运行结果;
- 提交完整系统设计的图文说明,粘贴关键代码和相关配置文件内容,每段代码不超过一页,截图展
-
- 系统源代码项目文件,ZIP格式,作为报告附件;
-
- 系统的视频介绍,MP4格式,编码需为H264,5分钟以内,大小不超过500M,作为报告附件。
-
实验操作
任务一
-
熟悉OpenFlow协议,对照OpenFlow源码,了解OpenFlow主要消息类型对应的数据结构定义,回答以下问题:
-
交换机与控制器建立通信时是使用TCP协议还是UDP协议?
交换机与控制器之间建立的通信是使用 TCP 协议。
-
OpenFlow控制器默认用哪个端口号和交换机通信?
66333端口
-
Packet_IN消息由通信的哪一端发出,产生该消息的原因有哪些?
Packet-in消息由OpenFlow交换机发出并发送到OpenFlow控制器
产生Packet_IN 消息的原因包括:
\(~~~\)交换机收到一个数据包后,会查找流表,如果流表中没有匹配条目,则交换机会将数据包封装在Packet-in消息中发送给控制器处理.
\(~~~\)数据包被标记为需要被转发到控制器的类型.
\(~~~\)控制器或交换机的流表不是最新的,或者没有针对特定数据包的匹配项.-
Flow_Mod和Packet_OUT的区别是什么
-
\(~~~\)Flow_Mod:这是一种用于修改流表项的消息。它可以用于添加、修改或删除流表中的流项。通常由控制器发出,以期望控制和管理数据包流转的方式。Flow_Mod 还可以包含与流匹配条件相关的动作,例如转发到特定端口、丢弃等。
\(~~~\)Packet_OUT:这是一种用于直接在交换机的数据平面上执行操作的消息。Packet_OUT 消息允许控制器向交换机发送一个具体的数据包并指定如何处理该数据包(例如,发送到特定端口)。主要用于实现更复杂的流量管理和即时响应。
topo_ovsvlan.py
from mininet.topo import Topo
from mininet.net import Mininet
from mininet.node import OVSSwitch, Controller, RemoteController, Host
from mininet.cli import CLI
from mininet.log import setLogLevel, info
from mininet.link import TCLinkclass MyTopo(Topo):"Simple topology example."def build(self):# Create switchess1 = self.addSwitch('s1', dpid='00:00:00:00:00:00:00:01',protocols='OpenFlow13')s2 = self.addSwitch('s2', dpid='00:00:00:00:00:00:00:02',protocols='OpenFlow13')# Create hostsh1 = self.addHost('h1', ip='192.168.0.101/24')h2 = self.addHost('h2', ip='192.168.0.102/24')h3 = self.addHost('h3', ip='192.168.0.103/24')h4 = self.addHost('h4', ip='192.168.0.104/24')h5 = self.addHost('h5', ip='192.168.0.105/24')h6 = self.addHost('h6', ip='192.168.0.106/24')# Add links with specific bandwidth and delay settingslinkopts0 = dict(bw=300, delay='1ms', loss=0)linkopts1 = dict(bw=100, delay='1ms', loss=0)self.addLink(h1, s1, cls=TCLink, **linkopts1)self.addLink(h2, s1, cls=TCLink, **linkopts1)self.addLink(h3, s1, cls=TCLink, **linkopts1)self.addLink(h4, s2, cls=TCLink, **linkopts1)self.addLink(h5, s2, cls=TCLink, **linkopts1)self.addLink(h6, s2, cls=TCLink, **linkopts1)self.addLink(s1, s2, cls=TCLink, **linkopts0)
def startMyNet():"Create network and run CLI"topo = MyTopo()net = Mininet(topo=topo, switch=OVSSwitch, controller=None,autoSetMacs=True, autoStaticArp=True)c0 = net.addController('c0', controller=Controller)net.start()# Add OpenFlow rules to the switchess1 = net.get('s1')s2 = net.get('s2')# Rules for s1s1.cmd('ovs-ofctl -O OpenFlow13 del-flows s1')s1.cmd('ovs-ofctl -O OpenFlow13 add-flow s1 priority=100,in_port=1,actions=push_vlan:0x8100,set_field:4096-\>vlan_vid,output:4')s1.cmd('ovs-ofctl -O OpenFlow13 add-flow s1 priority=100,in_port=2,actions=push_vlan:0x8100,set_field:4097-\>vlan_vid,output:4')s1.cmd('ovs-ofctl -O OpenFlow13 add-flow s1 priority=100,in_port=3,actions=push_vlan:0x8100,set_field:4098-\>vlan_vid,output:4')s1.cmd('ovs-ofctl -O OpenFlow13 add-flow s1 priority=100,dl_vlan=0,actions=pop_vlan,output:1')s1.cmd('ovs-ofctl -O OpenFlow13 add-flow s1 priority=100,dl_vlan=1,actions=pop_vlan,output:2')s1.cmd('ovs-ofctl -O OpenFlow13 add-flow s1 priority=100,dl_vlan=2,actions=pop_vlan,output:3')# Rules for s2s2.cmd('ovs-ofctl -O OpenFlow13 del-flows s2')s2.cmd('ovs-ofctl -O OpenFlow13 add-flow s2 priority=100,in_port=1,actions=push_vlan:0x8100,set_field:4096-\>vlan_vid,output:4')s2.cmd('ovs-ofctl -O OpenFlow13 add-flow s2 priority=100,in_port=2,actions=push_vlan:0x8100,set_field:4097-\>vlan_vid,output:4')s2.cmd('ovs-ofctl -O OpenFlow13 add-flow s2 priority=100,in_port=3,actions=push_vlan:0x8100,set_field:4098-\>vlan_vid,output:4')s2.cmd('ovs-ofctl -O OpenFlow13 add-flow s2 priority=100,dl_vlan=0,actions=pop_vlan,output:1')s2.cmd('ovs-ofctl -O OpenFlow13 add-flow s2 priority=100,dl_vlan=1,actions=pop_vlan,output:2')s2.cmd('ovs-ofctl -O OpenFlow13 add-flow s2 priority=100,dl_vlan=2,actions=pop_vlan,output:3')CLI(net)net.stop()
if __name__ == '__main__':setLogLevel('info')startMyNet()
遇到困难和解决方法
\(~~\)在使用Mininet创建拓扑和配置VLAN时,最初我在配置交换机和主机的VLAN ID时出现了错误。这导致主机之间的连通性未能如预期实现。例如,h1与h4之间无法互通。为了解决这个问题,我认真查阅了Mininet和Open vSwitch的相关文档,确保在使用ovs-vsctl命令时正确指定VLAN ID。同时,我借助ovs-ofctl show命令检查了交换机的配置。经过几次尝试后,我最终成功配置了VLAN,确保了 h1 - h4、h2 - h5 和 h3 - h6 的互通性,解决了连通性的问题。
个人感想和总结
\(~~\)这一任务让我对Mininet和Open vSwitch的操作有了更深入的理解。在实际配置VLAN时,我体会到了网络配置的重要性以及对细节的关注。做好每一步配置不仅是实现网络互通的基础,也是后续任务顺利展开的保障。这次实验强化了我的实践能力,我期待在在后续的学习中进一步掌握复杂的网络环境配置。
任务二
连接pox控制器
由于pox仅支持openflow 1.0,所以需要用到另一个openflow10拓扑
from mininet.topo import Topo
from mininet.net import Mininet
from mininet.node import OVSSwitch, Controller, RemoteController, Host
from mininet.cli import CLI
from mininet.log import setLogLevel, info
from mininet.link import TCLinkclass MyTopo(Topo):"Simple topology example."def build(self):# Create switchess1 = self.addSwitch('s1', dpid='00:00:00:00:00:00:00:01',protocols='OpenFlow10')
s2 = self.addSwitch('s2', dpid='00:00:00:00:00:00:00:02',protocols='OpenFlow10')# Create hostsh1 = self.addHost('h1', ip='192.168.0.101/24')h2 = self.addHost('h2', ip='192.168.0.102/24')h3 = self.addHost('h3', ip='192.168.0.103/24')h4 = self.addHost('h4', ip='192.168.0.104/24')h5 = self.addHost('h5', ip='192.168.0.105/24')h6 = self.addHost('h6', ip='192.168.0.106/24')# Add links with specific bandwidth and delay settingslinkopts0 = dict(bw=300, delay='1ms', loss=0)linkopts1 = dict(bw=100, delay='1ms', loss=0)self.addLink(h1, s1, cls=TCLink, **linkopts1)self.addLink(h2, s1, cls=TCLink, **linkopts1)self.addLink(h3, s1, cls=TCLink, **linkopts1)self.addLink(h4, s2, cls=TCLink, **linkopts1)self.addLink(h5, s2, cls=TCLink, **linkopts1)self.addLink(h6, s2, cls=TCLink, **linkopts1)self.addLink(s1, s2, cls=TCLink, **linkopts0)def startMyNet():"Create network and run CLI"topo = MyTopo()net = Mininet(topo=topo, switch=OVSSwitch, controller=None,autoSetMacs=True, autoStaticArp=True)# Add a remote controllerc0 = net.addController('c0', controller=RemoteController, ip='127.0.0.1',port=6633)net.start()# Add OpenFlow rules to the switches and any other configurations neededCLI(net)net.stop()if __name__ == '__main__':
setLogLevel('info')
poxcontroller.py
from pox.core import core
import pox.openflow.libopenflow_01 as of
from pox.lib.util import dpidToStrlog = core.getLogger()def _handle_ConnectionUp(event):dpid = event.connection.dpidsw = dpidToStr(dpid)log.info("Switch %s has connected.", sw)if sw == '00-00-00-00-00-01': # s1configure_switch(event.connection, is_s1=True)elif sw == '00-00-00-00-00-02': # s2configure_switch(event.connection, is_s1=False)else:log.warning("Unknown switch connected: %s", sw)def configure_switch(conn, is_s1):if is_s1:host_pairs = [("192.168.0.101", "192.168.0.104", 1),("192.168.0.102", "192.168.0.105", 2),("192.168.0.103", "192.168.0.106", 3)]else:host_pairs = [("192.168.0.104", "192.168.0.101", 1),("192.168.0.105", "192.168.0.102", 2),("192.168.0.106", "192.168.0.103", 3)]for src_ip, dst_ip, local_port in host_pairs:# 配置从源 IP 到目标 IP 的流表项msg = of.ofp_flow_mod()msg.priority = 1000msg.match.dl_type = 0x0800 # IPv4msg.match.nw_src = src_ipmsg.match.nw_dst = dst_ipmsg.actions.append(of.ofp_action_output(port=local_port)) # 使用local_port try:conn.send(msg)log.info("Flow mod sent: src_ip=%s, dst_ip=%s, action_port=%d", src_ip, dst_ip, local_port) except Exception as e:log.error("Failed to send flow mod: %s", e)# 配置从目标 IP 到源 IP 的流表项msg = of.ofp_flow_mod()msg.priority = 1000msg.match.dl_type = 0x0800 # IPv4msg.match.nw_src = dst_ipmsg.match.nw_dst = src_ipmsg.actions.append(of.ofp_action_output(port=local_port)) # 使用local_port try:conn.send(msg)log.info("Flow mod sent: src_ip=%s, dst_ip=%s, action_port=%d", dst_ip, src_ip, local_port) except Exception as e:log.error("Failed to send flow mod: %s", e)log.info("Configured IP-based flows for switch %s", "s1" if is_s1 else "s2") def launch():core.openflow.addListenerByName("ConnectionUp", _handle_ConnectionUp)log.info("POX IP-based communication module running.")
连接ryu控制器
ryucontroller.py
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER, set_ev_cls
from ryu.ofproto import ofproto_v1_0
from ryu.lib.packet import packet, ethernet, ipv4 class CustomSwitch(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION] def __init__(self, *args, **kwargs): super(CustomSwitch, self).__init__(*args, **kwargs) @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): msg = ev.msg datapath = msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser dpid = datapath.id self.logger.info("Packet in DPID: %s", dpid) # 打印 DPID pkt = packet.Packet(msg.data) eth = pkt.get_protocol(ethernet.ethernet) ip_pkt = pkt.get_protocol(ipv4.ipv4) if ip_pkt is None: return src_ip = ip_pkt.src dst_ip = ip_pkt.dst self.logger.info("Received packet: SRC IP: %s, DST IP: %s", src_ip, dst_ip) # 根据源 IP 和目的 IP 来决定流表规则 out_port = None if dpid == 1: # Switch 1 if src_ip == '192.168.0.101' and dst_ip == '192.168.0.104': # h1 -> h4 out_port = 4 elif src_ip == '192.168.0.104' and dst_ip == '192.168.0.101': # h4 -> h1 out_port = 1 elif src_ip == '192.168.0.102' and dst_ip == '192.168.0.105': # h2 -> h5 out_port = 4 elif src_ip == '192.168.0.105' and dst_ip == '192.168.0.102': # h5 -> h2 out_port = 2 elif src_ip == '192.168.0.103' and dst_ip == '192.168.0.106': # h3 -> h6 out_port = 4 elif src_ip == '192.168.0.106' and dst_ip == '192.168.0.103': # h6 -> h3 out_port = 3 elif dpid == 2: # Switch 2 if src_ip == '192.168.0.101' and dst_ip == '192.168.0.104': # h1 -> h4 out_port = 1 elif src_ip == '192.168.0.104' and dst_ip == '192.168.0.101': # h4 -> h1 out_port = 4 elif src_ip == '192.168.0.102' and dst_ip == '192.168.0.105': # h2 -> h5 out_port = 2 elif src_ip == '192.168.0.105' and dst_ip == '192.168.0.102': # h5 -> h2 out_port = 4 elif src_ip == '192.168.0.103' and dst_ip == '192.168.0.106': # h3 -> h6 out_port = 3 elif src_ip == '192.168.0.106' and dst_ip == '192.168.0.103': # h6 -> h3 out_port = 4 if out_port is not None: self.logger.info("Installing flow: DPID %s, SRC IP %s -> DST IP %s, OUTPORT %s", dpid, src_ip, dst_ip, out_port) actions = [parser.OFPActionOutput(out_port)] # 安装流表规则 match = parser.OFPMatch(dl_type=eth.ethertype, nw_src=src_ip, nw_dst=dst_ip) mod = parser.OFPFlowMod(datapath=datapath, priority=1, match=match, actions=actions) datapath.send_msg(mod) # 立即转发数据包 out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id, in_port=msg.in_port, actions=actions, data=msg.data) datapath.send_msg(out) else: self.logger.info("No matching flow found for DPID %s, SRC IP %s, DST IP %s", dpid, src_ip, dst_ip)
遇到困难和解决方法
\(~~\)在连接POX控制器时,流表项未能有效下发给交换机,导致主机之间无法正常通信。通过查看POX控制器的调试信息,我发现流表的优先级设置不合理,导致流量未能正确匹配。我调整了流表项的优先级,并并确保了匹配条件的准确性,成功通过控制器下发正确的流表项,实现了h1 - h4、h2 - h5和h3 - h6之间的互通。在切换到Ryu控制器时,我也遇到了相似的问题,但通过其强大的API文档,我快速找到了问题的根源,并顺利实现了想要的功能。
个人感想和总结
\(~~\)对比POX和Ryu控制器的使用,我意识到不同控制器在流表管理上的差异。Ryu控制器功能更为强大,适合进行更复杂的操作。这个任务让我更加熟悉控制器的工作机制,以及如何通过编程实现网络流量的管理和转发。实践中对流表的深入理解,大大增强了我在网络编程上的自信。
任务三
查看拓扑信息,包括主机、
交换机和链路,以及显示每台交换机的所有流表项
getxinxi.py
from mininet.net import Mininet
from mininet.node import RemoteController
from mininet.cli import CLI
from mininet.log import setLogLevel
import os def print_topology(MyTopo): "Prints the topology of the network including hosts, switches, and links." print("Network Topology:") print("Hosts:") for host in MyTopo.hosts: print(f" - {host.name}: {host.IP()}") print("Switches:") for switch in MyTopo.switches: print(f" - {switch.name}") print("Links:") for link in MyTopo.links: print(f" - {link}") def print_flow_tables(switches): "Prints the flow tables for each switch." for switch in switches: print(f"\nFlow table for switch {switch.name}:") flows = switch.cmd(f'ovs-ofctl dump-flows {switch.name}') print(flows) def add_flow(switch, match, actions): "Adds a flow rule to the switch." command = f'ovs-ofctl add-flow {switch.name} "{match}, {actions}"' switch.cmd(command) print(f"Added flow to {switch.name}: {match} -> {actions}") def delete_flow(switch, match): "Deletes a flow rule from the switch." command = f'ovs-ofctl del-flow {switch.name} "{match}"' switch.cmd(command) print(f"Deleted flow from {switch.name}: {match}") def main(): setLogLevel('info') # Connect to running Mininet instance MyTopo = Mininet(controller=RemoteController) MyTopo.start() # Print topology print_topology(MyTopo) # Print flow tables print_flow_tables(MyTopo.switches) # Example of adding and deleting flows add_flow(MyTopo.get('s1'), 'in_port=1,dl_type=0x0800,nw_dst=192.168.0.102', 'actions=output:2') delete_flow(MyTopo.get('s1'), 'in_port=1,dl_type=0x0800,nw_dst=192.168.0.102') # CLI for user interaction CLI(MyTopo) MyTopo.stop() if __name__ == '__main__': main()
遇到困难和解决方法
\(~~\)在编写Ryu的北向应用程序时,我遇到的主要问题是如何获取和展示拓扑信息。最开始尝试从Ryu的API中获取信息时,发现信息结构复杂,难以快速理解。为此,我参考了Ryu的示例代码和社区资源,逐步梳理出获取主机、交换机和链路信息的逻辑。通过实践,我成功实现了拓扑的可视化,同时也能够动态添加和删除流表项。这个过程让我深刻理解了如何利用Ryu提供的API进行流表管理。
个人感想和总结
\(~~\)这一任务不仅让我掌握了如何通过编程获取网络拓扑信息和流表项,还让我体会到了软件定义网络的灵活性和强大能力。通过实际的应用开发,我更加深刻地认识到,北向应用在SDN架构中的重要性。未来,我希望能继续探索网络编程,并将这一技术应用于更多的实际场景,尤其是在网络安全和负载均衡领域的应用。整体来说,这次任务让我对SDN有了更全面的认识,对我的学习旅程产生了积极的推动。