參考鏈接: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