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

 

 
原文地址:https://www.cnblogs.com/dream397/p/13156211.html