今回は、OpenFlow 1.3でRyuコントローラを用いてネットワークトポロジ検出に
挑戦してみた。
ネットワークトポロジ検出にはLLDPを活用する。
Ryuにはトポロジ検出の為のモジュールが既に存在しているが、OpenFlow1.3用に作り変えた。従って、実装自体はRyuの既存のモジュールを流用する形となっている。
今回は以下のような構成でネットワークトポロジ検出を行ってみた。
大まかに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