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 ?