Atom Login Admin

Above the clouds

Ryu コントローラでネットワークトポロジの検出

written on Saturday,September 07,2013

今回は、OpenFlow 1.3でRyuコントローラを用いてネットワークトポロジ検出に
挑戦してみた。
ネットワークトポロジ検出にはLLDPを活用する。
Ryuにはトポロジ検出の為のモジュールが既に存在しているが、OpenFlow1.3用に作り変えた。従って、実装自体はRyuの既存のモジュールを流用する形となっている。

今回は以下のような構成でネットワークトポロジ検出を行ってみた。
Network structures

大まかにLLDPを活用したネットワークトポロジ検出の仕組みを解説する。
LLDPパケットをコントローラで作り、packet_out でスイッチへと送る。
スイッチから出力されたLLDP は隣接するスイッチに届き、
packet_inメッセージでコントローラへと戻ってくる。
packet_inで戻ってきたパケットにはスイッチのポート情報や、データパスのIDが含まれている。
これら情報をもとにリンクを検出し、ネットワークトポロジを把握する。

ソースコードはSwitches_v1_3.pyより参照して頂きたい。
まず、OpenFlow1.3では未知のパケットを受け取った時のデフォルトの挙動がDropになっているので、あらかじめフローエントリの登録を行っておく必要がある。ハンドシェイク後のFeatures応答メッセージを受けたタイミングで行った。

    @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)

Flow-Modメッセージは、LLDPのイーサネットタイプを設定し、LLDPのパケットインをコントローラに送信するように設定した。
また、Flow-Modメッセージによる書き込みが完了する前にLLDPパケットをコントローラから送出することを防ぐ為に、
バリアーメッセージを送出する。
続いて、Flow-Modメッセージによる書き込みが完了した際にバリアーリプライメッセージを受け取る。
このタイミングで、ポート詳細をマルチパートメッセージで要求する。

    @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)

ポート情報が受け取れたら各ポートよりLLDPパケットを送出する。LLDPパケットには、データパスID、送出ポート、MACアドレスなどを含んでいる。

    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)

各ポートから送出されたLLDPパケットは隣接するスイッチへ送られ、パケットがスイッチへ届くとコントローラへpacket_inメッセージで送信される。パケットをパースし、送出ポート、データパスID、MACアドレスを取得する。また、メッセージより入力ポート、入力ポートのMACアドレス、データパスIDが取得できるので、リンクを検出することができる。

    @set_ev_cls(EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        """Handle packet_in events."""
        if not self.link_discovery:
            return
        msg = ev.msg
        fields = msg.match.fields
        datapath = msg.datapath
        ofproto = datapath.ofproto

        for f in fields:
            if f.header == ofproto.OXM_OF_IN_PORT:
                in_port = f.value

        try:
            src_dpid, src_port_no = LLDPPacket.lldp_parse(msg.data)
        except LLDPPacket.LLDPUnknownFormat as e:
            # This handler can receive all the packtes which can be
            # not-LLDP packet. Ignore it silently
            return
        else:
            dst_dpid = msg.datapath.id
            dst_port_no = in_port

            src = self._get_port(src_dpid, src_port_no)
            if not src or src.dpid == dst_dpid:
                return

            dst = self._get_port(dst_dpid, dst_port_no)
            if not dst:
                return

            old_peer = self.links.get_peer(src)
            if old_peer and old_peer != dst:
                old_link = Link(src, old_peer)
                self.send_event_to_observers(event.EventLinkDelete(old_link))

            link = Link(src, dst)
            if not link in self.links:
                self.send_event_to_observers(event.EventLinkAdd(link))

            if not self.links.update_link(src, dst):
                # reverse link is not detected yet.
                # So schedule the check early because it's very likely it's up
                try:
                    self.ports.lldp_received(dst)
                except KeyError as e:
                    # There are races between EventOFPPacketIn and
                    # EventDPPortAdd. So packet-in event can happend before
                    # port add event. In that case key error can happend.
                    pass
                else:
                    self.ports.move_front(dst)
                    self.lldp_event.set()
            if self.explicit_drop:
                self._drop_packet(msg)

リンク検出した結果は下記のとおり

Link: Port<dpid=3, port_no=2, LIVE> to Port<dpid=2, port_no=2, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=2, port_no=4, LIVE> to Port<dpid=5, port_no=2, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=2, port_no=3, LIVE> to Port<dpid=4, port_no=2, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=6, port_no=1, LIVE> to Port<dpid=1, port_no=5, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=5, port_no=2, LIVE> to Port<dpid=2, port_no=4, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=1, port_no=4, LIVE> to Port<dpid=5, port_no=1, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=4, port_no=1, LIVE> to Port<dpid=1, port_no=3, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=3, port_no=1, LIVE> to Port<dpid=1, port_no=2, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=1, port_no=5, LIVE> to Port<dpid=6, port_no=1, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=1, port_no=2, LIVE> to Port<dpid=3, port_no=1, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=1, port_no=3, LIVE> to Port<dpid=4, port_no=1, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=2, port_no=2, LIVE> to Port<dpid=3, port_no=2, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=4, port_no=2, LIVE> to Port<dpid=2, port_no=3, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=1, port_no=1, LIVE> to Port<dpid=2, port_no=1, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=5, port_no=1, LIVE> to Port<dpid=1, port_no=4, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=2, port_no=5, LIVE> to Port<dpid=6, port_no=2, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=6, port_no=2, LIVE> to Port<dpid=2, port_no=5, LIVE> timestamp 1378541410 (now 1378541492)
Link: Port<dpid=2, port_no=1, LIVE> to Port<dpid=1, port_no=1, LIVE> timestamp 1378541410 (now 1378541492)

ポートダウンによるリンク断の検出を行う仕組みも実装されており、リンク情報が更新されるようになっている。
また、LLDPパケットは一定周期で送出されリンク情報を更新している。
次回は、検出できたネットワークトポロジを利用して、OpenFlowの特性を生かした、ECMPの実現を検討してみる。
その前にまずは、リファクタリングしないとな。。

Comments

Add Comment

Login
This entry was tagged #Ryu #OpenFlow #SDN