naoki86star

インターネットの片隅でなにかしら書いてみる

gobgp + flowspec by nodejs

 python使ってgobgpにflowspec投入するを試してみた後、今度はnodejsではどうか?やってみた。nodejsはサーバサイドをスクリプトだけで構築するのに選択肢のひとつに持っておくといいかなぁ、と考えているので。

 最初、@grpc/proto-loaderとゆうのを使えば

const protoGobgp = grpc.loadPackageDefinition(protoLoader.loadSync('/path/to/gobgp.proto')).gobgpapi

みたくかけるんじゃん(、しゅごーい、前段でファイル生成しなくていーじゃん、いい仕組みー、)と喜んでいた。けどもenumが参照できなくて、綴りとかなにか違うのかとか悩んでいたら、そうではなく@grpc/proto-loader*1はenumを読み込んで展開してくれないらしい。*2
結局、protocコマンドでプリコンパイルみたいなことするのが確実でbest practice的と考えた。

インストールパッケージ

sudo npm install google-protobuf
sudo npm install grpc
sudo npm install grpc-tools

protocの呼び出し
これの前に、gobgp.proto/attribute.protoを編集し

import "google/protobuf/any.proto";

とかのように書き換えた

$ node_modules/grpc-tools/bin/protoc  --plugin=protoc-gen-grpc=node_modules/grpc-tools/bin/grpc_node_plugin --proto_path=gobgp-2.9.0/api --js_out=import_style=commonjs,binary:. --grpc_out=. gobgp.proto
$ node_modules/grpc-tools/bin/protoc  --plugin=protoc-gen-grpc=node_modules/grpc-tools/bin/grpc_node_plugin --proto_path=gobgp-2.9.0/api --js_out=import_style=commonjs,binary:. --grpc_out=. attribute.proto

あとは、pythonでやったときに得た知見に対して、言語バインディングというか対応する書き方を引き当てていく。

  • 宣言
const grpc = require('grpc')
const gobgp_grpc_pb = require('./gobgp_grpc_pb');
const gobgp_pb = require('./gobgp_pb');
const attribute_grpc_pb = require('./attribute_grpc_pb');
const attribute_pb = require('./attribute_pb');

変数を明示的に使っているのは、grpc gobgp_grpc_pb だけ。nodejs経験低く、どう書いておくのがいいか悩む(ふり)。

  • Any.pack()処理用に関数化
function _pack(any, target, name) {
    const serialized = target.serializeBinary();
    any.pack(serialized, name);
}

今回一番はめられたところ。今時点?javascriptのprotobufのAnyは、シリアライザを明示的に指定させるようになっている。*3TypeNameというのもpack()の引数に入っていて、これをpackしたいオブジェクトから参照したかったのがそこまでやっていない(できる?)

  • メッセージ生成処理
function family(v) {
  var afi = proto.gobgpapi.Family.Afi.AFI_IP;
  if(v==6) proto.gobgpapi.Family.Afi.AFI_IP6;
  var f = new proto.gobgpapi.Family();
  f.setSafi(proto.gobgpapi.Family.Safi.SAFI_FLOW_SPEC_UNICAST);
  f.setAfi(afi);
  return f;
}

function build_flowspecipprefix(rules, typeval, ipprefix, ipprefixlen) {
  var o = new proto.google.protobuf.Any();
  var v = new proto.gobgpapi.FlowSpecIPPrefix();
  v.setType(typeval);
  v.setPrefix(ipprefix);
  v.setPrefixLen(ipprefixlen);

  _pack(o, v, 'gobgpapi.FlowSpecIPPrefix');
  rules.push(o);
}

function build_flowspeccomponent(rules, typeval, conditions) {
  var items = new Array();
  conditions.forEach(function(e){
    var op = e[0], val = e[1];
    var v = new proto.gobgpapi.FlowSpecComponentItem();
    v.setOp(op);
    v.setValue(val);
    items.push(v);
  });
  var o = new proto.google.protobuf.Any();
  var v = new proto.gobgpapi.FlowSpecComponent();
  v.setType(typeval);
  v.setItemsList(items);
  _pack(o, v, 'gobgpapi.FlowSpecComponent');
  rules.push(o);
}

function build_action(rate) {
  var o = new proto.google.protobuf.Any();
  var v = new proto.gobgpapi.TrafficRateExtended();
  v.setRate(rate);
  _pack(o, v, 'gobgpapi.TrafficRateExtended');
  var r = new proto.gobgpapi.ExtendedCommunitiesAttribute();
  r.setCommunitiesList([o]);
  return r;
}

function build_pattrs(v, nlri, rate, origin){
  var ac = new proto.google.protobuf.Any();
  _pack(ac, build_action(rate), 'gobgpapi.ExtendedCommunitiesAttribute');

  var nh = new proto.google.protobuf.Any();
  var nullip = (v==4) ? "0.0.0.0" : "::0";
  var _v = new proto.gobgpapi.MpReachNLRIAttribute();
  _v.setFamily(family(v));
  _v.setNlrisList([nlri]);
  _v.setNextHopsList([nullip]);
  _pack(nh, _v, 'gobgpapi.MpReachNLRIAttribute');

  var og = new proto.google.protobuf.Any();
  var _v2 = new proto.gobgpapi.OriginAttribute();
  _v2.setOrigin(origin);
  _pack(og, _v2, 'gobgpapi.OriginAttribute');

  return [ac, nh, og];
}

function build_nlri(rules){
  var o = new proto.google.protobuf.Any();
  var v = new proto.gobgpapi.FlowSpecNLRI();
  v.setRulesList(rules);
  _pack(o, v, 'gobgpapi.FlowSpecNLRI');

  return o;
}

ipv4で実行してみる。

var client = new gobgp_grpc_pb.GobgpApiClient('127.0.0.1:50051', grpc.credentials.createInsecure());
client.getBgp(new proto.gobgpapi.GetBgpRequest(),function(error, res){
  var srcas = res.getGlobal().getAs();

  var rules = [];
  build_flowspecipprefix(rules, 1, '1.2.3.4', 32);
  build_flowspecipprefix(rules, 2, '9.8.7.6', 32);
  build_flowspeccomponent(rules, 3, [[1,17]]);
  build_flowspeccomponent(rules, 4, [[1,1900],[1,11211]]);
  var nlri = build_nlri(rules);
  var path = new proto.gobgpapi.Path();
  path.setNlri(nlri);
  path.setPattrsList(build_pattrs(4,nlri,0.0,0));
  path.setFamily(family(4));
  path.setSourceAsn(srcas);

  var req = new proto.gobgpapi.AddPathRequest();
  req.setTableType(proto.gobgpapi.GLOBAL);
  req.setPath(path);
  client.addPath(req,function(e,r){
    console.log("err", e);
    console.log("res", r);
  });
});

レコード入っていってます。

nao@nrt:~/gobgp$ ./gobgp global rib -a ipv4-flowspec
   Network                                                                              Next Hop             AS_PATH              Age        Attrs
*> [destination: 1.2.3.4/32][source: 9.8.7.6/32][protocol: ==udp][port: ==1900 ==11211] fictitious                                00:00:03   [{Extcomms: [discard]} {Origin: i}]

以上でした。