Pythonベースで、GoBGPを制御してみる 〜gRPC活用編〜
[ 2016.4.30修正:GoBGP最新版への対応 ]
GoBGPは、golangベースのBGPエンジンであり、経路追加などの設定コマンド等も、golangで実装されております。
今回は、Ryu SDN Frameworkとの連携を見据えて、PythonベースでGoBGPを制御する手法を試してみたいと思います。
元ネタは、こちらになります。
github.com
◆ まずは、構築準備から ...
(1) GoBGP環境の準備
前回のブログ記事に従って、PythonからgRPCを扱えるように環境を準備します。
ttsubo.hatenablog.com
(2) BGPルータ環境の準備
GoBGPと相互接続するBGPルータを準備します。
この段階で、BGPルータ側のBGPテーブルの状態を確認しておきます。
R1#show bgp ipv4 unicast BGP table version is 42, local router ID is 192.168.0.1 Status codes: s suppressed, d damped, h history, * valid, > best, i - internal, r RIB-failure, S Stale, m multipath, b backup-path, f RT-Filter, x best-external, a additional-path, c RIB-compressed, Origin codes: i - IGP, e - EGP, ? - incomplete RPKI validation codes: V valid, I invalid, N Not found Network Next Hop Metric LocPrf Weight Path *> 20.1.0.0/24 0.0.0.0 0 32768 i
(3) GoBGP動作開始
GoBGPを起動します。
$ cd $HOME $ cd golang/bin/ $ cat gobgpd.conf [global] [global.config] as = 65002 router-id = "192.168.0.2" [[neighbors]] [neighbors.config] peer-type = "external" neighbor-address = "192.168.0.1" peer-as = 65001 local-as = 65002 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" $ sudo ./gobgpd -f gobgpd.conf ... (snip)
この時、BGPルータとGoBGPとの間のリンクで、BGP UPDATEメッセージにより"Prefix情報"が伝搬される様子を確認しておきます。
ここでの確認ポイントとしては、
- "20.1.0.0/24"は、Network Layer reachability information(通称、nlri)のフィールドに設定されている。
- "Nexthop"や、"ORIGIN"などのBGPアトリビュートは、Path attributesのフィールドに設定させている。
をチェックしておいてください。
(4) GoBGP側でのBGPテーブル確認
先ほどのBGP UPDATEメッセージが、GoBGP側で反映できたことを確認しておきます。
$ gobgp global rib Network Next Hop AS_PATH Age Attrs *> 20.1.0.0/24 192.168.0.1 65001 00:03:22 [{Origin: i} {Med: 0}]
以上で、下準備が完了しました。
◆ gRPC基本動作の概要を理解する
GoBGPの内部構造として活用しているgRPCのインタフェース条件として、IDLファイルの内容を確認しておきます。
$ cd $GOPATH/src/github.com/osrg/gobgp/api $ cat gobgp.proto syntax = "proto3"; package gobgpapi; // Interface exported by the server. service GobgpApi { rpc GetGlobalConfig(Arguments) returns (Global) {} rpc ModGlobalConfig(ModGlobalConfigArguments) returns (Error) {} rpc GetNeighbors(Arguments) returns (stream Peer) {} rpc GetNeighbor(Arguments) returns (Peer) {} rpc ModNeighbor(ModNeighborArguments) returns(Error) {} rpc GetRib(Table) returns (Table) {} rpc Reset(Arguments) returns (Error) {} rpc SoftReset(Arguments) returns (Error) {} rpc SoftResetIn(Arguments) returns (Error) {} rpc SoftResetOut(Arguments) returns (Error) {} rpc Shutdown(Arguments) returns (Error) {} rpc Enable(Arguments) returns (Error) {} rpc Disable(Arguments) returns (Error) {} rpc ModPath(ModPathArguments) returns (ModPathResponse) {} rpc ModPaths(stream ModPathsArguments) returns (Error) {} rpc MonitorRib(Table) returns (stream Destination) {} rpc MonitorBestChanged(Arguments) returns (stream Destination) {} rpc MonitorPeerState(Arguments) returns (stream Peer) {} rpc GetMrt(MrtArguments) returns (stream MrtMessage) {} rpc ModMrt(ModMrtArguments) returns (Error) {} rpc ModBmp(ModBmpArguments) returns (Error) {} rpc GetRPKI(Arguments) returns (stream RPKI) {} rpc ModRPKI(ModRpkiArguments) returns (Error) {} rpc GetROA(Arguments) returns (stream ROA) {} rpc GetVrfs(Arguments) returns (stream Vrf) {} rpc ModVrf(ModVrfArguments) returns (Error) {} rpc GetDefinedSet(DefinedSet) returns (DefinedSet) {} rpc GetDefinedSets(DefinedSet) returns (stream DefinedSet) {} rpc ModDefinedSet(ModDefinedSetArguments) returns (Error) {} rpc GetStatement(Statement) returns (Statement) {} rpc GetStatements(Statement) returns (stream Statement) {} rpc ModStatement(ModStatementArguments) returns (Error) {} rpc GetPolicy(Policy) returns (Policy) {} rpc GetPolicies(Policy) returns (stream Policy) {} rpc ModPolicy(ModPolicyArguments) returns (Error) {} rpc GetPolicyAssignment(PolicyAssignment) returns (PolicyAssignment) {} rpc ModPolicyAssignment(ModPolicyAssignmentArguments) returns (Error) {} } … (snip)
今回のブログ記事では、二つのAPIの挙動に着目します。
- "MonitorBestChanged" : GoBGPで保持しているBGPテーブルの経路情報の変化の有無を監視する
- "ModPath" : GoBGPへの経路情報の設定を行う
ちなみに、これらのAPIを動作させる際には、事前に、引数/戻り値パラメータ定義を紐解く必要があります。
引数/戻り値パラメータとして入れ子定義されているパラメータを追っかけていくと、”Path"フィールドにたどり着きます。
message Path { bytes nlri = 1; repeated bytes pattrs = 2; int64 age = 3; bool best = 4; bool is_withdraw = 5; int32 validation = 6; bool no_implicit_withdraw = 7; uint32 family = 8; uint32 source_asn = 9; string source_id = 10; bool filtered = 11; bool stale = 12; bool is_from_external = 13; string neighbor_ip = 14; }
ここでの確認ポイントは、
- Pathフィールドのメンバとして、"nlri"および、"pattrs"が定義されている。
- "nlri"および、"pattrsの型は、"bytes"と定義されている。
すなわち、Pathフィールドの"nlri"および、"pattrs"は、BGP UPDATEメッセージのBGPアトリビュートに対応しております。
よって、"pattrs"は、ORIGIN等のBGPアトリビュートによって構成されることになります。
また、BGPアトリビュートに対応する各パラメータに値を設定する際には、"bytes"型に対応したバイナリ変換が必要になります。
今回は、RyuBGPのPacketパーサを活用してバイナリ変換に対応しました。
◆ BGPテーブル監視用サンプルアプリを試してみる
まずは、Pythonスクリプトから、RPCメソッド:”MonitorBestChanged”を動作させてみます。
任意のディレクトリを作成して、次のサンプルアプリを配備してください。
$ cd $HOME $ mkdir sample $ cd sample/ $ vi monitor_BestPathChange.py ----------------- import gobgp_pb2 import sys import signal import time import os from threading import Thread from ryu.lib.packet.bgp import IPAddrPrefix from ryu.lib.packet.bgp import _PathAttribute from ryu.lib.packet.bgp import BGPPathAttributeOrigin from ryu.lib.packet.bgp import BGPPathAttributeAsPath from ryu.lib.packet.bgp import BGPPathAttributeMultiExitDisc from ryu.lib.packet.bgp import BGPPathAttributeNextHop from ryu.lib.packet.bgp import BGPPathAttributeCommunities from grpc.beta import implementations _TIMEOUT_SECONDS = 1000 AFI_IP = 1 SAFI_UNICAST = 1 RF_IPv4_UC = AFI_IP<<16 | SAFI_UNICAST def run(gobgpd_addr, routefamily): channel = implementations.insecure_channel(gobgpd_addr, 50051) with gobgp_pb2.beta_create_GobgpApi_stub(channel) as stub: ribs = stub.MonitorBestChanged(gobgp_pb2.Arguments(family=routefamily), _TIMEOUT_SECONDS) for rib in ribs: paths_target = rib.paths for path_target in paths_target: nlri = IPAddrPrefix.parser(path_target.nlri) print "----------------------------" print (" Rib.prefix : %s" % nlri[0].prefix) for pattr in path_target.pattrs: path_attr = _PathAttribute.parser(pattr) if isinstance(path_attr[0], BGPPathAttributeOrigin): print (" Rib.origin : %s" % path_attr[0].value) elif isinstance(path_attr[0], BGPPathAttributeAsPath): if path_attr[0].type == 2: print(" Rib.aspath : %s" % path_attr[0].value) else: print(" Rib.aspath : ???") elif isinstance(path_attr[0], BGPPathAttributeMultiExitDisc): print (" Rib.med : %s" % path_attr[0].value) elif isinstance(path_attr[0], BGPPathAttributeNextHop): print (" Rib.nexthop : %s" % path_attr[0].value) elif isinstance(path_attr[0], BGPPathAttributeCommunities): for community in path_attr[0].communities: print(" Rib.community : %s" % community) print (" Rib.is_withdraw : %s" % path_target.is_withdraw) def receive_signal(signum, stack): print('signal received:%d' % signum) print('exit') os._exit(0) if __name__ == '__main__': gobgp = sys.argv[1] family = sys.argv[2] if family == "ipv4": routefamily = RF_IPv4_UC else: exit(1) signal.signal(signal.SIGINT, receive_signal) t = Thread(target=run, args=(gobgp, routefamily)) t.daemon = True t.start() # sleep 1 sec forever to keep main thread alive while True: time.sleep(1)
続いて、IDLファイルをコンパイルします。
すると、"gobgp_pb2.py"が自動生成されます。
$ GOBGP_API=$GOPATH/src/github.com/osrg/gobgp/api $ protoc -I=$GOPATH/src/github.com/osrg/gobgp/api --python_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_python_plugin` $GOPATH/src/github.com/osrg/gobgp/api/gobgp.proto $ ls -l total 184 -rw-rw-r-- 1 tsubo tsubo 180906 Apr 30 04:34 gobgp_pb2.py -rw-rw-r-- 1 tsubo tsubo 2768 Apr 30 04:34 monitor_BestPathChange.py
いよいよ、サンプルアプリを動かしてみます。
(1) 対向BGPルータ側で、Peerを切断しておく。
ここでは、物理インタフェースを停止されることで、Peerを切断しております。
R1(config)#int f1/0 R1(config-if)#shutdown
(2) GoBGPを起動する。
GoBGPを起動します。
$ sudo ./gobgpd -f gobgpd.conf {"level":"info","msg":"gobgpd started","time":"2016-04-30T05:17:37+09:00"} {"level":"info","msg":"finished reading the config file","time":"2016-04-30T05:17:37+09:00"} {"level":"info","msg":"Peer 192.168.0.1 is added","time":"2016-04-30T05:17:37+09:00"} {"level":"info","msg":"Add a peer configuration for 192.168.0.1","time":"2016-04-30T05:17:37+09:00"}
(3) サンプルアプリを起動する。
ちなみに、最後の引数"ipv4"は、AddressFamily: ipv4のみを抽出することを意図します。
$ python ./monitor_BestPathChange.py 192.168.0.2 ipv4
(4) 対向BGPルータ側で、Peerを開設する。
ここでは、物理インタフェースを起動されることで、Peerを開設しております。
R1(config-if)#no shutdown
(5) サンプルアプリ起動結果を確認する。
すると、BGP UPDATEメッセージの受信した様子が確認できます。
$ python ./monitor_BestPathChange.py 192.168.0.2 ipv4 ---------------------------- Rib.prefix : 20.1.0.0/24 Rib.origin : 0 Rib.aspath : [[65001]] Rib.nexthop : 192.168.0.1 Rib.med : 0 Rib.is_withdraw : False
(6) GoBGPのBGPテーブル確認する。
BGP UPDATEメッセージの受信に伴い、BGPテーブルが更新された様子が確認できます。
$ gobgp global rib Network Next Hop AS_PATH Age Attrs *> 20.1.0.0/24 192.168.0.1 65001 00:00:27 [{Origin: i} {Med: 0}]
◆ BGPテーブルへの経路追加用サンプルアプリを試してみる
続いて、Pythonスクリプトから、RPCメソッド:”ModPath”を動作させてみます。
先ほどのディレクトリに移動して、次のサンプルアプリを配備してください。
$ cd $HOME $ cd sample/ $ vi add_prefix.py ----------------- import gobgp_pb2 import sys from netaddr.ip import IPNetwork from ryu.lib.packet.bgp import BGPPathAttributeOrigin from ryu.lib.packet.bgp import IPAddrPrefix from ryu.lib.packet.bgp import BGPPathAttributeNextHop from grpc.beta import implementations _TIMEOUT_SECONDS = 10 Resource_GLOBAL = 0 def run(gobgpd_addr, prefix, nexthop): channel = implementations.insecure_channel(gobgpd_addr, 50051) with gobgp_pb2.beta_create_GobgpApi_stub(channel) as stub: subnet = IPNetwork(prefix) ipaddr = subnet.ip masklen = subnet.prefixlen nlri = IPAddrPrefix(addr=ipaddr, length=masklen) bin_nlri = nlri.serialize() nexthop = BGPPathAttributeNextHop(value=nexthop) bin_nexthop = nexthop.serialize() origin = BGPPathAttributeOrigin(value=2) bin_origin = origin.serialize() pattrs = [] pattrs.append(str(bin_nexthop)) pattrs.append(str(bin_origin)) path = {} path['nlri'] = str(bin_nlri) path['pattrs'] = pattrs uuid = stub.ModPath(gobgp_pb2.ModPathArguments(resource=Resource_GLOBAL, path=path) ,_TIMEOUT_SECONDS) if uuid: print "Success!" else: print "Error!" if __name__ == '__main__': gobgp = sys.argv[1] prefix = sys.argv[2] nexthop = sys.argv[3] run(gobgp, prefix, nexthop)
早速、サンプルアプリを起動してみます。
(1) GoBGPを起動する。
$ sudo ./gobgpd -f gobgpd.conf {"level":"info","msg":"gobgpd started","time":"2016-04-30T07:33:50+09:00"} {"level":"info","msg":"finished reading the config file","time":"2016-04-30T07:33:50+09:00"} {"level":"info","msg":"Peer 192.168.0.1 is added","time":"2016-04-30T07:33:50+09:00"} {"level":"info","msg":"Add a peer configuration for 192.168.0.1","time":"2016-04-30T07:33:50+09:00"} {"Key":"192.168.0.1","State":"BGP_FSM_OPENCONFIRM","Topic":"Peer","level":"info","msg":"Peer Up","time":"2016-04-30T07:33:54+09:00"}
(2) サンプルアプリを起動する。
うまく動作すれば、"Success!"と表示されます。
$ python ./add_prefix.py 192.168.0.2 30.1.0.0/24 0.0.0.0 Success!
(3) GoBGPのBGPテーブルを確認する。
先ほど、追加した経路"30.1.0.0/24"がエントリされているはずです。
$ gobgp global rib Network Next Hop AS_PATH Age Attrs *> 20.1.0.0/24 192.168.0.1 65001 00:03:05 [{Origin: i} {Med: 0}] *> 30.1.0.0/24 0.0.0.0 00:00:25 [{Origin: ?}]
(4) 対向BGPルータのBGPテーブルを確認する。
R1#show bgp ipv4 unicast BGP table version is 9, local router ID is 192.168.0.1 Status codes: s suppressed, d damped, h history, * valid, > best, i - internal, r RIB-failure, S Stale, m multipath, b backup-path, f RT-Filter, x best-external, a additional-path, c RIB-compressed, Origin codes: i - IGP, e - EGP, ? - incomplete RPKI validation codes: V valid, I invalid, N Not found Network Next Hop Metric LocPrf Weight Path *> 20.1.0.0/24 0.0.0.0 0 32768 i *> 30.1.0.0/24 192.168.0.2 0 65002 ?