SDN控制器拓扑发现(一)
参考链接:http://www.sdnlab.com/15425.html
SDN LAB3 — Ryu train
https://medium.com/@kweisamx0322/sdn-lab3-ryu-train-f8fe13b03548
OpenFlow协议的SDN控制器通过LLDP(Link Layer Discovery Protocol,链路发现协议)协议进行链路发现,并根据收集的链路信息来识别网络结构,生成网络拓扑。
LLDP协议
LLDP协议为二层协议,通过在本地子网中通告自己的设备标识以及相关接口信息来实现链路发现的功能,其基于Ethernet II格式封装的帧格式如下:
DMAC:目的MAC地址,为固定组播地址,0x0180-C200-000E
SMAC:源MAC地址,为端口MAC或设备MAC地址
Ether_Type:以太网类型,为0x88CC
Classis_ID TLV:用于描述设备信息
Port_ID TLV:用于描述发送的端口信息
TTL TLV:用于描述生存时间信息
END of LLDPDU TLV作为LLDPDU结尾
FCS:为帧校验序列
-
ryu.controller.handler.HANDSHAKE_DISPATCHER: 交换hello消息 ryu.controller.handler.CONFIG_DISPATCHER: openflow 版本协商,传送 SwitchFeatures 消息 ryu.controller.handler.MAIN_DISPATCHER: 接收 SwitchFeatures 消息,传送 set-config message ryu.controller.handler.DEAD_DISPATCHER: 連線中斷
# Send the lldp packet def send_lldp_packet(self, datapath, port, hw_addr, ttl): ofproto = datapath.ofproto ofp_parser = datapath.ofproto_parser pkt = packet.Packet() pkt.add_protocol(ethernet.ethernet(ethertype=ether_types.ETH_TYPE_LLDP,src=hw_addr ,dst=lldp.LLDP_MAC_NEAREST_BRIDGE)) chassis_id = lldp.ChassisID(subtype=lldp.ChassisID.SUB_LOCALLY_ASSIGNED, chassis_id=str(datapath.id).encode('utf-8')) #port_id = lldp.PortID(subtype=lldp.PortID.SUB_LOCALLY_ASSIGNED, port_id=str(port)) port_id = lldp.PortID(subtype=lldp.PortID.SUB_LOCALLY_ASSIGNED, port_id=str(port).encode('utf-8')) #port_id = lldp.PortID(subtype=lldp.PortID.SUB_LOCALLY_ASSIGNED, port_id=b'1/3') ttl = lldp.TTL(ttl=1) end = lldp.End() tlvs = (chassis_id,port_id,ttl,end) pkt.add_protocol(lldp.lldp(tlvs)) pkt.serialize() self.logger.info("packet-out %s" % pkt) data = pkt.data actions = [ofp_parser.OFPActionOutput(port=port)] out = ofp_parser.OFPPacketOut(datapath=datapath, buffer_id=ofproto.OFP_NO_BUFFER, in_port=ofproto.OFPP_CONTROLLER, actions=actions, data=data) datapath.send_msg(out)
@set_ev_cls(EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): """Handle switch features reply to install table miss flow entries.""" datapath = ev.msg.datapath self._register(datapath) self.install_table_miss(datapath, 0) def install_table_miss(self, datapath, table_id): """Create and install table miss flow entries.""" parser = datapath.ofproto_parser ofproto = datapath.ofproto match = parser.OFPMatch() match.set_dl_type(ETH_TYPE_LLDP) output = parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, self.LLDP_PACKET_LEN) write = parser.OFPInstructionActions(ofproto.OFPIT_WRITE_ACTIONS, [output]) instructions = [write] flow_mod = self.create_flow_mod(datapath, 0, table_id, match, instructions) datapath.send_msg(flow_mod) self.send_barrier_request(datapath)
@set_ev_cls(EventOFPBarrierReply, MAIN_DISPATCHER) def barrier_reply_handler(self, ev): msg = ev.msg datapath = msg.datapath self.send_port_desc_stats_request(datapath) def send_lldp_packet(self, datapath, port_no, dl_addr): ofproto = datapath.ofproto parser = datapath.ofproto_parser lldp_data = LLDPPacket.lldp_packet(datapath.id, port_no, dl_addr, self.DEFAULT_TTL) output_port = parser.OFPActionOutput(port_no, ofproto.OFPCML_NO_BUFFER) packet_out = parser.OFPPacketOut(datapath, ofproto.OFPP_ANY, ofproto.OFPP_CONTROLLER, [output_port], lldp_data) datapath.send_msg(packet_out) @set_ev_cls(EventOFPPortDescStatsReply, MAIN_DISPATCHER) def port_desc_stats_reply_handler(self, ev): msg = ev.msg datapath = msg.datapath self.show_port_desc(msg.body, datapath) def show_port_desc(self, body, datapath): self.port_state[datapath.id] = PortState() for p in body: self.port_state[datapath.id].add(p.port_no, p) self.send_lldp_packet(datapath, p.port_no, haddr_to_str(p.hw_addr)) port = self._get_port(datapath.id, p.port_no) if port and not port.is_reserved(): self._port_added(port) self.lldp_event.set() def send_port_desc_stats_request(self, datapath): ofp_parser = datapath.ofproto_parser req = ofp_parser.OFPPortDescStatsRequest(datapath, 0) datapath.send_msg(req)
from ryu.base import app_manager from ryu.ofproto import ofproto_v1_3 from ryu.controller.handler import set_ev_cls from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.lib.packet import ether_types,lldp,packet,ethernet class MySwitch(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] link = [] def __init__(self, *args,**kwargs): super(MySwitch,self).__init__(*args,**kwargs) self.mac_to_port = {} # Mac address is defined @set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER) def switch_features_handler(self, ev): datapath = ev.msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser #set if packet is lldp, send to controller actions = [parser.OFPActionOutput(port=ofproto.OFPP_CONTROLLER, max_len=ofproto.OFPCML_NO_BUFFER)] inst = [parser.OFPInstructionActions(type_=ofproto.OFPIT_APPLY_ACTIONS,actions=actions)] match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_LLDP) mod = parser.OFPFlowMod(datapath=datapath, priority=1, match=match, instructions=inst) datapath.send_msg(mod) self.send_port_desc_stats_request(datapath)# send the request def add_flow(self, datapath, priority, match, actions): ofproto = datapath.ofproto parser = datapath.ofproto_parser inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)] mod = parser.OFPFlowMod(datapath=datapath,priority=priority,match=match,instructions=inst) datapath.send_msg(mod) def send_port_desc_stats_request(self, datapath): ofproto = datapath.ofproto ofp_parser = datapath.ofproto_parser req = ofp_parser.OFPPortDescStatsRequest(datapath, 0) datapath.send_msg(req) # Send the lldp packet def send_lldp_packet(self, datapath, port, hw_addr, ttl): ofproto = datapath.ofproto ofp_parser = datapath.ofproto_parser pkt = packet.Packet() pkt.add_protocol(ethernet.ethernet(ethertype=ether_types.ETH_TYPE_LLDP,src=hw_addr ,dst=lldp.LLDP_MAC_NEAREST_BRIDGE)) #chassis_id = lldp.ChassisID(subtype=lldp.ChassisID.SUB_LOCALLY_ASSIGNED, chassis_id=str(datapath.id)) chassis_id = lldp.ChassisID(subtype=lldp.ChassisID.SUB_LOCALLY_ASSIGNED, chassis_id=str(datapath.id).encode('utf-8')) #port_id = lldp.PortID(subtype=lldp.PortID.SUB_LOCALLY_ASSIGNED, port_id=str(port)) port_id = lldp.PortID(subtype=lldp.PortID.SUB_LOCALLY_ASSIGNED, port_id=str(port).encode('utf-8')) #port_id = lldp.PortID(subtype=lldp.PortID.SUB_LOCALLY_ASSIGNED, port_id=b'1/3') ttl = lldp.TTL(ttl=1) end = lldp.End() tlvs = (chassis_id,port_id,ttl,end) pkt.add_protocol(lldp.lldp(tlvs)) pkt.serialize() self.logger.info("packet-out %s" % pkt) data = pkt.data actions = [ofp_parser.OFPActionOutput(port=port)] out = ofp_parser.OFPPacketOut(datapath=datapath, buffer_id=ofproto.OFP_NO_BUFFER, in_port=ofproto.OFPP_CONTROLLER, actions=actions, data=data) datapath.send_msg(out) @set_ev_cls(ofp_event.EventOFPPortDescStatsReply, MAIN_DISPATCHER) def port_desc_stats_reply_handler(self, ev): ports = [] for p in ev.msg.body: ports.append('port_no=%d hw_addr=%s name=%s config=0x%08x ' 'state=0x%08x curr=0x%08x advertised=0x%08x ' 'supported=0x%08x peer=0x%08x curr_speed=%d ' 'max_speed=%d' % (p.port_no, p.hw_addr, p.name, p.config, p.state, p.curr, p.advertised, p.supported, p.peer, p.curr_speed, p.max_speed)) self.logger.debug('OFPPortDescStatsReply received: %s', ports) ''' @set_ev_cls(ofp_event.EventOFPPortDescStatsReply, MAIN_DISPATCHER) def port_desc_stats_reply_handler(self, ev): datapath = ev.msg.datapath ofproto = datapath.ofproto ofp_parser = datapath.ofproto_parser ports = [] for stat in ev.msg.body: if stat.port_no <=ofproto.OFPP_MAX: ports.append({'port_no':stat.port_no,'hw_addr':stat.hw_addr}) for no in ports: in_port = no['port_no'] match = ofp_parser.OFPMatch(in_port = in_port) for other_no in ports: if other_no['port_no'] != in_port: out_port = other_no['port_no'] self.send_lldp_packet(datapath,no['port_no'],no['hw_addr'],10) actions = [ofp_parser.OFPActionOutput(out_port)] self.add_flow(datapath, 1, match, actions) ''' @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 pkt = packet.Packet(data=msg.data) dpid = datapath.id # switch id which send the packetin in_port = msg.match['in_port'] pkt_ethernet = pkt.get_protocol(ethernet.ethernet) pkt_lldp = pkt.get_protocol(lldp.lldp) if not pkt_ethernet: return #print(pkt_lldp) if pkt_lldp: self.handle_lldp(dpid,in_port,pkt_lldp.tlvs[0].chassis_id,pkt_lldp.tlvs[1].port_id) #self.logger.info("packet-in %s" % (pkt,)) # Link two switch def switch_link(self,s_a,s_b): return s_a + '<--->' + s_b def handle_lldp(self,dpid,in_port,lldp_dpid,lldp_in_port): switch_a = 'switch'+str(dpid)+', port'+str(in_port) switch_b = 'switch'+lldp_dpid.decode('utf-8')+', port'+lldp_in_port.decode('utf-8') link = self.switch_link(switch_a,switch_b) # Check the switch link is existed if not any(self.switch_link(switch_b,switch_a) == search for search in self.link): self.link.append(link) print(self.link)
生成拓扑
from mininet.log import setLogLevel,info from mininet.node import RemoteController from mininet.net import Mininet from mininet.cli import CLI from optparse import OptionParser def SetParse(): parser = OptionParser() parser.add_option("-n","--number",type="int",dest="switch_num",help="write the switch number",default=1 ) return parser.parse_args() def MininetTopo(switch_num): net = Mininet() info("Create host nodes.\n") h1 = net.addHost("h1") h2 = net.addHost("h2") info("Create switch node.\n") #s1 = net.addSwitch("s1",failMode = 'standalone') #s1 = net.addSwitch("s1",failMode = 'secure',protocols = 'OpenFlow13') for sw in range(1,switch_num+1): name = "s"+str(sw) net.addSwitch(name,failMode = 'secure',protocols = 'OpenFlow13') info("Create Links. \n") for link in range(0,switch_num+1): if link is 0: net.addLink(h1,"s"+str(link+1)) elif link is switch_num: net.addLink(h2,"s"+str(link)) else: net.addLink("s"+str(link),"s"+str(link+1)) info("Create controller ot switch. \n") net.addController(controller=RemoteController,ip='127.0.0.1',port=6633) info("Build and start network.\n") net.build() net.start() info("Run the mininet CLI") CLI(net) if __name__ == '__main__': setLogLevel('info') # Set Parse (options, args) = SetParse() print(options.switch_num) MininetTopo(options.switch_num)
mininet> net h1 h1-eth0:s1-eth1 h2 h2-eth0:s3-eth2 s1 lo: s1-eth1:h1-eth0 s1-eth2:s2-eth1 s2 lo: s2-eth1:s1-eth2 s2-eth2:s3-eth1 s3 lo: s3-eth1:s2-eth2 s3-eth2:h2-eth0 c0 mininet> node *** Unknown command: node mininet> nodes available nodes are: c0 h1 h2 s1 s2 s3 mininet> h1 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: h1-eth0@if116: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether b2:fe:b0:b5:c8:47 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.0.0.1/8 brd 10.255.255.255 scope global h1-eth0 valid_lft forever preferred_lft forever inet6 fe80::b0fe:b0ff:feb5:c847/64 scope link valid_lft forever preferred_lft forever mininet> h2 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: h2-eth0@if121: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 7a:73:94:49:38:bb brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.0.0.2/8 brd 10.255.255.255 scope global h2-eth0 valid_lft forever preferred_lft forever inet6 fe80::7873:94ff:fe49:38bb/64 scope link valid_lft forever preferred_lft forever mininet>
EVENT ofp_event->MySwitch EventOFPPortDescStatsReply move onto main mode EVENT ofp_event->MySwitch EventOFPPortDescStatsReply move onto main mode EVENT ofp_event->MySwitch EventOFPPortDescStatsReply OFPPortDescStatsReply received: ["port_no=4294967294 hw_addr=b6:ea:b2:d3:71:43 name=b's1' config=0x00000001 state=0x00000001 curr=0x00000000 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=0 max_speed=0", "port_no=1 hw_addr=9e:48:6a:42:e7:e6 name=b's1-eth1' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0", "port_no=2 hw_addr=26:fe:ef:c1:d1:cd name=b's1-eth2' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0"] OFPPortDescStatsReply received: ["port_no=4294967294 hw_addr=66:17:2e:64:b0:49 name=b's3' config=0x00000001 state=0x00000001 curr=0x00000000 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=0 max_speed=0", "port_no=1 hw_addr=1a:d2:d5:c9:3c:5e name=b's3-eth1' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0", "port_no=2 hw_addr=8a:d2:8d:2f:4c:21 name=b's3-eth2' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0"] OFPPortDescStatsReply received: ["port_no=4294967294 hw_addr=ba:69:a7:ba:36:4f name=b's2' config=0x00000001 state=0x00000001 curr=0x00000000 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=0 max_speed=0", "port_no=1 hw_addr=d6:63:a2:0c:7f:70 name=b's2-eth1' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0", "port_no=2 hw_addr=d2:3e:f9:67:b5:a0 name=b's2-eth2' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0"]
OFPPortDescStatsReply received没有h1 和h2
程序代码改为
OFPPortDescStatsReply received: ["port_no=1 hw_addr=9e:48:6a:42:e7:e6 name=b's1-eth1' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0", "port_no=2 hw_addr=26:fe:ef:c1:d1:cd name=b's1-eth2' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0"] OFPPortDescStatsReply received: ["port_no=1 hw_addr=d6:63:a2:0c:7f:70 name=b's2-eth1' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0", "port_no=2 hw_addr=d2:3e:f9:67:b5:a0 name=b's2-eth2' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0"] move onto main mode EVENT ofp_event->MySwitch EventOFPPortDescStatsReply OFPPortDescStatsReply received: ["port_no=1 hw_addr=1a:d2:d5:c9:3c:5e name=b's3-eth1' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0", "port_no=2 hw_addr=8a:d2:8d:2f:4c:21 name=b's3-eth2' config=0x00000000 state=0x00000004 curr=0x00000840 advertised=0x00000000 supported=0x00000000 peer=0x00000000 curr_speed=10000000 max_speed=0"]
123: s1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether b6:ea:b2:d3:71:43 brd ff:ff:ff:ff:ff:ff 124: s2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether ba:69:a7:ba:36:4f brd ff:ff:ff:ff:ff:ff 125: s3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 66:17:2e:64:b0:49 brd ff:ff:ff:ff:ff:ff
OFPPortDescStatsReply received没有s1 和s2、s3
s1-eth1 s1-eth2 s3-eth1 s3-eth2 s2-eth1 s2-eth2
mininet> net h1 h1-eth0:s1-eth1 h2 h2-eth0:s3-eth2 s1 lo: s1-eth1:h1-eth0 s1-eth2:s2-eth1 s2 lo: s2-eth1:s1-eth2 s2-eth2:s3-eth1 s3 lo: s3-eth1:s2-eth2 s3-eth2:h2-eth0